{"id":46288,"date":"2025-09-04T10:36:09","date_gmt":"2025-09-04T08:36:09","guid":{"rendered":"https:\/\/cmm.imgw.pl\/?page_id=46288"},"modified":"2025-12-16T11:56:35","modified_gmt":"2025-12-16T10:56:35","slug":"prognozy-wiazkowe-imgw","status":"publish","type":"page","link":"https:\/\/cmm.imgw.pl\/?page_id=46288","title":{"rendered":"IMGW-PIB LMM: Prognozy wi\u0105zkowe"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"46288\" class=\"elementor elementor-46288\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-ec94721 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"ec94721\" 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-3669e89\" data-id=\"3669e89\" 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-1af22bf elementor-widget elementor-widget-html\" data-id=\"1af22bf\" 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\">\n  <div class=\"layout-container\">\n    <!-- Lewa kolumna z przyciskami -->\n    <div class=\"left-column\">\n      <!-- Krok 1: Wyb\u00f3r modelu -->\n      <div class=\"section\">\n        <h3>Model:<\/h3>\n        <div id=\"models\" class=\"buttons models-buttons\"><\/div>\n      <\/div>\n\n      <!-- Krok 2: Wyb\u00f3r terminu startu -->\n      <div class=\"section\">\n        <h3>Termin startu:<\/h3>\n        <div id=\"terms\" class=\"buttons terms-buttons\"><\/div>\n      <\/div>\n\n      <!-- Krok 3: Wyb\u00f3r parametru -->\n      <div class=\"section\">\n        <h3>Parametr:<\/h3>\n        <div id=\"params\" class=\"select-container\">\n          <select id=\"paramsSelect\"><\/select>\n        <\/div>\n      <\/div>\n\n      <!-- Krok 4: Wyb\u00f3r statystyki (dla standardowych modeli) -->\n      <div class=\"section\" id=\"statsSection\" style=\"display: none;\">\n        <h3>Statystyka:<\/h3>\n        <div id=\"stats\" class=\"select-container\">\n          <select id=\"statsSelect\"><\/select>\n        <\/div>\n      <\/div>\n      \n      <!-- Krok 4b: Wyb\u00f3r regionu (dla modeli z podsumowania z regionami) -->\n      <div class=\"section\" id=\"regionSection\" style=\"display: none;\">\n        <h3>Obszar:<\/h3>\n        <div id=\"regions\" class=\"buttons region-buttons\"><\/div>\n      <\/div>\n      \n      <!-- Krok 5: Wyb\u00f3r terminu prognozy -->\n      <div class=\"section\">\n        <h3 id=\"hoursTitle\">Godziny prognozy:<\/h3>\n        <div id=\"hours\" class=\"buttons\"><\/div>\n      <\/div>\n      \n      <!-- Informacja o czasie -->\n      <div class=\"time-note\">\n        *Je\u015bli przy formacie czasu nie widnieje adnotacja UTC to znaczy, \u017ce obraz generowany jest na wskazany termin wg czasu urz\u0119dowego.\n      <\/div>\n      \n      <!-- Informacja o obs\u0142udze klawiatury -->\n      <div class=\"keyboard-info\">\n        <strong>\ud83d\udca1 Skr\u00f3ty klawiszowe:<\/strong><br>\n        1-5 Wyb\u00f3r modelu<br>\n        &lt; &gt; Zmiana terminu startu<br>\n        \u2190 \u2192 Zmiana godzin prognozy<br>\n       <!---  \u2191 \u2193 Zmiana poziom\u00f3w ci\u015bnienia<br> -->\n        Tab Rozwini\u0119cie parametr\u00f3w<br>\n        <!--- Q Rozwini\u0119cie statystyk<br> -->\n        A Animacja godzin prognozy\n      <\/div>\n    <\/div>\n    \n    <!-- \u015arodkowa kolumna z suwakiem poziom\u00f3w ci\u015bnienia (widoczna tylko dla odpowiednich parametr\u00f3w) -->\n    <div class=\"slider-column\" id=\"pressureLevelSliderSection\" style=\"display: none;\">\n      <div class=\"section pressure-slider\">\n        <h3>Poziom ci\u015bnienia:<\/h3>\n        <div class=\"pressure-display\">\n          <span id=\"pressureValue\">1000 hPa<\/span>\n        <\/div>\n        <!-- Slider - ukryty na urz\u0105dzeniach mobilnych -->\n        <div class=\"vertical-slider-container\" id=\"verticalSliderContainer\">\n          <input type=\"range\" id=\"pressureLevelSlider\" class=\"vertical-slider\" min=\"0\" max=\"5\" step=\"1\" value=\"0\" orient=\"vertical\">\n          <div class=\"pressure-labels\">\n            <span class=\"pressure-label\" style=\"bottom: 100%\">300 hPa<\/span>\n            <span class=\"pressure-label\" style=\"bottom: 80%\">500 hPa<\/span>\n            <span class=\"pressure-label\" style=\"bottom: 60%\">700 hPa<\/span>\n            <span class=\"pressure-label\" style=\"bottom: 40%\">850 hPa<\/span>\n            <span class=\"pressure-label\" style=\"bottom: 20%\">950 hPa<\/span>\n            <span class=\"pressure-label\" style=\"bottom: 0%\">1000 hPa<\/span>\n          <\/div>\n        <\/div>\n        <!-- Przyciski - widoczne tylko na urz\u0105dzeniach mobilnych -->\n        <div class=\"buttons\" id=\"pressureLevelButtons\" style=\"display: none;\"><\/div>\n      <\/div>\n    <\/div>\n    \n    <!-- Prawa kolumna z obrazkiem -->\n    <div class=\"right-column\">\n      <!--- Wy\u015bwietlanie obrazka -->\n      <div class=\"section images\">\n        <h3 id=\"imageTitle\" style=\"display: none;\"><\/h3>\n        \n        <!-- Dodatkowy poziomy slider do zmiany terminu prognozy (nad obrazkiem) -->\n        <div class=\"horizontal-slider-container\" id=\"horizontalSliderContainer\">\n          <div class=\"slider-header\">\n            <span class=\"slider-title\" id=\"sliderTitle\">Godzina prognozy:<\/span>\n            <span class=\"hour-value\" id=\"hourValue\">00h<\/span>\n            <div class=\"slider-controls\">\n              <button id=\"animateBtn\" class=\"control-btn\" title=\"Animuj godziny prognozy\">\u25b6<\/button>\n            <\/div>\n          <\/div>\n          <input type=\"range\" id=\"forecastHourSlider\" class=\"horizontal-slider\" min=\"0\" max=\"0\" step=\"1\" value=\"0\">\n          <div class=\"hour-labels\" id=\"hourLabels\"><\/div>\n        <\/div>\n        \n        <div id=\"images\" class=\"images\"><\/div>\n        <div id=\"parameterDescription\" class=\"parameter-description\"><\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<style>\n  .forecast-app { \n    font-family: Arial, sans-serif;\n    max-width: 1200px;\n    margin: 0 auto;\n  }\n  \n  \/* Uk\u0142ad dwukolumnowy *\/\n  .layout-container {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 10px;\n  }\n  \n  .left-column {\n    flex: 1;\n    min-width: 230px;\n    max-width: 230px;\n  }\n  \n  .time-note {\n    font-size: 10px;\n    color: #666;\n    margin-top: 15px;\n    padding: 0 10px;\n    line-height: 1.4;\n    font-style: italic;\n    text-align: justify;\n  }\n  \n  .keyboard-info {\n    font-size: 11px;\n    color: #555;\n    margin-top: 20px;\n    padding: 10px;\n    line-height: 1.6;\n    background-color: #f0f8ff;\n    border-left: 3px solid rgba(86, 221, 208, 1);\n    border-radius: 4px;\n  }\n  \n  .keyboard-info strong {\n    color: rgba(86, 221, 208, 1);\n    font-size: 12px;\n  }\n  \n  .slider-column {\n    flex: 0 0 auto;\n    width: 110px;\n    margin-top: 180px;\n    \n  }\n  \n  .right-column {\n    flex: 2;\n    min-width: 400px;\n  }\n  \n  \/* Style dla suwaka pionowego *\/\n  .pressure-slider {\n    height: 400px;\n    display: flex;\n    flex-direction: column;\n    align-items: flex-start;\n  }\n  \n  .vertical-slider-container {\n    height: 265px;\n    position: relative;\n    margin: 70px 0 10px 0;\n    padding-left: 0px;\n    display: flex; \n    justify-content: center;\n    align-items: center;\n  }\n  \n  .vertical-slider {\n    -webkit-appearance: none;\n    appearance: none;\n    width: 260px;\n    height: 10px;\n    border-radius: 5px;\n    background: #e0e0e0;\n    outline: none;\n    position: absolute;\n    left: -115px;\n    top: 50px;\n    transform: rotate(270deg);\n    margin: 0;\n  }\n  \n  .vertical-slider::-webkit-slider-thumb {\n    -webkit-appearance: none;\n    appearance: none;\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    background: rgba(86, 221, 208, 1);\n    cursor: pointer;\n  }\n  \n  .vertical-slider::-moz-range-thumb {\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    background: rgba(86, 221, 208, 1);\n    cursor: pointer;\n  }\n  \n  .pressure-display {\n    margin-top: 0px;\n    margin-bottom: 20px;\n    padding-left: 0px;\n    padding-right: 0px;\n    padding-top: 10px;\n    font-size: 1.4em;\n    font-weight: bold;\n    align-self: center;\n    color: rgba(86, 221, 208, 1);\n  }\n  \n  \/* Style dla etykiet poziomu ci\u015bnienia *\/\n  .pressure-labels {\n    position: absolute;\n    height: 240px;\n    right: -72px;\n    top: -60px;\n    width: 40px;\n    z-index: 10;\n  }\n  \n  .pressure-label {\n    position: absolute;\n    right: 0;\n    font-size: 0.65em;\n    color: #666;\n    white-space: nowrap;\n    margin-bottom: -5px;\n    width: 45px;\n    text-align: right;\n  }\n  \n  \/* Dodanie ma\u0142ych kropek przy etykietach *\/\n  .pressure-label:before {\n    content: \"\";\n    position: absolute;\n    left: -15px;\n    top: 50%;\n    width: 4px;\n    height: 4px;\n    background-color: #666;\n    border-radius: 50%;\n    transform: translateY(-50%);\n  }\n  \n  \/* Style dla poziomego slidera termin\u00f3w prognozy *\/\n  .horizontal-slider-container {\n    width: 100%;\n    position: relative;\n    margin: 0px 0 20px 0;\n    padding: 10px 0;\n    background-color: #f5f5f5;\n    border-radius: 5px;\n    box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n    display: block;\n  }\n  \n  .horizontal-slider {\n    -webkit-appearance: none;\n    appearance: none;\n    width: 100%;\n    height: 10px;\n    border-radius: 5px;\n    background: linear-gradient(to right, #b0b0b0 0%, #b0b0b0 0%, #e0e0e0 0%, #e0e0e0 100%);\n    outline: none;\n    margin: 0px 0 10px 0;\n    position: relative;\n    z-index: 2;\n  }\n  \n  .horizontal-slider::-webkit-slider-thumb {\n    -webkit-appearance: none;\n    appearance: none;\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    background: rgba(86, 221, 208, 1);\n    cursor: pointer;\n    border: 2px solid #fff;\n    box-shadow: 0 1px 3px rgba(0,0,0,0.2);\n  }\n  \n  .horizontal-slider::-moz-range-thumb {\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    background: rgba(86, 221, 208, 1);\n    cursor: pointer;\n    border: 2px solid #fff;\n    box-shadow: 0 1px 3px rgba(0,0,0,0.2);\n  }\n  \n  .slider-header {\n    display: flex;\n    align-items: center;\n    text-align: left;\n    margin-bottom: 0px;\n    gap: 10px;\n  }\n  \n  .slider-title {\n    font-size: 1.1em;\n    font-weight: bold;\n    color: #333;\n    margin-right: 8px;\n  }\n  \n  .hour-value {\n    font-size: 1.3em;\n    font-weight: bold;\n    color: rgba(86, 221, 208, 1);\n  }\n  \n  .slider-controls {\n    margin-left: auto;\n    display: flex;\n    gap: 8px;\n  }\n  \n  .control-btn {\n    background: rgba(86, 221, 208, 0.1);\n    border: 2px solid rgba(86, 221, 208, 1);\n    color: rgba(86, 221, 208, 1);\n    font-size: 1.4em;\n    width: 40px;\n    height: 40px;\n    border-radius: 8px;\n    cursor: pointer;\n    transition: all 0.2s;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    padding: 0;\n  }\n  \n  .control-btn:hover {\n    background: rgba(86, 221, 208, 0.2);\n    transform: scale(1.05);\n  }\n  \n  .control-btn:active {\n    transform: scale(0.95);\n  }\n  \n  .control-btn.active {\n    background: rgba(86, 221, 208, 1);\n    color: white;\n  }\n  \n  .hour-labels {\n    display: flex;\n    justify-content: space-between;\n    margin-top: 0px;\n    color: #666;\n    font-size: 0.9em;\n    padding: 0 5px;\n  }\n  \n  \n  .section { \n    margin-bottom: 12px; \/* Zmniejszony margines dolny *\/\n    background: #f9f9f9;\n    padding: 10px; \/* Zmniejszony padding ca\u0142ej sekcji *\/\n    border-radius: 8px;\n    box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n  }\n  \n  .section h3 {\n    margin-top: 0;\n    margin-bottom: 6px; \/* Mniejszy margines pod nag\u0142\u00f3wkiem *\/\n    border-bottom: 1px solid #ddd;\n    padding-bottom: 5px; \/* Zmniejszony padding pod nag\u0142\u00f3wkiem *\/\n    color: #333;\n  }\n  \n  .buttons { \n    display: flex;\n    flex-wrap: wrap;\n    gap: 5px; \/* Zmniejszony odst\u0119p *\/\n    margin-top: 3px; \/* Dodany ma\u0142y margines na g\u00f3rze *\/\n  }\n  \n  .buttons button {\n    padding: 4px 10px; \/* Zmniejszony padding *\/\n    border: 1px solid #666;\n    border-radius: 6px;\n    background: #f5f5f5;\n    cursor: pointer;\n    transition: all 0.2s;\n    font-size: 0.9em;\n  }\n  \n  .buttons button:hover { \n    background: #ddd; \n  }\n  \n  .section.images {\n    min-height: 600px;\n  }\n  \n  #images.images {\n    min-height: 600px;\n  }\n  \n  .parameter-description {\n    margin-top: 15px;\n    padding: 10px;\n    background-color: #f8f9fa;\n    border: 1px solid #dee2e6;\n    border-radius: 4px;\n    font-size: 14px;\n    color: #333;\n    line-height: 1.5;\n    text-align: justify;\n  }\n  \n  .images img {\n    max-width: 100%;\n    margin: 5px auto;\n    border: 1px solid #ccc;\n    display: block;\n    box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n  }\n  \n  \/* Kontener obraz\u00f3w *\/\n  .images {\n    position: relative;\n    width: 100%;\n  }\n  \n  .buttons button.active {\n    background-color: rgba(86, 221, 208, 1);\n    color: white;\n    border-color: #2E7D32;\n  }\n  \n  .buttons button.loaded {\n    background: linear-gradient(135deg, #d8d8d8 0%, #b8b8b8 100%);\n    position: relative;\n    box-shadow: inset 0 1px 3px rgba(0,0,0,0.15);\n  }\n  \n  .buttons button.loaded:not(.active) {\n    background: linear-gradient(135deg, #c8c8c8 0%, #a8a8a8 100%);\n    color: #333;\n  }\n  \n  .buttons button.active.loaded {\n    background: linear-gradient(135deg, rgba(86, 221, 208, 1) 0%, rgba(56, 191, 178, 1) 100%);\n    box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);\n  }\n  \n  \/* Style dla przycisk\u00f3w modeli - ustawienie jeden pod drugim *\/\n  .models-buttons {\n    flex-direction: column;\n    align-items: center;\n    width: 100%;\n    gap: 3px; \/* Zmniejszony odst\u0119p mi\u0119dzy przyciskami *\/\n  }\n  \n  .models-buttons button {\n    width: 100%;\n    text-align: center;\n    margin: 1px 0; \/* Zmniejszony margines zewn\u0119trzny *\/\n    padding: 3px 12px; \/* Zmniejszony padding od g\u00f3ry i do\u0142u *\/\n  }\n  \n  \/* Style dla przycisk\u00f3w termin\u00f3w - wype\u0142nianie ca\u0142ej przestrzeni w wierszu *\/\n  .terms-buttons {\n    width: 100%;\n    justify-content: space-between;\n  }\n  \n  .terms-buttons button {\n    flex-grow: 1;\n    text-align: center;\n    margin: 1px 0; \/* Zmniejszony margines zewn\u0119trzny *\/\n  }\n  \n  \/* Style dla przycisk\u00f3w region\u00f3w - kolumna pionowa *\/\n  .region-buttons {\n    display: flex;\n    flex-direction: column;\n    gap: 4px;\n    width: 100%;\n  }\n  \n  .region-buttons button {\n    width: 100%;\n    text-align: center;\n    padding: 6px 12px;\n    font-size: 0.85em;\n  }\n  \n  \/* Style dla list rozwijanych *\/\n  .select-container {\n    width: 100%;\n    margin-bottom: 8px;\n    position: relative;\n  }\n  \n  .select-container select {\n    width: 100%;\n    padding: 8px 12px;\n    border: 1px solid #ccc;\n    border-radius: 6px;\n    background-color: #f5f5f5;\n    font-size: 0.9em;\n    min-height: 36px; \/* Minimalna wysoko\u015b\u0107 pola select *\/\n    height: auto; \/* Wysoko\u015b\u0107 dostosowuje si\u0119 do zawarto\u015bci *\/\n    white-space: normal; \/* Tekst mo\u017ce zawija\u0107 si\u0119 do nast\u0119pnej linii *\/\n    overflow: visible; \/* Tekst nie zostanie uci\u0119ty *\/\n    appearance: none;\n    -webkit-appearance: none;\n    -moz-appearance: none;\n    background-image: url(\"data:image\/svg+xml;utf8,<svg fill='black' height='24' viewBox='0 0 24 24' width='24' xmlns='http:\/\/www.w3.org\/2000\/svg'><path d='M7 10l5 5 5-5z'\/><path d='M0 0h24v24H0z' fill='none'\/><\/svg>\");\n    background-repeat: no-repeat;\n    background-position: right 8px top 50%;\n  }\n  \n  .select-container select option {\n    padding: 8px 12px; \/* Dodatkowe wype\u0142nienie dla opcji *\/\n    min-height: 28px; \/* Minimalna wysoko\u015b\u0107 opcji *\/\n    line-height: 1.4; \/* Zwi\u0119kszony odst\u0119p mi\u0119dzy wierszami *\/\n    white-space: normal; \/* Tekst mo\u017ce zawija\u0107 si\u0119 do nast\u0119pnej linii *\/\n  }\n  \n  \/* Style dla niestandardowego selecta *\/\n  .custom-select-wrapper {\n    position: relative;\n    width: 100%;\n  }\n  \n  .custom-select {\n    width: 100%;\n    padding: 8px 12px;\n    border: 1px solid #ccc;\n    border-radius: 6px;\n    background-color: #f5f5f5;\n    font-size: 0.9em;\n    min-height: 36px;\n    cursor: pointer;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n  \n  .custom-select:after {\n    content: '';\n    width: 0;\n    height: 0;\n    border-left: 5px solid transparent;\n    border-right: 5px solid transparent;\n    border-top: 5px solid #333;\n    margin-left: 10px;\n  }\n  \n  .custom-options {\n    position: absolute;\n    top: 100%;\n    left: 0;\n    right: 0;\n    background: white;\n    border: 1px solid #ccc;\n    border-radius: 0 0 6px 6px;\n    box-shadow: 0 2px 8px rgba(0,0,0,0.2);\n    max-height: 300px;\n    overflow-y: auto;\n    z-index: 100;\n    display: none;\n  }\n  \n  .custom-option {\n    padding: 8px 12px;\n    cursor: pointer;\n  }\n  \n  .custom-option:hover {\n    background-color: #f0f0f0;\n  }\n  \n  .custom-option.pressure-level {\n    color: rgba(86, 221, 208, 1) !important;\n    font-weight: bold;\n  }\n  \n  .select-container select:focus {\n    outline: none;\n    border-color: rgba(86, 221, 208, 1);\n    box-shadow: 0 0 5px rgba(76, 175, 80, 0.3);\n    height: auto; \/* Automatyczna wysoko\u015b\u0107 przy rozwini\u0119ciu *\/\n  }\n  \n  \/* Animacja \u0142adowania *\/\n  .loader-container {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    flex-direction: column;\n    width: 100%;\n    height: 300px;\n    z-index: 100;\n    position: relative;\n    background-color: rgba(245, 245, 245, 0.7); \/* Lekkie t\u0142o dla lepszej widoczno\u015bci *\/\n  }\n  \n  .loader {\n    border: 12px solid #f3f3f3;\n    border-radius: 50%;\n    border-top: 12px solid rgba(86, 221, 208, 1);\n    width: 100px;\n    height: 100px;\n    animation: spin 0.8s linear infinite;\n    z-index: 101;\n    box-shadow: 0px 0px 20px rgba(86, 221, 208, 0.7);\n  }\n  \n  @keyframes spin {\n    0% { transform: rotate(0deg); }\n    100% { transform: rotate(360deg); }\n  }\n  \n  \/* Responsywno\u015b\u0107 na ma\u0142ych ekranach *\/\n  @media (max-width: 768px) {\n    .layout-container {\n      flex-direction: column;\n    }\n    \n    .left-column, .right-column, .slider-column {\n      max-width: 100%;\n      width: 100%;\n    }\n    \n    .slider-column {\n      width: auto;\n      flex: 0 0 auto;\n      margin-top: 20px;\n    }\n    \n    .pressure-slider {\n      height: auto;\n      width: fit-content;\n      margin: 0 auto;\n    }\n    \n    #pressureLevelButtons {\n      display: flex !important;\n      flex-wrap: wrap;\n      gap: 10px;\n      justify-content: center;\n      width: fit-content;\n      margin: 4px auto;\n    }\n    \n    #pressureLevelButtons button {\n      min-width: 70px;\n    }\n    \n    .vertical-slider-container {\n      display: none !important;\n    }\n    \n    \/* Ukryj skr\u00f3ty klawiszowe na urz\u0105dzeniach mobilnych *\/\n    .keyboard-info {\n      display: none;\n    }\n  }\n<\/style>\n\n<script>\n\/\/ Wersja: 2025-01-24 21:00 - zachowywanie stanu: model \u2192 termin \u2192 parametr \u2192 godzina\n\/\/ const models = [\"EPS28km\", \"EPS70km\", \"gfs_lagged\", \"icon_lagged\", \"a-laef\", \"cosmo_ens\", \"icon_ens\", \"ecmwf_ifs_ens\", \"ecmwf_aifs_ens\"];\nconst models = [\"a-laef\", \"cosmo_ens\", \"icon_ens\", \"ecmwf_ifs_ens\", \"ecmwf_aifs_ens\"];\nconst termsDefault = [\"00\", \"06\", \"12\", \"18\"];\nconst alaefVariants = [\"1h\", \"12h\"];\n\n\/\/ parametry dla poszczeg\u00f3lnych modeli\nconst paramsForModels = {\n  \/\/ Dla a-laef dost\u0119pne s\u0105 r\u00f3\u017cne parametry zale\u017cnie od wariantu (1h lub 12h)\n  \"a-laef\": {\n    \"1h\": [\"tp\", \"t2m\", \"gst\"],\n    \"12h\": [\"t0m_min\", \"t2m_max\", \"t2m_min\", \"ws_max\", \"gst_max\", \"tp\", \"sf\"]\n  },\n  \/\/ Dla cosmo_ens\n  \"cosmo_ens\": {\n    \"12h\": [\"t0m_min\", \"t2m_max\", \"t2m_min\", \"ws_max\", \"gst_max\", \"tp\", \"sf\"]\n  },\n  \/\/ Dla icon_ens\n  \"icon_ens\": {\n    \"12h\": [\"t2m_max\", \"t2m_min\", \"ws_max\", \"gst_max\", \"tp\", \"sf\"]\n  },\n  \/\/ Dla ecmwf_ifs_ens\n  \"ecmwf_ifs_ens\": {\n     \"3h\": [\"vis_3h\", \"ptype3h\"],\n    \"12h\": [\"t0m_min\", \"t2m_max\", \"t2m_min\", \"ws_max\", \"gst_max\", \"ptype\", \"tp\", \"sf\", \"sd\", \"vis_min\"]\n  },\n  \/\/ Dla ecmwf_aifs_ens\n  \"ecmwf_aifs_ens\": {\n    \"12h\": [\"t2m_max\", \"t2m_min\", \"ws_max\", \"tp\", \"sf\"]\n  },\n  \"EPS28km\": [\"3CAPE\", \"CLCT\", \"CXCL\", \"GUST\", \"HIN\", \"IFS\", \"LCL_ML\", \"LFC_ML\", \"LPI\", \n             \"MCAPE\", \"NCIN\", \"PMSL\", \"PS\", \"Q1001\", \"Q3001\", \"Q6001\", \"Q8001\", \"RAIN\", \n             \"RELHUM\", \"SDI_1\", \"SDI_2\", \"SLI\", \"SNOW\", \"SWI\", \"SWISS00\", \"SWISS12\", \n             \"T2M\", \"TD2M\", \"TOT_PREC\", \"T_SURF\", \"U10M\", \"UCAPE\", \"VCIN\", \"VRANGE\",\n             \/\/ Parametry z poziomami: tylko mean\n             \"A\", \"A0300\", \"A0500\", \"A0700\", \"A0850\", \"A0950\", \"A1000\",\n             \"B\", \"B0300\", \"B0500\", \"B0700\", \"B0850\", \"B0950\", \"B1000\",\n             \"D\", \"D0300\", \"D0500\", \"D0700\", \"D0850\", \"D0950\", \"D1000\",\n             \"E\", \"E0300\", \"E0500\", \"E0700\", \"E0850\", \"E0950\", \"E1000\",\n             \"F\", \"F0300\", \"F0500\", \"F0700\", \"F0850\", \"F0950\", \"F1000\",\n             \"G\", \"G0300\", \"G0500\", \"G0700\", \"G0850\", \"G0950\", \"G1000\",\n             \"K\", \"K0300\", \"K0500\", \"K0700\", \"K0850\", \"K0950\", \"K1000\",\n             \"N\", \"N0300\", \"N0500\", \"N0700\", \"N0850\", \"N0950\", \"N1000\",\n             \"R\", \"R0300\", \"R0500\", \"R0700\", \"R0850\", \"R0950\", \"R1000\",\n             \"T\", \"T0300\", \"T0500\", \"T0700\", \"T0850\", \"T0950\", \"T1000\",\n             \"U\", \"U0300\", \"U0500\", \"U0700\", \"U0850\", \"U0950\", \"U1000\",\n             \"W\", \"W0300\", \"W0500\", \"W0700\", \"W0850\", \"W0950\", \"W1000\",\n             \"Y\", \"Y0300\", \"Y0500\", \"Y0700\", \"Y0850\", \"Y0950\", \"Y1000\",\n             \"Z\", \"Z0300\", \"Z0500\", \"Z0700\", \"Z0850\", \"Z0950\", \"Z1000\"],\n  \"EPS70km\": [\"A\", \"A0300\", \"A0500\", \"A0700\", \"A0850\", \"A0950\", \"A1000\",\n             \"B\", \"B0300\", \"B0500\", \"B0700\", \"B0850\", \"B0950\", \"B1000\",\n             \"CLCT\", \"CXCL\", \n             \"D\", \"D0300\", \"D0500\", \"D0700\", \"D0850\", \"D0950\", \"D1000\",\n             \"E\", \"E0300\", \"E0500\", \"E0700\", \"E0850\", \"E0950\", \"E1000\",\n             \"F\", \"F0300\", \"F0500\", \"F0700\", \"F0850\", \"F0950\", \"F1000\",\n             \"G\", \"G0300\", \"G0500\", \"G0700\", \"G0850\", \"G0950\", \"G1000\",\n             \"GUST\", \"HIN\", \"IFS\",\n             \"K\", \"K0300\", \"K0500\", \"K0700\", \"K0850\", \"K0950\", \"K1000\",\n             \"LPI\", \n             \"N\", \"N0300\", \"N0500\", \"N0700\", \"N0850\", \"N0950\", \"N1000\",\n             \"PMSL\", \"PS\", \n             \"Q\",\n             \"R\", \"R0300\", \"R0500\", \"R0700\", \"R0850\", \"R0950\", \"R1000\", \"RELHUM\",\n             \"T\", \"T0300\", \"T0500\", \"T0700\", \"T0850\", \"T0950\", \"T1000\", \"T2M\", \"TD2M\", \"TOT_PREC\", \"T_SURF\",\n             \"U\", \"U0300\", \"U0500\", \"U0700\", \"U0850\", \"U0950\", \"U1000\", \"U10M\",\n             \"VRANGE\",\n             \"W\", \"W0300\", \"W0500\", \"W0700\", \"W0850\", \"W0950\", \"W1000\",\n             \"Y\", \"Y0300\", \"Y0500\", \"Y0700\", \"Y0850\", \"Y0950\", \"Y1000\",\n             \"Z\", \"Z0300\", \"Z0500\", \"Z0700\", \"Z0850\", \"Z0950\", \"Z1000\"],\n  \"gfs_lagged\": [\"A\", \"A0300\", \"A0500\", \"A0700\", \"A0850\", \"A0950\", \"A1000\",\n             \"B\", \"B0300\", \"B0500\", \"B0700\", \"B0850\", \"B0950\", \"B1000\",\n             \"CLCT\", \"CXCL\", \n             \"D\", \"D0300\", \"D0500\", \"D0700\", \"D0850\", \"D0950\", \"D1000\",\n             \"E\", \"E0300\", \"E0500\", \"E0700\", \"E0850\", \"E0950\", \"E1000\",\n             \"F\", \"F0300\", \"F0500\", \"F0700\", \"F0850\", \"F0950\", \"F1000\",\n             \"G\", \"G0300\", \"G0500\", \"G0700\", \"G0850\", \"G0950\", \"G1000\",\n             \"GUST\", \"HIN\", \"IFS\",\n             \"K\", \"K0300\", \"K0500\", \"K0700\", \"K0850\", \"K0950\", \"K1000\",\n             \"LPI\", \n             \"N\", \"N0300\", \"N0500\", \"N0700\", \"N0850\", \"N0950\", \"N1000\",\n             \"PMSL\", \"PS\", \n             \"Q1001\", \"Q3001\", \"Q6001\", \"Q8001\", \n             \"R\", \"R0300\", \"R0500\", \"R0700\", \"R0850\", \"R0950\", \"R1000\", \"RELHUM\",\n             \"T\", \"T0300\", \"T0500\", \"T0700\", \"T0850\", \"T0950\", \"T1000\", \"T2M\", \"TD2M\", \"TOT_PREC\", \"T_SURF\",\n             \"U\", \"U0300\", \"U0500\", \"U0700\", \"U0850\", \"U0950\", \"U1000\", \"U10M\",\n             \"VRANGE\",\n             \"W\", \"W0300\", \"W0500\", \"W0700\", \"W0850\", \"W0950\", \"W1000\",\n             \"Y\", \"Y0300\", \"Y0500\", \"Y0700\", \"Y0850\", \"Y0950\", \"Y1000\",\n             \"Z\", \"Z0300\", \"Z0500\", \"Z0700\", \"Z0850\", \"Z0950\", \"Z1000\"],\n  \"icon_lagged\": [\"CAPE_CON\", \"CIN_ML\", \"CLCL\", \"CLCT\", \"PMSL\", \"PS\", \n              \"RELHUM\", \"T2M\", \"TD2M\", \"T_G\", \"TMAX_2M\", \"TMIN_2M\", \n              \"TOT_PREC\", \"U10M\", \"VMAX_10M\"]\n};\n\n\/\/ Konfiguracja region\u00f3w dla modeli z podsumowania\n\/\/ Parametry z tej listy maj\u0105 dodatkowe warianty regionalne (Sudety, Tatry, Bieszczady)\nconst regionsConfig = {\n  \"a-laef\": {\n    \/\/ Parametry kt\u00f3re maj\u0105 warianty regionalne w modelu a-laef\n    \"12h\": [\"sf\", \"gst_max\"]\n  },\n  \"cosmo_ens\": {\n    \"12h\": [\"sf\", \"gst_max\"]\n  }\n};\n\n\/\/ Definicja region\u00f3w\nconst regions = [\n  { id: null, name: \"Polska\" },\n  { id: \"sudety\", name: \"Sudety\" },\n  { id: \"tatry\", name: \"Tatry i Beskidy Zachodnie\" },\n  { id: \"bieszczady\", name: \"Bieszczady i Beskidy \u015arodkowe\" }\n];\n\n\n\/\/ Konfiguracja termin\u00f3w prognozy dla r\u00f3\u017cnych modeli\nconst forecastHours = {\n  \"a-laef\": {\n    \"1h\": Array.from({length: 67}, (_, i) => i + 6), \/\/ od 6 do 72\n    \"12h\": Array.from({length: 5}, (_, i) => i + 1)  \/\/ od 1 do 5\n  },\n  \"cosmo_ens\": {\n    \"12h\": Array.from({length: 3}, (_, i) => i + 1)  \/\/ od 1 do 3\n  },\n  \"icon_ens\": {\n    \"12h\": Array.from({length: 10}, (_, i) => i + 1)  \/\/ od 1 do 10\n  },\n  \"ecmwf_ifs_ens\": {\n    \"3h\": Array.from({length: 22}, (_, i) => i * 3 + 9).filter(h => h <= 72), \/\/ co 3h od 9 do 72\n    \"12h\": Array.from({length: 21}, (_, i) => i + 1)  \/\/ od 1 do 21\n  },\n  \"ecmwf_aifs_ens\": {\n    \"12h\": Array.from({length: 21}, (_, i) => i + 1)  \/\/ od 1 do 21\n  },\n  \"default\": Array.from({length: 13}, (_, i) => i * 3).filter(h => h <= 36) \/\/ co 3h od 00 do 36\n};\n\n\/\/ Obiekt do przechowywania dynamicznie okre\u015blonych godzin dla parametr\u00f3w\nconst paramHours = {\n  \"cosmo_ens\": {},\n  \"icon_ens\": {},\n  \"ecmwf_ifs_ens\": {},\n  \"ecmwf_aifs_ens\": {},\n  \"a-laef\": {}\n};\n\n\/\/ Mapowanie przyjaznych nazw modeli dla interfejsu u\u017cytkownika\nconst modelDisplayNames = {\n  \"a-laef\": \"A-LAEF (podsumowanie)\",\n  \"EPS28km\": \"COSMO2k8\",\n  \"EPS70km\": \"COSMO7k0\",\n  \"gfs_lagged\": \"GFS\",\n  \"icon_lagged\": \"ICON-EU\",\n  \"cosmo_ens\": \"COSMO (podsumowanie)\",\n  \"icon_ens\": \"ICON (podsumowanie)\",\n  \"ecmwf_ifs_ens\": \"ECMWF IFS (podsumowanie)\",\n  \"ecmwf_aifs_ens\": \"ECMWF AIFS (podsumowanie)\"\n};\n\n\/\/ Mapowanie przyjaznych nazw parametr\u00f3w dla interfejsu u\u017cytkownika\nconst parameterDisplayNames = {\n  \/\/ a-laef, cosmo_ens parametry\n  \"tp\": \"Opad ca\u0142kowity\",\n  \"t2m\": \"Temperatura 2m npg\",\n  \"gst\": \"Poryw wiatru\",\n  \"gst_max\": \"Maks. poryw wiatru\",\n  \"t0m_min\": \"Min. temperatura przy powierzchni\",\n  \"t2m_max\": \"Maks. temperatura 2m npg\",\n  \"t2m_min\": \"Min. temperatura 2m npg\",\n  \"ws_max\": \"Maks. pr\u0119dko\u015b\u0107 wiatru\",\n  \"vis_min\": \"Min. widzialno\u015b\u0107\",\n  \"vis_3h\": \"Widzialno\u015b\u0107\",\n  \"ptype\": \"Rodzaj opadu\",\n  \"ptype3h\": \"Rodzaj opadu\",\n  \"sf\": \"Opad \u015bniegu\",\n  \"sd\": \"Zmiana pokrywy \u015bnie\u017cnej\",\n  \n  \/\/ EPS28km, EPS70km, gfs_lagged parametry\n  \"RAIN\": \"Opad deszczu\",\n  \"SNOW\": \"Opad \u015bniegu\",\n  \"T2M\": \"Temperatura 2m npg\",\n  \"3CAPE\": \"CAPE 3km\",\n  \"UCAPE\": \"CAPE MU\",\n  \"VCIN\": \"CIN MU\",\n  \"PMSL\": \"Ci\u015bnienie na poz. morza\",\n  \"PS\": \"Ci\u015bnienie na poz. gruntu\",\n  \"Y\": \"Dywergencja\",\n  \"F\": \"Geopotencja\u0142\",\n  \"K\": \"Gradient temperatury\",\n  \"LPI\": \"Indeks potencja\u0142u wy\u0142adowa\u0144\",\n  \"IFS\": \"Indeks stabilno\u015bci mg\u0142y\",\n  \"SWI\": \"Indeks Showaltera\",\n  \"SWISS00\": \"Indeks Showaltera, uskok wiatru 00\",\n  \"SWISS12\": \"Surface Lifted Index, uskok wiatru 12\",\n  \"MCAPE\": \"Maksymalna warto\u015b\u0107 CAPE\",\n  \"NCIN\": \"Convective Inhibition\",\n  \"LCL_ML\": \"Poziom kondensacji\",\n  \"LFC_ML\": \"Poziom swobodnej kondensacji\",\n  \"U\": \"Pr\u0119dko\u015b\u0107 wiatru\",\n  \"TOT_PREC\": \"Opad ca\u0142kowity\",\n  \"SDI_1\": \"Supercell Detection Index 1\",\n  \"SDI_2\": \"Supercell Detection Index 2\",\n  \"SLI\": \"Surface Lifted Index\",\n  \"E\": \"Temp. ekwiw. -potencjal.\",\n  \"HIN\": \"Temp. odczuwalna (cie\u0144)\",\n  \"D\": \"Temp. punktu rosy\",\n  \"TD2M\": \"Temp. punktu rosy 2m npg\",\n  \"B\": \"Temp. term. zwil\u017c.\",\n  \"T\": \"Temperatura\",\n  \"A\": \"Topog. wzgl.\/Ci\u015b.\/Geopot.\",\n  \"W\": \"Pr\u0119dko\u015b\u0107 pionowa\",\n  \"R\": \"Wilgotno\u015b\u0107 wzgl\u0119dna\",\n  \"RELHUM\": \"Wilgotno\u015b\u0107 wzgl\u0119dna 2m npg\",\n  \"Z\": \"Wirowo\u015b\u0107\",\n  \"G\": \"Wysoko\u015b\u0107 izotermy\",\n  \"CLCT\": \"Zachmurzenie ca\u0142kowite\",\n  \"CXCL\": \"Zachmurzenie niskiego poziomu\",\n  \"VRANGE\": \"Zasi\u0119g widzialno\u015bci\",\n  \"GUST\": \"Poryw wiatru 10m npg\",\n  \"VRANGE\": \"Zasi\u0119g widzialno\u015bci\",\n  \"Q1001\": \"Uskok wiatru 0-1000m\",\n  \"Q3001\": \"Uskok wiatru 0-3000m\",\n  \"Q6001\": \"Uskok wiatru 0-6000m\",\n  \"Q8001\": \"Uskok wiatru 0-8000m\",\n  \"T_SURF\": \"Temp. powierzchni\",\n  \"U10M\": \"Pr\u0119dko\u015b\u0107 wiatru 10m npg\",\n  \"N\": \"Niedosyt punktu rosy\",\n  \"Q\": \"Uskok wiatru\",\n\n  \/\/ icon_lagged parametry\n  \"TOT_PREC\": \"Opad ca\u0142kowity\",\n  \"T2M\": \"Temperatura 2m npg\",\n  \"VMAX_10M\": \"Maks. pr\u0119dko\u015b\u0107 wiatru 10m npg\",\n  \"U10M\": \"Pr\u0119dko\u015b\u0107 wiatru 10m npg\",\n  \"CLCT\": \"Zachmurzenie ca\u0142kowite\",\n  \"CLCL\": \"Zachmurzenie chmurami niskimi\",\n  \"CAPE_CON\": \"CAPE\",\n  \"CIN_ML\": \"CIN\",\n  \"TMAX_2M\": \"Maks. temperatura 2m npg\",\n  \"TMIN_2M\": \"Min. temperatura 2m npg\",\n  \"T_G\": \"Temp. przy gruncie\",\n  \"TD2M\": \"Temp. punktu rosy 2m npg\",\n  \"RELHUM\": \"Wilgotno\u015b\u0107 wzgl\u0119dna 2m npg\",\n  \"PS\": \"Ci\u015bnienie na poz. gruntu\",\n  \"PMSL\": \"Ci\u015bnienie na poz. morza\",\n};\n\n\/\/ Przyjazne nazwy dla statystyk\nconst statisticsDisplayNames = {\n  \"mean\": \"\u015arednia z wi\u0105zki\",\n  \"maxof\": \"Absolutne maksimum w wi\u0105zce\",\n  \"minof\": \"Absolutne minimum w wi\u0105zce\",\n  \"spread\": \"Rozrzut w wi\u0105zce\",\n  \"max-min_dif\": \"R\u00f3\u017cnica max-min w wi\u0105zce\",\n  \/\/prawdopodobie\u0144stwa\n  \/\/CLOUD\n  \"prb1_12pct\": \"Prawdopodobie\u0144stwo przekroczenia 12%\",\n  \"prb2_25pct\": \"Prawdopodobie\u0144stwo przekroczenia 25%\",\n  \"prb3_36pct\": \"Prawdopodobie\u0144stwo przekroczenia 36%\",\n  \"prb4_50pct\": \"Prawdopodobie\u0144stwo przekroczenia 50%\",\n  \"prb5_62pct\": \"Prawdopodobie\u0144stwo przekroczenia 62%\",\n  \"prb6_75pct\": \"Prawdopodobie\u0144stwo przekroczenia 75%\",\n  \"prb7_85pct\": \"Prawdopodobie\u0144stwo przekroczenia 85%\",\n  \/\/GUST, WIND\n  \"prb1_30kmh\": \"Prawdopodobie\u0144stwo przekroczenia 30km\/h\",\n  \"prb2_50kmh\": \"Prawdopodobie\u0144stwo przekroczenia 50km\/h\",\n  \"prb3_70kmh\": \"Prawdopodobie\u0144stwo przekroczenia 70km\/h\",\n  \"prb4_90kmh\": \"Prawdopodobie\u0144stwo przekroczenia 90km\/h\",\n  \"prb5_110kmh\": \"Prawdopodobie\u0144stwo przekroczenia 110km\/h\",\n  \"prb6_115kmh\": \"Prawdopodobie\u0144stwo przekroczenia 115km\/h\",\n  \/\/SNOW, TOT PREC\n  \"prb1_0.1mm\": \"Prawdopodobie\u0144stwo przekroczenia 0.1mm\",\n  \"prb2_0.5mm\": \"Prawdopodobie\u0144stwo przekroczenia 0.5mm\",\n  \"prb3_1mm\": \"Prawdopodobie\u0144stwo przekroczenia 1mm\",\n  \"prb4_2mm\": \"Prawdopodobie\u0144stwo przekroczenia 2mm\",\n  \"prb5_5mm\": \"Prawdopodobie\u0144stwo przekroczenia 5mm\",\n  \"prb6_10mm\": \"Prawdopodobie\u0144stwo przekroczenia 10mm\",\n\n  \"prb2_1mm\": \"Prawdopodobie\u0144stwo przekroczenia 1mm\",\n  \"prb3_5mm\": \"Prawdopodobie\u0144stwo przekroczenia 5mm\",\n  \"prb4_10mm\": \"Prawdopodobie\u0144stwo przekroczenia 10mm\",\n  \"prb5_20mm\": \"Prawdopodobie\u0144stwo przekroczenia 20mm\",\n  \"prb6_30mm\": \"Prawdopodobie\u0144stwo przekroczenia 30mm\",\n  \"prb7_40mm\": \"Prawdopodobie\u0144stwo przekroczenia 40mm\",\n  \"prb8_50mm\": \"Prawdopodobie\u0144stwo przekroczenia 50mm\",\n  \/\/TEMP, TSURF, HIN\n  \"prb1_2C\": \"Prawdopodobie\u0144stwo przekroczenia 2\u00b0C\",\n  \"prb2_5C\": \"Prawdopodobie\u0144stwo przekroczenia 5\u00b0C\",\n  \"prb3_10C\": \"Prawdopodobie\u0144stwo przekroczenia 10\u00b0C\",\n  \"prb4_15C\": \"Prawdopodobie\u0144stwo przekroczenia 15\u00b0C\",\n  \"prb5_20C\": \"Prawdopodobie\u0144stwo przekroczenia 20\u00b0C\",\n  \"drb1_-5C\": \"Prawdopodobie\u0144stwo warto\u015bci < -5\u00b0C\",\n  \"drb2_-1C\": \"Prawdopodobie\u0144stwo warto\u015bci < -1\u00b0C\",\n  \"drb3_0C\": \"Prawdopodobie\u0144stwo warto\u015bci < 0\u00b0C\",\n  \"drb4_2C\": \"Prawdopodobie\u0144stwo warto\u015bci < 2\u00b0C\",\n  \"drb5_5C\": \"Prawdopodobie\u0144stwo warto\u015bci < 5\u00b0C\",\n  \"drb6_10C\": \"Prawdopodobie\u0144stwo warto\u015bci < 10\u00b0C\",\n\n  \"prb1_0C\": \"Prawdopodobie\u0144stwo przekroczenia 0\u00b0C\",\n  \"prb2_2C\": \"Prawdopodobie\u0144stwo przekroczenia 2\u00b0C\",\n  \"prb3_5C\": \"Prawdopodobie\u0144stwo przekroczenia 5\u00b0C\",\n  \"drb2_-2C\": \"Prawdopodobie\u0144stwo warto\u015bci < -2\u00b0C\",\n  \"drb3_-1C\": \"Prawdopodobie\u0144stwo warto\u015bci < -1\u00b0C\",\n  \"drb4_0C\": \"Prawdopodobie\u0144stwo warto\u015bci < 0\u00b0C\",\n\n  \"prb1_10C\": \"Prawdopodobie\u0144stwo przekroczenia 10\u00b0C\",\n  \"prb2_20C\": \"Prawdopodobie\u0144stwo przekroczenia 20\u00b0C\",\n  \"prb3_30C\": \"Prawdopodobie\u0144stwo przekroczenia 30\u00b0C\",\n  \"drb1_5C\": \"Prawdopodobie\u0144stwo warto\u015bci < 5\u00b0C\",\n  \"drb2_0C\": \"Prawdopodobie\u0144stwo warto\u015bci < 0\u00b0C\",\n  \"drb3_-5C\": \"Prawdopodobie\u0144stwo warto\u015bci < -5\u00b0C\",\n  \"drb4_-10C\": \"Prawdopodobie\u0144stwo warto\u015bci < -10\u00b0C\",\n  \"drb5_-20C\": \"Prawdopodobie\u0144stwo warto\u015bci < -20\u00b0C\",\n  \/\/LPI\n  \"prb1_0.001_\": \"Prawdopodobie\u0144stwo przekroczenia 0.001\",\n  \"prb2_0.01_\": \"Prawdopodobie\u0144stwo przekroczenia 0.01\",\n  \/\/SDI\n  \"prb1_0.00031s\": \"Prawdopodobie\u0144stwo przekroczenia 0.00031s\",\n  \"prb2_0.0031s\": \"Prawdopodobie\u0144stwo przekroczenia 0.0031s\",\n  \"drb1_-0.0031s\": \"Prawdopodobie\u0144stwo warto\u015bci < -0.0031s\",\n  \"drb2_-0.00031s\": \"Prawdopodobie\u0144stwo warto\u015bci < -0.00031s\",\n  \/\/SLI, SWI\n  \"drb1_-5.0K\": \"Prawdopodobie\u0144stwo warto\u015bci < -5.0K\",\n  \"drb2_-4.0K\": \"Prawdopodobie\u0144stwo warto\u015bci < -4.0K\",\n  \"drb3_-3.0K\": \"Prawdopodobie\u0144stwo warto\u015bci < -3.0K\",\n  \"drb4_0.0K\": \"Prawdopodobie\u0144stwo warto\u015bci < 0.0K\",\n  \/\/SWISS12\n  \"drb1_0.6K\": \"Prawdopodobie\u0144stwo warto\u015bci < 0.6K\",\n};\n\n\/\/ Formatowanie termin\u00f3w prognozy w formacie dwucyfrowym\nconst forecastHoursDisplayNames = {\n  \"0\": \"00\",\n  \"1\": \"01\",\n  \"2\": \"02\",\n  \"3\": \"03\",\n  \"4\": \"04\", \n  \"5\": \"05\",\n  \"6\": \"06\",\n  \"7\": \"07\",\n  \"8\": \"08\",\n  \"9\": \"09\"\n};\n\n\/\/ Opisy parametr\u00f3w dla dynamicznego wy\u015bwietlania\n\/\/ Wsp\u00f3lny opis dla wszystkich parametr\u00f3w - informacje o prognozach wi\u0105zkowych EPS\nconst epsDescription = `\n  <div style=\"font-size: 1.4rem;\">\n    <p>Czym s\u0105 meteorologiczne prognozy wi\u0105zkowe (EPS &ndash; Ensemble Prediction System)?<\/p>\n    <p>Opracowanie: dr Andrzej Mazur, IMGW-PIB CMM.<\/p>\n  <\/div>\n  <div>\n    <p>Niekt\u00f3rych proces\u00f3w atmosferycznych nie jeste\u015bmy w stanie opisa\u0107 z niesko\u0144czon\u0105 dok\u0142adno\u015bci\u0105. Dlaczego? Ze wzgl\u0119du na:<\/p>\n    <p>niepewno\u015b\u0107 (b\u0142\u0105d) pomiaru i jego propagacj\u0119 do warunk\u00f3w pocz\u0105tkowych modeli;<br \/>\n    brak wiedzy o chwilowych warunkach pogodowych na tyle dok\u0142adnej, aby b\u0142\u0105d w d\u0142ugookresowych obliczeniach by\u0142 niezauwa\u017calny;<br \/>\n    sko\u0144czon\u0105 dok\u0142adno\u015b\u0107 oblicze\u0144, parametryzacje zamiast dok\u0142adnego opisu zjawisk;<br \/>\n    b\u0142\u0119dy metod numerycznych, kt\u00f3re s\u0105 coraz bardziej widoczne wraz z wyd\u0142u\u017caniem horyzontu czasowego oblicze\u0144;<br \/>\n    &bdquo;efekt motyla&rdquo; &ndash; wra\u017cliwo\u015b\u0107 na warunki pocz\u0105tkowe (wg s\u0142\u00f3w E. Lorenza, autora koncepcji &bdquo;efektu motyla&rdquo;: &bdquo;dowolny uk\u0142ad fizyczny, kt\u00f3ry zachowuje si\u0119 nieokresowo, jest nieprzewidywalny&rdquo;).<\/p>\n    <div><p><a style=\"color: #c40000\" href=\"\/?page_id=43948\">Czytaj dalej w #AkademiaCMM.<\/a><\/p><\/div>\n  <\/div>\n`;\n\n\/\/ Opisy parametr\u00f3w - wszystkie parametry u\u017cywaj\u0105 wsp\u00f3lnego opisu EPS\nconst parameterDescriptions = {};\n\n\/\/ Konfiguracja sufix\u00f3w dla obrazk\u00f3w standardowych modeli\nconst suffixConfig = {\n  \/\/ Domy\u015blny zestaw sufix\u00f3w dla wszystkich parametr\u00f3w\n  \"default\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \n  \/\/ Parametry z pe\u0142nym zestawem statystyk (mean, maxof, minof, spread, max-min_dif)\n  \"3CAPE\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"CAPE\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"PMSL\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"PS\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \n  \/\/ Parametry icon_lagged z podstawowym zestawem statystyk\n  \"CAPE_CON\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"CIN_ML\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"CLCL\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"CLCT\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"PMSL\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"PS\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"RELHUM\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"TD2M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"T_G\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"TMAX_2M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"TMIN_2M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"U10M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"VMAX_10M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"IFS\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"LCL_ML\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"LFC_ML\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"MCAPE\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"NCIN\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"PMSL\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"PS\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"Q1001\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"Q3001\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"Q6001\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"Q8001\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"RAIN\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"RELHUM\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"SDI_2\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"TD2M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"UCAPE\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \"VCIN\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\"],\n  \n  \/\/ Parametry z prawdopodobie\u0144stwami (prb)\n  \"CLCT\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_12pct\", \"prb2_25pct\", \"prb3_36pct\", \"prb4_50pct\", \"prb5_62pct\", \"prb6_75pct\", \"prb7_85pct\"],\n  \"CXCL\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_12pct\", \"prb2_25pct\", \"prb3_36pct\", \"prb4_50pct\", \"prb5_62pct\", \"prb6_75pct\", \"prb7_85pct\"],\n  \"GUST\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_30kmh\", \"prb2_50kmh\", \"prb3_70kmh\", \"prb4_90kmh\", \"prb5_110kmh\", \"prb6_115kmh\"],\n  \"SNOW\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_0.1mm\", \"prb2_0.5mm\", \"prb3_1mm\", \"prb4_2mm\", \"prb5_5mm\", \"prb6_10mm\"],\n  \"TOT_PREC\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_0.1mm\", \"prb2_1mm\", \"prb3_5mm\", \"prb4_10mm\", \"prb5_20mm\", \"prb6_30mm\", \"prb7_40mm\", \"prb8_50mm\"],\n  \"U10M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_30kmh\", \"prb2_50kmh\", \"prb3_70kmh\", \"prb4_90kmh\", \"prb5_110kmh\", \"prb6_115kmh\", \"drb1_1.8kmh\", \"drb2_3.6kmh\"],\n  \/\/ Parametry icon_lagged z prawdopodobie\u0144stwami\n  \"tot_prec\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_0.1mm\", \"prb2_1mm\", \"prb3_5mm\", \"prb4_10mm\", \"prb5_20mm\", \"prb6_30mm\", \"prb7_40mm\", \"prb8_50mm\"],\n  \n  \/\/ Parametry z dodatkowymi statystykami (drb)\n  \"HIN\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_10C\", \"prb2_20C\", \"prb3_30C\", \"drb1_5C\", \"drb2_0C\", \"drb3_-5C\", \"drb4_-10C\", \"drb5_-20C\"],\n  \"LPI\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_0.001_\", \"prb2_0.01_\"],\n  \"SDI_1\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_0.00031s\", \"prb2_0.0031s\", \"drb1_-0.0031s\", \"drb2_-0.00031s\"],\n  \"SLI\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"drb1_-5.0K\", \"drb2_-4.0K\", \"drb3_-3.0K\", \"drb4_0.0K\"],\n  \"SWI\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"drb1_-5.0K\", \"drb2_-4.0K\", \"drb3_-3.0K\", \"drb4_0.0K\"],\n  \"SWISS12\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"drb1_0.6K\"],\n  \"T2M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_2C\", \"prb2_5C\", \"prb3_10C\", \"prb4_15C\", \"prb5_20C\", \"drb1_-5C\", \"drb2_-1C\", \"drb3_0C\", \"drb4_2C\", \"drb5_5C\", \"drb6_10C\"],\n  \/\/ Parametry icon_lagged z dodatkowymi statystykami\n  \"T2M\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_2C\", \"prb2_5C\", \"prb3_10C\", \"prb4_15C\", \"prb5_20C\", \"drb1_-5C\", \"drb2_-1C\", \"drb3_0C\", \"drb4_2C\", \"drb5_5C\", \"drb6_10C\"],\n  \"T_SURF\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"prb1_0C\", \"prb2_2C\", \"prb3_5C\", \"drb1_-5C\", \"drb2_-2C\", \"drb3_-1C\", \"drb4_0C\"],\n  \"VRANGE\": [\"mean\", \"maxof\", \"minof\", \"spread\", \"max-min_dif\", \"drb1_0.1km\", \"drb2_0.5km\", \"drb3_1.0km\", \"drb4_5.0km\", \"drb5_10.0km\"],\n  \n  \/\/ Parametry tylko z mean\n  \"A\": [\"mean\"], \"A0300\": [\"mean\"], \"A0500\": [\"mean\"], \"A0700\": [\"mean\"], \"A0850\": [\"mean\"], \"A0950\": [\"mean\"], \"A1000\": [\"mean\"],\n  \"B\": [\"mean\"], \"B0300\": [\"mean\"], \"B0500\": [\"mean\"], \"B0700\": [\"mean\"], \"B0850\": [\"mean\"], \"B0950\": [\"mean\"], \"B1000\": [\"mean\"],\n  \"D\": [\"mean\"], \"D0300\": [\"mean\"], \"D0500\": [\"mean\"], \"D0700\": [\"mean\"], \"D0850\": [\"mean\"], \"D0950\": [\"mean\"], \"D1000\": [\"mean\"],\n  \"E\": [\"mean\"], \"E0300\": [\"mean\"], \"E0500\": [\"mean\"], \"E0700\": [\"mean\"], \"E0850\": [\"mean\"], \"E0950\": [\"mean\"], \"E1000\": [\"mean\"],\n  \"F\": [\"mean\"], \"F0300\": [\"mean\"], \"F0500\": [\"mean\"], \"F0700\": [\"mean\"], \"F0850\": [\"mean\"], \"F0950\": [\"mean\"], \"F1000\": [\"mean\"],\n  \"G\": [\"mean\"], \"G0300\": [\"mean\"], \"G0500\": [\"mean\"], \"G0700\": [\"mean\"], \"G0850\": [\"mean\"], \"G0950\": [\"mean\"], \"G1000\": [\"mean\"],\n  \"K\": [\"mean\"], \"K0300\": [\"mean\"], \"K0500\": [\"mean\"], \"K0700\": [\"mean\"], \"K0850\": [\"mean\"], \"K0950\": [\"mean\"], \"K1000\": [\"mean\"],\n  \"N\": [\"mean\"], \"N0300\": [\"mean\"], \"N0500\": [\"mean\"], \"N0700\": [\"mean\"], \"N0850\": [\"mean\"], \"N0950\": [\"mean\"], \"N1000\": [\"mean\"],\n  \"Q\": [\"mean\"],\n  \"R\": [\"mean\"], \"R0300\": [\"mean\"], \"R0500\": [\"mean\"], \"R0700\": [\"mean\"], \"R0850\": [\"mean\"], \"R0950\": [\"mean\"], \"R1000\": [\"mean\"],\n  \"SWISS00\": [\"mean\"],\n  \"T\": [\"mean\"], \"T0300\": [\"mean\"], \"T0500\": [\"mean\"], \"T0700\": [\"mean\"], \"T0850\": [\"mean\"], \"T0950\": [\"mean\"], \"T1000\": [\"mean\"],\n  \"U\": [\"mean\"], \"U0300\": [\"mean\"], \"U0500\": [\"mean\"], \"U0700\": [\"mean\"], \"U0850\": [\"mean\"], \"U0950\": [\"mean\"], \"U1000\": [\"mean\"],\n  \"W\": [\"mean\"], \"W0300\": [\"mean\"], \"W0500\": [\"mean\"], \"W0700\": [\"mean\"], \"W0850\": [\"mean\"], \"W0950\": [\"mean\"], \"W1000\": [\"mean\"],\n  \"Y\": [\"mean\"], \"Y0300\": [\"mean\"], \"Y0500\": [\"mean\"], \"Y0700\": [\"mean\"], \"Y0850\": [\"mean\"], \"Y0950\": [\"mean\"], \"Y1000\": [\"mean\"],\n  \"Z\": [\"mean\"], \"Z0300\": [\"mean\"], \"Z0500\": [\"mean\"], \"Z0700\": [\"mean\"], \"Z0850\": [\"mean\"], \"Z0950\": [\"mean\"], \"Z1000\": [\"mean\"]\n};\n\n\/\/ Funkcja do renderowania przycisk\u00f3w\nfunction renderButtons(containerId, items, onClick, activeItem = null, displayNames = null) {\n  const container = document.getElementById(containerId);\n  container.innerHTML = \"\";\n  \n  \/\/ Sprawd\u017a czy renderujemy przyciski modeli\n  const isModelButtons = containerId === \"models\";\n  \n  items.forEach(item => {\n    const btn = document.createElement(\"button\");\n    \n    \/\/ Ustaw data-value dla identyfikacji przycisku\n    btn.dataset.value = item;\n    \n    \/\/ Je\u015bli istnieje przyjazna nazwa dla tego elementu, u\u017cyj jej\n    if (displayNames && displayNames[item]) {\n      btn.textContent = displayNames[item];\n    } else {\n      btn.textContent = item;\n    }\n    \n    \/\/ Ustaw klas\u0119 'active' dla aktywnego przycisku\n    if (activeItem && item === activeItem) {\n      btn.classList.add('active');\n    }\n    \n    btn.onclick = () => {\n      \/\/ Resetuj stan przy zmianie modelu\n      if (isModelButtons) {\n        \/\/ Wyczy\u015b\u0107 bufor obraz\u00f3w przy zmianie modelu\n        if (selectedModel !== item) {\n          resetStateForModelChange();\n        }\n      }\n      \n      \/\/ Usu\u0144 klas\u0119 'active' ze wszystkich przycisk\u00f3w w kontenerze\n      container.querySelectorAll('button').forEach(b => b.classList.remove('active'));\n      \/\/ Dodaj klas\u0119 'active' do klikni\u0119tego przycisku\n      btn.classList.add('active');\n      \n      \/\/ Wywo\u0142aj funkcj\u0119 onClick\n      onClick(item);\n    };\n    \n    container.appendChild(btn);\n  });\n}\n\n\/\/ Funkcja do renderowania list rozwijanych\nfunction renderSelect(selectId, items, onChange, displayNames = null) {\n  const select = document.getElementById(selectId);\n  select.innerHTML = \"\";\n  \n  \/\/ Dodanie domy\u015blnej opcji\n  const defaultOption = document.createElement(\"option\");\n  defaultOption.value = \"\";\n  defaultOption.textContent = \"-- Wybierz --\";\n  select.appendChild(defaultOption);\n  \n  \/\/ Dodanie opcji z listy\n  items.forEach(item => {\n    const option = document.createElement(\"option\");\n    option.value = item;\n    \n    \/\/ Je\u015bli istnieje przyjazna nazwa dla tego parametru, u\u017cyj jej\n    if (displayNames && displayNames[item]) {\n      option.textContent = displayNames[item];\n    } else {\n      option.textContent = item;\n    }\n    \n    \/\/ Ustawienie atrybutu dla parametr\u00f3w z poziomami ci\u015bnienia\n    if (selectId === \"paramsSelect\" && hasPressureLevels(item)) {\n      option.setAttribute(\"style\", \"color: rgba(86, 221, 208, 1) !important; font-weight: bold;\");\n    }\n    \n    select.appendChild(option);\n  });\n  \n  \/\/ Dodanie obs\u0142ugi zmiany\n  if (selectId === \"paramsSelect\" || selectId === \"statsSelect\") {\n    \/\/ Specjalna obs\u0142uga dla paramsSelect i statsSelect - zmiana tylko na Enter lub klikni\u0119cie myszk\u0105\n    let isSelectingWithMouse = false;\n    \n    \/\/ Wykryj klikni\u0119cie myszk\u0105 na opcji\n    select.addEventListener(\"mousedown\", function(e) {\n      isSelectingWithMouse = true;\n    });\n    \n    \/\/ Obs\u0142uga zmiany\n    select.onchange = function(e) {\n      \/\/ Je\u015bli to klikni\u0119cie myszk\u0105, wywo\u0142aj onChange\n      if (isSelectingWithMouse && this.value) {\n        onChange(this.value);\n        this.size = 0;\n        this.blur();\n        isSelectingWithMouse = false;\n        return;\n      }\n      \n      \/\/ W przeciwnym razie (strza\u0142ki) tylko zapisz warto\u015b\u0107, nie wywo\u0142uj onChange\n      e.preventDefault();\n      isSelectingWithMouse = false;\n    };\n    \n    \/\/ Obs\u0142uga Enter dla zatwierdzenia\n    select.addEventListener(\"keydown\", function(e) {\n      if (e.key === \"Enter\" && this.value) {\n        e.preventDefault();\n        onChange(this.value);\n        \/\/ Zamknij list\u0119 natychmiast\n        this.size = 0;\n        this.blur();\n      }\n    });\n  } else {\n    \/\/ Dla pozosta\u0142ych select - standardowa obs\u0142uga\n    select.onchange = function() {\n      if (this.value) {\n        onChange(this.value);\n      }\n    };\n  }\n}\n\n\/\/ Definiowanie domy\u015blnych warto\u015bci dla ka\u017cdego modelu\nconst defaultSettings = {\n  \"a-laef\": {\n    term: \"12h\",\n    param: \"t2m_max\",\n    hour: \"01\",\n    stat: null \/\/ a-laef nie ma statystyk\n  },\n  \"cosmo_ens\": {\n    term: \"12h\",\n    param: \"t2m_max\",\n    hour: \"01\",\n    stat: null \/\/ cosmo_ens nie ma statystyk\n  },\n  \"icon_ens\": {\n    term: \"12h\",\n    param: \"t2m_max\",\n    hour: \"01\",\n    stat: null \/\/ icon_ens nie ma statystyk\n  },\n  \"ecmwf_ifs_ens\": {\n    term: \"12h\",\n    param: \"t2m_max\",\n    hour: \"01\",\n    stat: null \/\/ ecmwf_ifs_ens nie ma statystyk\n  },\n  \"ecmwf_aifs_ens\": {\n    term: \"12h\",\n    param: \"t2m_max\",\n    hour: \"01\",\n    stat: null \/\/ ecmwf_aifs_ens nie ma statystyk\n  },\n  \"EPS28km\": {\n    term: \"00\",\n    param: \"T2M\",\n    hour: \"0\",\n    stat: \"mean\"\n  },\n  \"EPS70km\": {\n    term: \"00\",\n    param: \"T2M\",\n    hour: \"0\",\n    stat: \"mean\"\n  },\n  \"gfs_lagged\": {\n    term: \"00\",\n    param: \"T2M\",\n    hour: \"0\",\n    stat: \"mean\"\n  },\n  \"icon_lagged\": {\n    term: \"00\",\n    param: \"T2M\",\n    hour: \"0\",\n    stat: \"mean\"\n  }\n};\n\n\/\/ Inicjalizacja interfejsu z domy\u015blnymi warto\u015bciami\nfunction initializeInterface() {\n  \/\/ Ustawienie domy\u015blnego modelu\n  selectedModel = \"a-laef\";\n  \n  \/\/ Ustawienie domy\u015blnych warto\u015bci dla wybranego modelu\n  selectedTerm = defaultSettings[selectedModel].term;\n  selectedParam = defaultSettings[selectedModel].param;\n  selectedHour = defaultSettings[selectedModel].hour;\n  selectedStat = defaultSettings[selectedModel].stat;\n  \n  \/\/ Aktualizacja etykiety terminu\n  updateTermLabel();\n  selectedPressureLevel = null;\n  \n  \/\/ Inicjalizacja przycisk\u00f3w modeli z aktywnym modelem\n  renderButtons(\"models\", models, (model) => {\n    const prevModel = selectedModel;\n    selectedModel = model;\n    \n    \/\/ Zapisz poprzednie warto\u015bci przed zmian\u0105\n    const prevTerm = selectedTerm;\n    const prevStat = selectedStat;\n    const prevHour = selectedHour;\n    \n    \/\/ Sprawd\u017a czy zmieniamy typ modelu\n    const prevModelWasSummary = summaryModels.includes(prevModel);\n    const newModelIsSummary = summaryModels.includes(model);\n    const changingModelType = prevModelWasSummary !== newModelIsSummary;\n    \n    \/\/ Zachowaj poprzedni parametr\n    const prevParam = selectedParam;\n    \n    \/\/ Ustawienie domy\u015blnych warto\u015bci dla wybranego modelu\n    \/\/ Zachowaj parametr je\u015bli prze\u0142\u0105czamy mi\u0119dzy modelami podsumowania i parametr istnieje w nowym modelu\n    if (!changingModelType && newModelIsSummary && prevParam) {\n      const newTerm = defaultSettings[model].term;\n      const availableParams = paramsForModels[model] && paramsForModels[model][newTerm] ? paramsForModels[model][newTerm] : [];\n      if (availableParams.includes(prevParam)) {\n        selectedParam = prevParam;\n      } else {\n        selectedParam = defaultSettings[model].param;\n      }\n    } else {\n      selectedParam = defaultSettings[model].param;\n    }\n    \n    \/\/ Zachowaj godzin\u0119 TYLKO je\u015bli pozostajemy w tym samym typie modelu\n    if (!changingModelType && prevHour) {\n      selectedHour = prevHour;\n    } else {\n      selectedHour = defaultSettings[model].hour;\n    }\n    \n    \/\/ Zachowaj termin dla zwyk\u0142ych modeli\n    if (!summaryModels.includes(model)) {\n      if (prevTerm && termsDefault.includes(prevTerm)) {\n        selectedTerm = prevTerm;\n      } else {\n        selectedTerm = defaultSettings[model].term;\n      }\n      \n      \/\/ Zachowaj statystyk\u0119 dla zwyk\u0142ych modeli\n      const availableStats = suffixConfig[selectedParam] || suffixConfig[\"default\"];\n      if (prevStat && availableStats.includes(prevStat)) {\n        selectedStat = prevStat;\n      } else {\n        selectedStat = defaultSettings[model].stat;\n      }\n    } else {\n      selectedTerm = defaultSettings[model].term;\n      selectedStat = null; \/\/ Modele podsumowania nie maj\u0105 statystyk\n      selectedPressureLevel = null; \/\/ Modele podsumowania nie maj\u0105 poziom\u00f3w ci\u015bnienia\n      \/\/ Ukryj slider ci\u015bnienia dla modeli podsumowania\n      document.getElementById(\"pressureLevelSliderSection\").style.display = \"none\";\n    }\n    \n    \/\/ Resetuj region przy zmianie modelu (tylko warto\u015b\u0107, nie ukrywaj sekcji)\n    selectedRegion = null;\n    \n    \n    \/\/ NATYCHMIAST ukryj\/poka\u017c regiony dla nowego modelu\n    renderRegions();\n    \n    \n    \/\/ Aktualizacja etykiety terminu\n    updateTermLabel();\n\n    \/\/ Je\u015bli to model podsumowania, potrzebujemy sprawdzi\u0107 dost\u0119pne godziny\n    if (summaryModels.includes(model)) {\n      determineAvailableHours(model, selectedTerm, selectedParam)\n        .then(() => {\n          updateTermButtons();\n          renderParams();\n          renderRegions();\n          renderForecastHours();\n        })\n        .catch(err => {\n          console.error(\"determineAvailableHours failed:\", err);\n        });\n    } else {\n      \/\/ Dla standardowych modeli ukryj sekcj\u0119 region\u00f3w\n      const regionSection = document.getElementById(\"regionSection\");\n      const regionsContainer = document.getElementById(\"regions\");\n      if (regionsContainer) regionsContainer.innerHTML = \"\";\n      if (regionSection) regionSection.style.display = \"none\";\n      selectedRegion = null;\n      updateTermButtons();\n    }\n  }, selectedModel, modelDisplayNames);\n  \n  \/\/ Inicjalizacja pozosta\u0142ych element\u00f3w\n  \n  \/\/ Je\u015bli pocz\u0105tkowy model jest modelem podsumowania, sprawdzamy dost\u0119pne godziny\n  if (summaryModels.includes(selectedModel)) {\n    determineAvailableHours(selectedModel, selectedTerm, selectedParam)\n      .then(() => {\n        updateTermButtons();\n        renderForecastHours();\n      });\n  } else {\n    updateTermButtons();\n  }\n}\n\n\/\/ Lista wszystkich modeli typu \"podsumowanie\"\nconst summaryModels = [\"a-laef\", \"cosmo_ens\", \"icon_ens\", \"ecmwf_ifs_ens\", \"ecmwf_aifs_ens\"];\n\n\/\/ Dodajemy style CSS dla komunikatu \u0142adowania\nconst styleElement = document.createElement('style');\nstyleElement.textContent = `\n  .loading-hours {\n    padding: 10px;\n    text-align: center;\n    color: #666;\n    font-style: italic;\n  }\n`;\ndocument.head.appendChild(styleElement);\n\n\/\/ Funkcja do sprawdzenia czy plik istnieje\n\/\/ Cache dla sprawdzania obrazk\u00f3w\nconst imageExistsCache = {};\n\nfunction checkImageExists(url) {\n  \/\/ Sprawd\u017a cache\n  if (imageExistsCache.hasOwnProperty(url)) {\n    return Promise.resolve(imageExistsCache[url]);\n  }\n  \n  \/\/ U\u017cyj fetch z HEAD request - szybsze ni\u017c \u0142adowanie ca\u0142ego obrazka\n  return fetch(url, { \n    method: 'HEAD',\n    cache: 'no-cache'\n  })\n    .then(response => {\n      const exists = response.ok;\n      imageExistsCache[url] = exists;\n      return exists;\n    })\n    .catch(error => {\n      \/\/ Je\u015bli fetch nie dzia\u0142a (np. CORS), u\u017cyj fallback z Image\n      return new Promise((resolve) => {\n        const img = new Image();\n        let timer = null;\n        \n        timer = setTimeout(() => {\n          img.onload = img.onerror = null;\n          imageExistsCache[url] = false;\n          resolve(false);\n        }, 800);\n        \n        img.onload = () => {\n          clearTimeout(timer);\n          imageExistsCache[url] = true;\n          resolve(true);\n        };\n        \n        img.onerror = () => {\n          clearTimeout(timer);\n          imageExistsCache[url] = false;\n          resolve(false);\n        };\n        \n        img.src = url;\n      });\n    });\n}\n\n\/\/ Funkcja do dynamicznego ustalania dost\u0119pnych godzin dla parametr\u00f3w\n\/\/ Zmienna do \u015bledzenia aktualnie sprawdzanych dost\u0119pno\u015bci godzin\nconst pendingHourChecks = {};\n\nasync function determineAvailableHours(model, term, param) {\n  \/\/ modele typu podsumowanie\n  if (!summaryModels.includes(model)) return;\n  \n  \/\/ Stw\u00f3rz klucz do identyfikacji tego konkretnego sprawdzenia\n  const checkKey = `${model}_${term}_${param}`;\n  \n  \/\/ Sprawd\u017a czy takie samo sprawdzenie jest ju\u017c w toku\n  if (pendingHourChecks[checkKey]) {\n    return pendingHourChecks[checkKey]; \/\/ Zwr\u00f3\u0107 obietnic\u0119 z ju\u017c trwaj\u0105cego sprawdzania\n  }\n  \n  \/\/ Utw\u00f3rz now\u0105 obietnic\u0119 i zapisz j\u0105, aby unikn\u0105\u0107 duplikacji \u017c\u0105da\u0144\n  const promise = new Promise(async (resolve) => {\n    try {\n      \/\/ Reszta kodu funkcji (zostanie wykonana po return)\n      const result = await performAvailableHoursCheck(model, term, param);\n      \/\/ Usu\u0144 klucz z pendingHourChecks po zako\u0144czeniu sprawdzania\n      delete pendingHourChecks[checkKey];\n      resolve(result);\n    } catch (error) {\n      console.error(\"B\u0142\u0105d podczas sprawdzania dost\u0119pnych godzin:\", error);\n      \/\/ Usu\u0144 klucz nawet w przypadku b\u0142\u0119du, aby umo\u017cliwi\u0107 ponowne pr\u00f3by\n      delete pendingHourChecks[checkKey];\n      resolve([]);\n    }\n  });\n  \n  \/\/ Zapisz obietnic\u0119 w s\u0142owniku pendingHourChecks\n  pendingHourChecks[checkKey] = promise;\n  \n  return promise; \/\/ Zwr\u00f3\u0107 obietnic\u0119 do obs\u0142ugi przez wywo\u0142uj\u0105cego\n}\n\n\/\/ Funkcja wykonuj\u0105ca rzeczywiste sprawdzenie dost\u0119pnych godzin\nasync function performAvailableHoursCheck(model, term, param) {\n  \/\/ komunikat o wyszukiwaniu godzin\n  const hoursContainer = document.getElementById(\"hours\");\n  if (hoursContainer) {\n    hoursContainer.innerHTML = '<div class=\"loading-hours\">Sprawdzanie dost\u0119pnych godzin...<\/div>';\n  }\n  \n  \/\/ animacja \u0142adowania w kontenerze obraz\u00f3w\n  const imagesContainer = document.getElementById(\"images\");\n  let loaderId = null;\n  if (imagesContainer) {\n    imagesContainer.innerHTML = \"\";\n    loaderId = \"loader-\" + new Date().getTime();\n    const loaderContainer = document.createElement(\"div\");\n    loaderContainer.className = \"loader-container\";\n    loaderContainer.id = loaderId;\n    const loader = document.createElement(\"div\");\n    loader.className = \"loader\";\n    loaderContainer.appendChild(loader);\n    imagesContainer.appendChild(loaderContainer);\n    \n    \/\/ timeout na usuni\u0119cie animacji (5 sekund)\n    setTimeout(function() {\n      const loaderElement = document.getElementById(loaderId);\n      if (loaderElement) {\n        \/\/ obrazek zast\u0119pczy\n        imagesContainer.innerHTML = \"\";\n        \n        \n        const errorImg = document.createElement(\"img\");\n        errorImg.src = \"\/wp-content\/uploads\/production\/prognozy_wiazkowe\/configuration_not.png\";\n        errorImg.alt = \"Konfiguracja niedost\u0119pna\";\n        errorImg.style.maxWidth = \"100%\";\n        errorImg.style.display = \"block\";\n        imagesContainer.appendChild(errorImg);\n        \n        \/\/  komunikat\n        const errorMsg = document.createElement(\"p\");\n        errorMsg.textContent = \"Brak dost\u0119pnych prognoz dla wybranej konfiguracji.\";\n        errorMsg.style.color = \"red\";\n        errorMsg.style.textAlign = \"center\";\n        imagesContainer.appendChild(errorMsg);\n      }\n    }, 5000); \/\/ 5 sekund\n  }\n  \n  \/\/ Inicjalizacja struktury paramHours dla modelu, je\u015bli nie istnieje\n  if (!paramHours[model]) {\n    paramHours[model] = {};\n  }\n  \n  \/\/ maksymalny zakres godzin w zale\u017cno\u015bci od modelu i wariantu\n  let maxHour = 10; \/\/ domy\u015blnie 10 godzin\n  if (model === \"cosmo_ens\") {\n    maxHour = 3;\n  } else if (model === \"ecmwf_ifs_ens\" || model === \"ecmwf_aifs_ens\") {\n    maxHour = (term === \"3h\") ? 72 : 21;\n  } else if (model === \"a-laef\") {\n    maxHour = 5; \n  }\n  \n  \/\/ tablica wszystkich mo\u017cliwych godzin dla modelu\n  const allPossibleHours = [];\n  if (model === \"ecmwf_ifs_ens\" && term === \"3h\") {\n    \/\/ Pliki s\u0105 co 3h od 09 do 72 (np. vis_3h_09.webp)\n    for (let h = 9; h <= 72; h += 3) {\n      allPossibleHours.push(String(h).padStart(2, \"0\"));\n    }\n  } else {\n    for (let i = 1; i <= maxHour; i++) {\n      \/\/ godziny jako stringi z wiod\u0105cym zerem dla 1-9\n      const formattedHour = String(i).padStart(2, \"0\");\n      allPossibleHours.push(formattedHour);\n    }\n  }\n  \n  \/\/ Optymalizacja: sprawdzanie godzin w BATCHACH (po 12 r\u00f3wnolegle z HEAD request)\n  const BATCH_SIZE = 12;\n  const availableHours = [];\n  const startTime = performance.now();\n  \n  \/\/ Funkcja sprawdzaj\u0105ca pojedyncz\u0105 godzin\u0119\n  const checkSingleHour = async (hour) => {\n    \/\/ Sprawdzanie z wiod\u0105cym zerem\n    let fileName = generateFileName(model, term, param, hour, null, null);\n    let url = `\/wp-content\/uploads\/production\/prognozy_wiazkowe\/${model}\/${term}\/${fileName}`;\n    let hourAvailable = await checkImageExists(url);\n    \n    \/\/ Je\u015bli nie znaleziono pliku z wiod\u0105cym zerem, spr\u00f3buj bez wiod\u0105cego zera\n    if (!hourAvailable && hour.startsWith(\"0\")) {\n      const hourWithoutLeadingZero = hour.replace(\/^0+\/, '');\n      fileName = generateFileName(model, term, param, hourWithoutLeadingZero, null, null);\n      url = `\/wp-content\/uploads\/production\/prognozy_wiazkowe\/${model}\/${term}\/${fileName}`;\n      hourAvailable = await checkImageExists(url);\n    }\n    \n    return { hour, available: hourAvailable };\n  };\n  \n  \/\/ Przetwarzaj godziny w batchach z early stopping\n  let consecutiveMissing = 0;\n  const MAX_CONSECUTIVE_MISSING = 3; \/\/ Je\u015bli 3 z rz\u0119du brakuje, przestajemy sprawdza\u0107\n  \n  for (let i = 0; i < allPossibleHours.length; i += BATCH_SIZE) {\n    \/\/ Early stop je\u015bli za du\u017co kolejnych brakuj\u0105cych\n    if (consecutiveMissing >= MAX_CONSECUTIVE_MISSING) {\n      break;\n    }\n    \n    const batch = allPossibleHours.slice(i, i + BATCH_SIZE);\n    const batchResults = await Promise.all(batch.map(checkSingleHour));\n    \n    \/\/ Dodaj dost\u0119pne godziny z tego batcha i \u015bled\u017a kolejne braki\n    batchResults.forEach(result => {\n      if (result.available) {\n        availableHours.push(result.hour);\n        consecutiveMissing = 0; \/\/ Reset licznika\n      } else {\n        consecutiveMissing++;\n      }\n    });\n  }\n  \n  \/\/ Usu\u0144 animacj\u0119 \u0142adowania, je\u015bli wci\u0105\u017c istnieje\n  if (loaderId) {\n    const loaderElement = document.getElementById(loaderId);\n    if (loaderElement && loaderElement.parentNode) {\n      loaderElement.parentNode.removeChild(loaderElement);\n    }\n  }\n  \n  \/\/ u\u017cycie tylko znalezionych godzin\n  paramHours[model][param] = availableHours;\n  \n  return availableHours;\n}\n\n\/\/ Aktualnie wybrane opcje\nlet selectedModel = \"a-laef\";  \/\/ Domy\u015blny model\nlet selectedTerm = \"12h\";       \/\/ Domy\u015blny termin startu\nlet selectedParam = \"t2m_max\";     \/\/ Domy\u015blny parametr\nlet selectedHour = \"01\";        \/\/ Domy\u015blny termin prognozy\nlet selectedStat = null;     \/\/ Domy\u015blna statystyka\nlet selectedPressureLevel = null;\nlet selectedRegion = null; \/\/ null = ca\u0142a Polska, \"sudety\", \"tatry\", \"bieszczady\"\n\n\/\/ Zmienna do obs\u0142ugi animacji \u0142adowania\nlet loaderTimeout = null;\n\n\/\/ Funkcja pod\u015bwietlaj\u0105ca aktywny przycisk\nfunction setActiveButton(container, activeValue) {\n  if (activeValue === undefined || activeValue === null) return;\n  \n  \/\/ Konwertuj activeValue na string\n  let activeValueStr = String(activeValue).trim();\n  \n  \/\/ Usu\u0144 suffix \"h\" je\u015bli istnieje (dla przycisku godziny)\n  if (activeValueStr.endsWith('h')) {\n    activeValueStr = activeValueStr.substring(0, activeValueStr.length - 1);\n  }\n  \n  const buttons = document.querySelectorAll(`#${container} button`);\n  buttons.forEach(btn => {\n    let btnText = String(btn.textContent).trim();\n    \n    \/\/ Usu\u0144 suffix \"h\" je\u015bli istnieje (dla przycisku godziny)\n    if (btnText.endsWith('h')) {\n      btnText = btnText.substring(0, btnText.length - 1);\n    }\n    \n    \/\/ Formatujemy obie warto\u015bci z wiod\u0105cym zerem dla pewno\u015bci\n    const formattedActiveValue = String(activeValueStr).padStart(2, \"0\");\n    const formattedBtnValue = String(btnText).padStart(2, \"0\");\n    \n    \/\/ Por\u00f3wnujemy sformatowane warto\u015bci\n    if (formattedBtnValue === formattedActiveValue) {\n      btn.classList.add('active');\n    } else {\n      btn.classList.remove('active');\n    }\n  });\n}\n\n\/\/ 1. Renderowanie modeli\nrenderButtons(\"models\", models, (model) => {\n  const prevModel = selectedModel;\n  let prevTerm = selectedTerm;\n  let prevParam = isParameterWithPressureLevel(selectedParam) ? getBaseParam(selectedParam) : selectedParam;\n  let prevHour = selectedHour;\n  let prevStat = selectedStat;\n  let prevPressureLevel = selectedPressureLevel;\n  \n  selectedModel = model;\n  \n  setActiveButton(\"models\", model);\n  \n  \/\/ Sprawd\u017a czy zmieniamy typ modelu (podsumowanie \u2194 zwyk\u0142y)\n  const prevModelWasSummary = summaryModels.includes(prevModel);\n  const newModelIsSummary = summaryModels.includes(model);\n  const changingModelType = prevModelWasSummary !== newModelIsSummary;\n  \n  \/\/ Zachowanie terminu startu dla zwyk\u0142ych modeli\n  if (!summaryModels.includes(model)) {\n    if (prevTerm && termsDefault.includes(prevTerm)) {\n      selectedTerm = prevTerm;\n    } else {\n      selectedTerm = defaultSettings[model].term;\n    }\n  } else {\n    \/\/ Dla modeli podsumowania u\u017cywamy domy\u015blnego terminu (1h lub 12h)\n    selectedTerm = defaultSettings[model].term;\n  }\n  \n  \/\/ Sprawd\u017a czy poprzedni parametr jest dost\u0119pny w nowym modelu\n  \/\/ Pobierz list\u0119 parametr\u00f3w dla nowego modelu\n  let availableParams = [];\n  if (summaryModels.includes(model)) {\n    \/\/ Dla modeli typu podsumowanie pobieramy parametry zale\u017cnie od wariantu (1h lub 12h)\n    availableParams = paramsForModels[model][selectedTerm] || [];\n  } else {\n    \/\/ Dla pozosta\u0142ych modeli parametry s\u0105 takie same niezale\u017cnie od terminu\n    availableParams = paramsForModels[model] || [];\n  }\n  \n  \/\/ Filtruj parametry (usu\u0144 te z poziomem ci\u015bnienia)\n  let filteredParams = [];\n  let baseParamsAdded = new Set();\n  availableParams.forEach(param => {\n    if (\/^[A-Z]$\/.test(param)) {\n      if (!baseParamsAdded.has(param)) {\n        filteredParams.push(param);\n        baseParamsAdded.add(param);\n      }\n    } else if (isParameterWithPressureLevel(param)) {\n      const baseParam = getBaseParam(param);\n      if (!baseParamsAdded.has(baseParam)) {\n        filteredParams.push(baseParam);\n        baseParamsAdded.add(baseParam);\n      }\n    } else {\n      filteredParams.push(param);\n    }\n  });\n  \n  \/\/ Je\u015bli poprzedni parametr jest dost\u0119pny w nowym modelu, zachowaj go\n  if (prevParam && filteredParams.includes(prevParam)) {\n    selectedParam = prevParam;\n  } else {\n    selectedParam = defaultSettings[model].param;\n  }\n  \n  \/\/ Okre\u015bl dost\u0119pne godziny dla nowego modelu\n  let availableHoursTemp = [];\n  if (forecastHours[model] && forecastHours[model][selectedTerm]) {\n    availableHoursTemp = forecastHours[model][selectedTerm];\n  } else {\n    availableHoursTemp = forecastHours[\"default\"];\n  }\n  \n  \/\/ Zachowaj godzin\u0119 TYLKO je\u015bli pozostajemy w tym samym typie modelu\n  if (!changingModelType && prevHour) {\n    const prevHourInt = parseInt(String(prevHour), 10);\n    const isHourAvailable = availableHoursTemp.includes(prevHourInt);\n    \n    if (isHourAvailable) {\n      selectedHour = String(prevHour).padStart(2, \"0\");\n    } else {\n      selectedHour = String(defaultSettings[model].hour).padStart(2, \"0\");\n    }\n  } else {\n    \/\/ Zmiana typu modelu - u\u017cyj domy\u015blnej godziny\n    selectedHour = String(defaultSettings[model].hour).padStart(2, \"0\");\n  }\n  \n  \/\/ Zachowaj statystyk\u0119 dla zwyk\u0142ych modeli\n  if (!summaryModels.includes(model)) {\n    const availableStats = suffixConfig[selectedParam] || suffixConfig[\"default\"];\n    \n    if (prevStat && availableStats.includes(prevStat)) {\n      selectedStat = prevStat;\n    } else {\n      selectedStat = defaultSettings[model].stat;\n    }\n  } else {\n    selectedStat = null; \/\/ Modele podsumowania nie maj\u0105 statystyk\n  }\n  \n  document.getElementById(\"images\").innerHTML = \"\";\n  document.getElementById(\"hours\").innerHTML = \"\";\n  document.getElementById(\"statsSelect\").innerHTML = \"\";\n  document.getElementById(\"statsSection\").style.display = \"none\";\n  document.getElementById(\"pressureLevelSliderSection\").style.display = \"none\";\n  selectedPressureLevel = null; \/\/ Reset poziomu ci\u015bnienia przy zmianie modelu\n  \n  \/\/ Je\u015bli a-laef -> warianty, je\u015bli nie -> terminy\n  if (model === \"a-laef\") {\n    \/\/ Domy\u015blne warto\u015bci dla modelu a-laef z konfiguracji\n    selectedTerm = defaultSettings[\"a-laef\"].term;  \/\/ \"12h\"\n    \n    \/\/ Pobierz dost\u0119pne parametry dla tego wariantu\n    const availableParamsForAlaef = paramsForModels[\"a-laef\"][selectedTerm] || [];\n    \n    \/\/ Filtruj parametry\n    let filteredParamsForAlaef = [];\n    let baseParamsAdded = new Set();\n    availableParamsForAlaef.forEach(param => {\n      if (\/^[A-Z]$\/.test(param)) {\n        if (!baseParamsAdded.has(param)) {\n          filteredParamsForAlaef.push(param);\n          baseParamsAdded.add(param);\n        }\n      } else if (isParameterWithPressureLevel(param)) {\n        const baseParam = getBaseParam(param);\n        if (!baseParamsAdded.has(baseParam)) {\n          filteredParamsForAlaef.push(baseParam);\n          baseParamsAdded.add(baseParam);\n        }\n      } else {\n        filteredParamsForAlaef.push(param);\n      }\n    });\n    \n    \/\/ Je\u015bli poprzedni parametr jest dost\u0119pny w a-laef, zachowaj go\n    if (prevParam && filteredParamsForAlaef.includes(prevParam)) {\n      selectedParam = prevParam;\n    } else {\n      \/\/ W przeciwnym razie ustaw domy\u015blny parametr zale\u017cnie od wariantu\n      if (selectedTerm === \"1h\") {\n        selectedParam = \"t2m\";\n      } else {\n        selectedParam = defaultSettings[\"a-laef\"].param;\n      }\n    }\n    \n    \/\/ Okre\u015bl dost\u0119pne godziny dla a-laef z danym wariantem\n    let availableHoursAlaef = forecastHours[\"a-laef\"][selectedTerm] || [];\n    \n    \/\/ Spr\u00f3buj zachowa\u0107 poprzedni\u0105 godzin\u0119 je\u015bli jest dost\u0119pna\n    const prevHourInt = parseInt(String(prevHour), 10);\n    const isHourAvailableAlaef = availableHoursAlaef.includes(prevHourInt);\n    \n    if (prevHour && isHourAvailableAlaef) {\n      selectedHour = String(prevHour).padStart(2, \"0\");\n    } else {\n      selectedHour = String(defaultSettings[\"a-laef\"].hour).padStart(2, \"0\");\n    }\n    \n    renderButtons(\"terms\", alaefVariants, (variant) => {\n      selectedTerm = variant;\n      \n      \/\/ Resetuj region przy zmianie wariantu\n      selectedRegion = null;\n      document.getElementById(\"regionSection\").style.display = \"none\";\n      \n      \/\/ Pobierz dost\u0119pne parametry dla tego wariantu\n      const availableParamsForVariant = paramsForModels[\"a-laef\"][variant] || [];\n      \n      \/\/ Filtruj parametry (usu\u0144 te z poziomem ci\u015bnienia)\n      let filteredParamsForVariant = [];\n      let baseParamsAdded = new Set();\n      availableParamsForVariant.forEach(param => {\n        if (\/^[A-Z]$\/.test(param)) {\n          if (!baseParamsAdded.has(param)) {\n            filteredParamsForVariant.push(param);\n            baseParamsAdded.add(param);\n          }\n        } else if (isParameterWithPressureLevel(param)) {\n          const baseParam = getBaseParam(param);\n          if (!baseParamsAdded.has(baseParam)) {\n            filteredParamsForVariant.push(baseParam);\n            baseParamsAdded.add(baseParam);\n          }\n        } else {\n          filteredParamsForVariant.push(param);\n        }\n      });\n      \n      \/\/ Je\u015bli poprzedni parametr jest dost\u0119pny w tym wariancie, zachowaj go\n      if (prevParam && filteredParamsForVariant.includes(prevParam)) {\n        selectedParam = prevParam;\n      } else {\n        \/\/ W przeciwnym razie u\u017cyj domy\u015blnego parametru dla tego wariantu\n        if (variant === \"12h\") {\n          selectedParam = \"t2m_max\";\n        } else if (variant === \"1h\") {\n          selectedParam = \"t2m\";\n        }\n      }\n      \n      renderParams();\n      \n      \/\/ resetowanie interfejsu gdy trzeba\n      if (prevParam !== selectedParam || prevHour !== selectedHour) {\n        document.getElementById(\"hours\").innerHTML = \"\";\n        document.getElementById(\"images\").innerHTML = \"\";\n      }\n    }, selectedTerm);\n  } else {\n    renderButtons(\"terms\", termsDefault, (term) => {\n      selectedTerm = term;\n      \n      \/\/ Wyczy\u015b\u0107 paramHours dla modeli podsumowania przy zmianie zestawienia\n      if (summaryModels.includes(selectedModel) && paramHours[selectedModel]) {\n        paramHours[selectedModel] = {};\n      }\n      \n      \/\/ Resetuj region przy zmianie terminu\n      selectedRegion = null;\n      document.getElementById(\"regionSection\").style.display = \"none\";\n      \n      \/\/ Zachowanie poprzednich warto\u015bci je\u015bli to mo\u017cliwe\n      \/\/ NIE przywracaj selectedPressureLevel - to zosta\u0142o ju\u017c zresetowane przy zmianie modelu\n      if (prevTerm && prevTerm === term && prevParam) {\n        selectedParam = prevParam;\n        selectedHour = prevHour;\n        selectedStat = prevStat;\n        \/\/ selectedPressureLevel zosta\u0142 ju\u017c odpowiednio ustawiony lub zresetowany\n      }\n      \n      renderParams();\n\n      \/\/ resetowanie interfejsu gdy trzeba\n      if (prevParam !== selectedParam || prevHour !== selectedHour) {\n        document.getElementById(\"hours\").innerHTML = \"\";\n        document.getElementById(\"images\").innerHTML = \"\";\n      }\n    }, selectedTerm);\n  }\n  document.getElementById(\"paramsSelect\").innerHTML = \"\";\n}, selectedModel, modelDisplayNames);\n\n\/\/ Zdefiniowanie poziom\u00f3w ci\u015bnienia (od najni\u017cszego do najwy\u017cszego)\nconst pressureLevels = [\"1000\", \"950\", \"850\", \"700\", \"500\", \"300\"];\n\n\/\/ Funkcja pobieraj\u0105ca dost\u0119pne poziomy ci\u015bnienia dla parametru\nfunction getAvailablePressureLevels(param) {\n  if (!param || !selectedModel) return [];\n  \n  const params = paramsForModels[selectedModel] || [];\n  const levels = [];\n  \n  \/\/ Znajd\u017a wszystkie warianty tego parametru z _LVL_\n  params.forEach(p => {\n    if (p.startsWith(param + \"_LVL_\")) {\n      \/\/ Wyci\u0105gnij numer poziomu (np. z \"WWIND_LVL_950\" -> \"950\")\n      const level = p.split(\"_LVL_\")[1];\n      if (level && !levels.includes(level)) {\n        levels.push(level);\n      }\n    }\n  });\n  \n  \/\/ Sortuj od najwy\u017cszego (1000) do najni\u017cszego (100)\n  levels.sort((a, b) => parseInt(b) - parseInt(a));\n  \n  return levels;\n}\n\n\/\/ Sprawdzenie czy parametr ma warianty poziom\u00f3w ci\u015bnienia\nfunction hasPressureLevels(param) {\n  \/\/ Weryfikacja czy parametr ko\u0144czy si\u0119 liczb\u0105 (np. T0300, T0500, itp.) lub czy jest to pojedyncza litera\n  \/\/ (parametry z pojedyncz\u0105 liter\u0105 jak T, A, B, K, N itp. zawsze maj\u0105 poziomy ci\u015bnienia)\n  return \/[A-Z]0(300|500|700|850|950|1000)$\/.test(param) || \/^[A-Z]$\/.test(param);\n}\n\n\/\/ Sprawdzenie czy parametr jest z poziomem ci\u015bnienia\nfunction isParameterWithPressureLevel(param) {\n  \/\/ Sprawdza czy parametr ma poziom ci\u015bnienia (np. A0300, A0500, A1000, T1000)\n  return \/[A-Z]0(300|500|700|850|950)$\/.test(param) || \/[A-Z]1000$\/.test(param);\n}\n\n\/\/ Funkcja do ekstrakcji podstawowego parametru bez poziomu ci\u015bnienia\nfunction getBaseParam(param) {\n  \/\/ Zwraca cz\u0119\u015b\u0107 parametru przed cyframi poziomu\n  \/\/ Obs\u0142uguje zar\u00f3wno format z 0 (np. T0300) jak i bez 0 (np. T1000)\n  return param.replace(\/0(300|500|700|850|950)$\/, '').replace(\/1000$\/, '');\n}\n\n\/\/ 2. Renderowanie parametr\u00f3w\nfunction renderParams() {\n  if (!selectedModel || !selectedTerm) return;\n  \n  \/\/ Ukryj sekcj\u0119 statystyk i slider ci\u015bnienia dla modeli podsumowania\n  if (summaryModels.includes(selectedModel)) {\n    document.getElementById(\"statsSection\").style.display = \"none\";\n    document.getElementById(\"pressureLevelSliderSection\").style.display = \"none\";\n    selectedPressureLevel = null;\n  }\n  \n  let params = [];\n  \n  \/\/ Pobieranie odpowiednich parametr\u00f3w w zale\u017cno\u015bci od modelu i wariantu\n  if (summaryModels.includes(selectedModel)) {\n    \/\/ Dla modeli typu podsumowanie pobieramy parametry zale\u017cnie od wariantu (1h lub 12h)\n    params = paramsForModels[selectedModel][selectedTerm] || [];\n  } else {\n    \/\/ Dla pozosta\u0142ych modeli parametry s\u0105 takie same niezale\u017cnie od terminu\n    params = paramsForModels[selectedModel] || [];\n  }\n  \n  \/\/ Sprawd\u017a czy model w og\u00f3le ma parametry z poziomami ci\u015bnienia\n  const modelHasAnyPressureLevels = params.some(p => \n    \/^[A-Z]$\/.test(p) || \/[A-Z]0(300|500|700|850|950)$\/.test(p) || \/[A-Z]1000$\/.test(p)\n  );\n  \n  \/\/ Je\u015bli model nie ma \u017cadnych parametr\u00f3w z poziomami, ukryj slider i zresetuj\n  if (!modelHasAnyPressureLevels) {\n    document.getElementById(\"pressureLevelSliderSection\").style.display = \"none\";\n    selectedPressureLevel = null;\n  }\n  \n  \/\/ Zapami\u0119tanie aktualnie wybranego parametru\n  const currentParam = selectedParam;\n  \n  \/\/ Filtrowanie parametr\u00f3w, aby usun\u0105\u0107 te z poziomem ci\u015bnienia, ale zachowa\u0107 parametr podstawowy\n  let filteredParams = [];\n  let baseParamsAdded = new Set();\n  \n  params.forEach(param => {\n    \/\/ Dla jednoliterowych parametr\u00f3w (A, B, T, itp.) - zawsze traktujemy jako parametry z poziomami\n    if (\/^[A-Z]$\/.test(param)) {\n      if (!baseParamsAdded.has(param)) {\n        filteredParams.push(param);\n        baseParamsAdded.add(param);\n      }\n    }\n    \/\/ Je\u015bli to jest parametr z poziomem ci\u015bnienia (np. A0300, A1000)\n    else if (isParameterWithPressureLevel(param)) {\n      \/\/ Pobieranie podstawowego parametru\n      const baseParam = getBaseParam(param);\n      \/\/ dodawanie\n      if (!baseParamsAdded.has(baseParam)) {\n        filteredParams.push(baseParam);\n        baseParamsAdded.add(baseParam);\n      }\n    } else {\n      \/\/ Dla parametr\u00f3w bez poziomu ci\u015bnienia, dodajemy je normalnie\n      filteredParams.push(param);\n    }\n  });\n  \n  renderSelect(\"paramsSelect\", filteredParams, (param) => {\n    \/\/ Resetowanie wszystkich flag wykonywania przy zmianie parametru\n    isShowImagesRunning = false;\n    isShowImagesRunningTimestamp = 0;\n    isRenderingForecastHours = false;\n    isRenderingForecastHoursTimestamp = 0;\n    isPreloadingImages = false;\n    isPreloadingImagesTimestamp = 0;\n    isLoadingImages = false;\n    \n    \/\/ Zapami\u0119tanie poprzednich warto\u015bci terminu, statystyki i poziomu ci\u015bnienia\n    let prevHour = selectedHour;\n    let prevStat = selectedStat;\n    let prevPressureLevel = selectedPressureLevel;\n    \n    selectedParam = param;\n    \n    \/\/ Natychmiast zaktualizuj widoczno\u015b\u0107 region\u00f3w dla nowego parametru\n    if (summaryModels.includes(selectedModel)) {\n      renderRegions();\n    }\n    \n    \/\/ Nie resetujemy wcze\u015bniej wybranego terminu prognozy i statystyki\n    \/\/ je\u015bli zmieniamy tylko parametr\n    \n    document.getElementById(\"images\").innerHTML = \"\";\n    document.getElementById(\"hours\").innerHTML = \"\";\n    \n    \/\/ Weryfikacja czy wybrany parametr ma poziomy ci\u015bnienia\n    \/\/ Dla jednoliterowych parametr\u00f3w (A, B, T, itp.) zawsze true\n    const paramHasLevels = \/^[A-Z]$\/.test(param) ? true : params.some(p => \n      p.startsWith(param + \"0\") || p === param + \"1000\");\n    \n    \/\/ Pokazanie lub ukrycie suwaka poziom\u00f3w ci\u015bnienia - tylko dla zwyk\u0142ych modeli (nie podsumowania)\n    const shouldShowPressureSlider = !summaryModels.includes(selectedModel) && paramHasLevels;\n    document.getElementById(\"pressureLevelSliderSection\").style.display = shouldShowPressureSlider ? \"block\" : \"none\";\n    \n    \/\/ Je\u015bli parametr ma poziomy ci\u015bnienia i to NIE jest model podsumowania, ustaw domy\u015blny poziom na 1000 hPa lub zachowaj poprzedni\n    if (shouldShowPressureSlider) {\n      \/\/ Je\u015bli by\u0142 wcze\u015bniej wybrany poziom ci\u015bnienia i przechodzimy mi\u0119dzy parametrami z poziomami, zachowaj\n      if (prevPressureLevel && pressureLevels.includes(prevPressureLevel)) {\n        const levelIndex = pressureLevels.indexOf(prevPressureLevel);\n        if (levelIndex !== -1) {\n          document.getElementById(\"pressureLevelSlider\").value = levelIndex;\n          document.getElementById(\"pressureValue\").textContent = prevPressureLevel + \" hPa\";\n          selectedPressureLevel = prevPressureLevel;\n        } else {\n          document.getElementById(\"pressureLevelSlider\").value = 0; \n          document.getElementById(\"pressureValue\").textContent = \"1000 hPa\";\n          selectedPressureLevel = \"1000\";\n        }\n      } else {\n        document.getElementById(\"pressureLevelSlider\").value = 0; \n        document.getElementById(\"pressureValue\").textContent = \"1000 hPa\";\n        selectedPressureLevel = \"1000\";\n      }\n      \/\/ Renderuj przyciski dla urz\u0105dze\u0144 mobilnych\n      renderPressureLevelButtons();\n    } else {\n      selectedPressureLevel = null; \/\/ Reset gdy suwak jest ukryty\n    }\n    \n    \/\/ Ukrywamy sekcj\u0119 statystyk dla wszystkich modeli typu \"podsumowanie\"\n    if (summaryModels.includes(selectedModel)) {\n      document.getElementById(\"statsSection\").style.display = \"none\";\n      \n      \/\/ Sprawd\u017a dost\u0119pne godziny dla nowo wybranego parametru (dla modeli z r\u00f3\u017cnymi zakresami godzin)\n      if ((selectedModel === \"cosmo_ens\" || selectedModel === \"icon_ens\" || selectedModel === \"ecmwf_ifs_ens\" || selectedModel === \"ecmwf_aifs_ens\" || selectedModel === \"a-laef\") && \n          paramHours[selectedModel] && \n          paramHours[selectedModel][selectedParam]) {\n          \n        \/\/ Aktualizuj godziny dla wszystkich parametr\u00f3w modeli cosmo_ens, icon_ens i a-laef\n        if (selectedModel === \"cosmo_ens\" || selectedModel === \"icon_ens\" || selectedModel === \"a-laef\") {\n          \/\/ Animacja \u0142adowania jest dodawana wewn\u0105trz funkcji determineAvailableHours\n          \/\/ Aktualizujemy godziny dla wybranego parametru\n          determineAvailableHours(selectedModel, selectedTerm, selectedParam)\n            .then(() => {\n              \/\/ Po zako\u0144czeniu aktualizacji, kontynuujemy z aktualnymi godzinami\n              const availableHours = paramHours[selectedModel][selectedParam];\n              \n              if (availableHours && availableHours.length > 0) {\n                \/\/ Sprawdzanie, czy aktualnie wybrana godzina jest dost\u0119pna dla tego parametru\n                const formattedSelectedHour = selectedHour.padStart(2, \"0\");\n                if (!availableHours.includes(formattedSelectedHour)) {\n                  \/\/ Je\u015bli nie, wybierz pierwsz\u0105 dost\u0119pn\u0105 godzin\u0119\n                  selectedHour = availableHours[0]; \/\/ Warto\u015bci s\u0105 ju\u017c sformatowane\n                }\n                \n                \/\/ WA\u017bNE: Czyszczenie obraz\u00f3w przed renderowaniem nowych godzin\n                document.getElementById(\"images\").innerHTML = \"\";\n                \n                \/\/ Renderowanie przycisk\u00f3w godzin\n                renderForecastHours();\n              } else {\n                console.error(`Brak dost\u0119pnych godzin dla parametru ${selectedParam} w modelu ${selectedModel}`);\n                document.getElementById(\"hours\").innerHTML = '<div class=\"loading-hours\">Brak dost\u0119pnych godzin<\/div>';\n              }\n            });\n        } else {\n          \/\/ zdefiniowane wcze\u015bniej godziny\n          const availableHours = paramHours[selectedModel][selectedParam];\n          \n          \/\/ Sprawdzanie czy s\u0105 dost\u0119pne godziny dla tego parametru\n          if (!availableHours || availableHours.length === 0) {\n            console.error(`Brak dost\u0119pnych godzin dla parametru ${selectedParam} w modelu ${selectedModel}`);\n            document.getElementById(\"hours\").innerHTML = '<div class=\"loading-hours\">Brak dost\u0119pnych godzin<\/div>';\n            return;\n          }\n          \n          \/\/ Sprawd\u017a, czy aktualnie wybrana godzina jest dost\u0119pna dla tego parametru\n          const formattedSelectedHour = selectedHour.padStart(2, \"0\");\n          if (!availableHours.includes(formattedSelectedHour)) {\n            \/\/ Je\u015bli nie, wybierz pierwsz\u0105 dost\u0119pn\u0105 godzin\u0119\n            selectedHour = availableHours[0]; \/\/ Warto\u015bci s\u0105 ju\u017c sformatowane\n          }\n          \n          \/\/ Renderowanie przycisk\u00f3w godzin\n          renderForecastHours();\n        }\n      }\n      \n      \/\/ Dla modeli typu \"podsumowanie\" sprawdzamy dost\u0119pne godziny przed renderowaniem\n      if (summaryModels.includes(selectedModel)) {\n        \/\/ Animacja \u0142adowania jest dodawana wewn\u0105trz funkcji determineAvailableHours\n        determineAvailableHours(selectedModel, selectedTerm, selectedParam)\n          .then(() => {\n            \/\/ Renderuj przyciski region\u00f3w (je\u015bli parametr ma regiony)\n            renderRegions();\n            renderForecastHours();\n            \/\/ Wst\u0119pne wczytanie obrazk\u00f3w po zmianie parametru\n            preloadForecastImages();\n          });\n      } else {\n        \/\/ Dla pozosta\u0142ych modeli od razu pokazujemy wyb\u00f3r terminu prognozy\n        renderForecastHours();\n        \/\/ Wst\u0119pne wczytanie obrazk\u00f3w\n        preloadForecastImages();\n      }\n    } else {\n      \/\/ Ukryj sekcj\u0119 region\u00f3w dla standardowych modeli\n      document.getElementById(\"regionSection\").style.display = \"none\";\n      selectedRegion = null;\n      \n      \/\/ Dla standardowych modeli najpierw pokazujemy wyb\u00f3r statystyki\n      getElementCached(\"statsSection\").style.display = \"block\";\n      \n      \/\/ Sprawdzanie czy poprzednia statystyka jest dost\u0119pna dla nowego parametru\n      \/\/ Je\u015bli nie, ustawia domy\u015blnie na 'mean'\n      const availableStats = suffixConfig[param] || suffixConfig[\"default\"];\n      \n      \/\/ Zawsze prze\u0142\u0105czamy na statystyk\u0119 \"mean\" przy zmianie parametru\n      selectedStat = \"mean\";\n      \n      \/\/ Zachowaj selectedHour przed wywo\u0142aniem renderStatistics()\n      const preservedHour = selectedHour;\n      renderStatistics();\n      \n      \/\/ Przywr\u00f3\u0107 zachowan\u0105 godzin\u0119 je\u015bli zosta\u0142a zmieniona\n      if (preservedHour && selectedHour !== preservedHour) {\n        selectedHour = preservedHour;\n      }\n    }\n  }, parameterDisplayNames);\n  \n  \/\/ Po zako\u0144czeniu renderowania parametr\u00f3w, ustaw warto\u015b\u0107 select'a\n  if (currentParam && filteredParams.includes(currentParam)) {\n    const paramsSelect = document.getElementById(\"paramsSelect\");\n    paramsSelect.value = currentParam;\n  } else if (selectedParam && filteredParams.includes(selectedParam)) {\n    const paramsSelect = document.getElementById(\"paramsSelect\");\n    paramsSelect.value = selectedParam;\n  } else {\n    \/\/ Je\u015bli nie mamy wybranego parametru, wybierz domy\u015blny dla danego modelu\n    if (defaultSettings[selectedModel] && defaultSettings[selectedModel].param && filteredParams.includes(defaultSettings[selectedModel].param)) {\n      const paramsSelect = document.getElementById(\"paramsSelect\");\n      paramsSelect.value = defaultSettings[selectedModel].param;\n      selectedParam = defaultSettings[selectedModel].param;\n    }\n  }\n  \n  \/\/ Dla zwyk\u0142ych modeli (nie-podsumowania) wywo\u0142aj renderStatistics()\n  if (!summaryModels.includes(selectedModel)) {\n    renderStatistics();\n  }\n}\n\n\/\/ 3. Renderowanie statystyk (tylko dla standardowych modeli)\nfunction renderStatistics() {\n  \/\/ Modele podsumowania nie maj\u0105 statystyk\n  if (summaryModels.includes(selectedModel) || !selectedParam) return;\n  \n  \/\/ Pobieranie dost\u0119pnych statystyk dla wybranego parametru\n  const stats = suffixConfig[selectedParam] || suffixConfig[\"default\"];\n  \n  \/\/ Je\u015bli jest tylko jedna statystyka (mean) - wybierz j\u0105 automatycznie\n  if (stats.length === 1 && stats[0] === \"mean\") {\n    selectedStat = \"mean\";\n    \/\/ Ukrycie sekcji wyboru statystyk, gdy\u017c nie ma co wybiera\u0107\n    document.getElementById(\"statsSection\").style.display = \"none\";\n    \/\/ Bezpo\u015brednie przej\u015bcie do wyboru terminu prognozy\n    renderForecastHours();\n    \/\/ Wst\u0119pne wczytanie obrazk\u00f3w po automatycznym wyborze statystyki\n    preloadForecastImages();\n    return;\n  }\n  \n  \/\/ Je\u015bli jest wi\u0119cej statystyk, poka\u017c list\u0119 wyboru\n  document.getElementById(\"statsSection\").style.display = \"block\";\n  \n  \/\/ Zapami\u0119taj aktualnie wybran\u0105 statystyk\u0119\n  const currentStat = selectedStat;\n  \n  renderSelect(\"statsSelect\", stats, (stat) => {\n    \/\/ Resetowanie wszystkich flag wykonywania przy zmianie statystyki\n    isShowImagesRunning = false;\n    isShowImagesRunningTimestamp = 0;\n    isRenderingForecastHours = false;\n    isRenderingForecastHoursTimestamp = 0;\n    isPreloadingImages = false;\n    isPreloadingImagesTimestamp = 0;\n    isLoadingImages = false;\n    \n    selectedStat = stat;\n    \/\/ Po wyborze statystyki przechodzimy do wyboru terminu prognozy\n    renderForecastHours();\n    \/\/ Preload obrazk\u00f3w po zmianie statystyki\n    preloadForecastImages();\n    \/\/ Obrazek zostanie wy\u015bwietlony po renderForecastHours(), wi\u0119c nie ma potrzeby wywo\u0142ywa\u0107 showImages() tutaj\n  }, statisticsDisplayNames);\n  \n  \/\/ Je\u015bli mamy wybran\u0105 statystyk\u0119 i jest ona dost\u0119pna, ustaw j\u0105 w interfejsie\n  if (currentStat && stats.includes(currentStat)) {\n    const statsSelect = document.getElementById(\"statsSelect\");\n    statsSelect.value = currentStat;\n    \/\/ Renderuj interfejs godzin bez symulowania zdarzenia change\n    renderForecastHours();\n    \/\/ Wst\u0119pne wczytanie obrazk\u00f3w\n    preloadForecastImages();\n  } else if (selectedStat && stats.includes(selectedStat)) {\n    \/\/ Je\u015bli mamy domy\u015bln\u0105 statystyk\u0119 i jest dost\u0119pna\n    const statsSelect = document.getElementById(\"statsSelect\");\n    statsSelect.value = selectedStat;\n    \/\/ Renderuj interfejs godzin bez symulowania zdarzenia change\n    renderForecastHours();\n    \/\/ Wst\u0119pne wczytanie obrazk\u00f3w\n    preloadForecastImages();\n  }\n}\n\n\/\/ 3b. Sprawdzanie czy parametr ma warianty regionalne\nfunction hasRegions(model, term, param) {\n  return regionsConfig[model] && \n         regionsConfig[model][term] && \n         regionsConfig[model][term].includes(param);\n}\n\n\/\/ 3c. Renderowanie przycisk\u00f3w region\u00f3w (dla modeli z podsumowania)\nfunction renderRegions() {\n  const regionSection = document.getElementById(\"regionSection\");\n  const regionsContainer = document.getElementById(\"regions\");\n  \n  \/\/ Wyczy\u015b\u0107 zawarto\u015b\u0107\n  regionsContainer.innerHTML = \"\";\n  selectedRegion = null;\n  \n  \/\/ Sprawd\u017a czy pokaza\u0107 regiony\n  const shouldShow = hasRegions(selectedModel, selectedTerm, selectedParam);\n  \n  \n  if (!shouldShow) {\n    \/\/ Ukryj sekcj\u0119\n    if (regionSection) {\n      regionSection.style.display = \"none\";\n    }\n    return;\n  }\n  \n  \/\/ Poka\u017c sekcj\u0119\n  if (regionSection) {\n    regionSection.style.display = \"block\";\n  }\n  \n  \/\/ Dodaj przyciski dla ka\u017cdego regionu\n  regions.forEach(region => {\n    const btn = document.createElement(\"button\");\n    btn.textContent = region.name;\n    btn.dataset.regionId = region.id || \"\";\n    \n    \/\/ Domy\u015blnie wybierz Polsk\u0119\n    if (selectedRegion === null && region.id === null) {\n      btn.classList.add(\"active\");\n      selectedRegion = null;\n    } else if (selectedRegion === region.id) {\n      btn.classList.add(\"active\");\n    }\n    \n    btn.addEventListener(\"click\", () => {\n      selectedRegion = region.id;\n      \n      \/\/ Aktualizuj aktywny przycisk\n      regionsContainer.querySelectorAll(\"button\").forEach(b => b.classList.remove(\"active\"));\n      btn.classList.add(\"active\");\n      \n      \/\/ Prze\u0142aduj obrazki\n      preloadForecastImages();\n      showImages();\n    });\n    \n    regionsContainer.appendChild(btn);\n  });\n  \n  \/\/ Je\u015bli selectedRegion nie jest ustawiony, ustaw na null (Polska)\n  if (selectedRegion === undefined) {\n    selectedRegion = null;\n  }\n\n}\n\n\/\/ 4. Renderowanie termin\u00f3w prognozy\n\/\/ Zmienna do \u015bledzenia, czy funkcja renderForecastHours jest aktualnie wykonywana\nlet isRenderingForecastHours = false;\nlet isRenderingForecastHoursTimestamp = 0;\nlet latestRenderRequest = null;\nlet pendingRenderRequest = false;\n\nfunction renderForecastHours() {\n  if (!selectedModel || !selectedTerm || !selectedParam) {\n    return;\n  }\n  \n  \/\/ Dla modeli innych ni\u017c \"podsumowanie\", potrzebujemy statystyki\n  if (!summaryModels.includes(selectedModel) && !selectedStat) {\n    return;\n  }\n  \n  \/\/ Zapami\u0119taj aktualne \u017c\u0105danie\n  latestRenderRequest = {\n    model: selectedModel,\n    term: selectedTerm,\n    param: selectedParam,\n    stat: selectedStat\n  };\n  \n  \/\/ Je\u015bli funkcja ju\u017c jest wykonywana, zaznacz \u017ce jest nowe \u017c\u0105danie\n  if (isRenderingForecastHours) {\n    pendingRenderRequest = true;\n    return;\n  }\n  \n  \/\/ Ustaw flag\u0119 wykonywania\n  isRenderingForecastHours = true;\n  isRenderingForecastHoursTimestamp = Date.now();\n  \n  \/\/ Dodaj wska\u017anik \u0142adowania je\u015bli nie mamy wcze\u015bniej preloadowanych danych\n  if (!preloadedModels[selectedModel]) {\n    const hoursContainer = document.getElementById(\"hours\");\n    if (hoursContainer) {\n      hoursContainer.innerHTML = '<div class=\"loading-hours\">Wczytywanie danych modelu...<\/div>';\n    }\n    \n    const imagesContainer = document.getElementById(\"images\");\n    if (imagesContainer) {\n      imagesContainer.innerHTML = \"\";\n      const loaderContainer = document.createElement(\"div\");\n      loaderContainer.className = \"loader-container\";\n      const loader = document.createElement(\"div\");\n      loader.className = \"loader\";\n      loaderContainer.appendChild(loader);\n      loaderContainer.appendChild(document.createElement(\"p\")).textContent = \"\u0141adowanie prognozy...\";\n      imagesContainer.appendChild(loaderContainer);\n    }\n  }\n  \n  \/\/ Aktualizacja tytu\u0142u sekcji godzin w zale\u017cno\u015bci od typu modelu\n  const hoursTitle = document.getElementById(\"hoursTitle\");\n  if (hoursTitle) {\n    if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n      hoursTitle.textContent = \"Kolejne prognozy:\";\n    } else {\n      hoursTitle.textContent = \"Godziny prognozy:\";\n    }\n  }\n  \n  let hours = [];\n  \n  \/\/ Specjalna obs\u0142uga dla modeli, kt\u00f3re maj\u0105 r\u00f3\u017cne godziny dla r\u00f3\u017cnych parametr\u00f3w\n  if ((selectedModel === \"cosmo_ens\" || selectedModel === \"icon_ens\" || selectedModel === \"ecmwf_ifs_ens\" || selectedModel === \"ecmwf_aifs_ens\" || selectedModel === \"a-laef\") && \n      paramHours[selectedModel] && \n      paramHours[selectedModel][selectedParam] &&\n      paramHours[selectedModel][selectedParam].length > 0) {\n    \/\/ U\u017cywamy tylko godzin dost\u0119pnych dla wybranego parametru w tym modelu\n    hours = paramHours[selectedModel][selectedParam];\n  } \n  \/\/ Wy\u015bwietl komunikat, je\u015bli nie ma dost\u0119pnych godzin dla wybranego parametru\n  else if ((selectedModel === \"cosmo_ens\" || selectedModel === \"icon_ens\" || selectedModel === \"ecmwf_ifs_ens\" || selectedModel === \"ecmwf_aifs_ens\") && \n      (!paramHours[selectedModel] || \n       !paramHours[selectedModel][selectedParam] || \n       paramHours[selectedModel][selectedParam].length === 0)) {\n    \n    \/\/ Sprawd\u017a czy sprawdzanie ju\u017c trwa\n    const checkKey = `${selectedModel}_${selectedTerm}_${selectedParam}`;\n    if (!pendingHourChecks[checkKey]) {\n      \/\/ Tylko je\u015bli sprawdzanie NIE trwa, wy\u015bwietl komunikat i dodaj animacj\u0119\n      document.getElementById(\"hours\").innerHTML = '<div class=\"loading-hours\">Sprawdzanie dost\u0119pnych godzin...<\/div>';\n      \n      \/\/ NIE dodajemy tutaj animacji \u0142adowania - performAvailableHoursCheck() to zrobi\n    }\n    \n    \/\/ Pr\u00f3ba sprawdzenia dost\u0119pnych godzin\n    determineAvailableHours(selectedModel, selectedTerm, selectedParam)\n      .then((availableHours) => {\n        \/\/ Zresetuj flag\u0119 renderowania przed dalszym przetwarzaniem\n        isRenderingForecastHours = false;\n        isRenderingForecastHoursTimestamp = 0;\n        \n        if (!availableHours || availableHours.length === 0) {\n          document.getElementById(\"hours\").innerHTML = '<div class=\"loading-hours\">Brak dost\u0119pnych godzin dla tego parametru<\/div>';\n          \n          \/\/ Pokazujemy obrazek zast\u0119pczy zamiast animacji \u0142adowania\n          const imagesContainer = document.getElementById(\"images\");\n          if (imagesContainer) {\n            \/\/ Wyczy\u015b\u0107 kontener (usu\u0144 animacj\u0119)\n            imagesContainer.innerHTML = \"\";\n            \n            \/\/ Dodaj obrazek zast\u0119pczy\n            const errorImg = document.createElement(\"img\");\n            errorImg.src = \"\/wp-content\/uploads\/production\/prognozy_wiazkowe\/configuration_not.png\";\n            errorImg.alt = \"Konfiguracja niedost\u0119pna\";\n            errorImg.style.maxWidth = \"100%\";\n            errorImg.style.display = \"block\";\n            imagesContainer.appendChild(errorImg);\n            \n            \/\/ Dodaj komunikat\n            const errorMsg = document.createElement(\"p\");\n            errorMsg.textContent = \"Brak dost\u0119pnych prognoz dla wybranej konfiguracji.\";\n            errorMsg.style.color = \"red\";\n            errorMsg.style.textAlign = \"center\";\n            imagesContainer.appendChild(errorMsg);\n          }\n        } else {\n          \/\/ Od\u015bwie\u017c interfejs z nowymi godzinami\n          renderForecastHours();\n        }\n      });\n    \n    return; \/\/ Przerywamy wykonywanie funkcji\n  }\n  \/\/ Je\u015bli nie ma specjalnych godzin lub to inny model, u\u017cywamy standardowych godzin\n  else if (forecastHours[selectedModel] && forecastHours[selectedModel][selectedTerm]) {\n    hours = forecastHours[selectedModel][selectedTerm];\n  } else {\n    hours = forecastHours[\"default\"];\n  }\n  \n  \/\/ Zachowujemy aktualnie wybrany termin\n  const currentHour = selectedHour;\n  \n  renderButtons(\"hours\", hours.map(h => {\n    \/\/ Zawsze u\u017cywamy sformatowanych warto\u015bci z wiod\u0105cym zerem\n    return String(h).padStart(2, \"0\");\n  }), (hour) => {\n    \/\/ Ustaw wybran\u0105 godzin\u0119\n    selectedHour = hour;\n    \n    \/\/ Aktualizacja poziomego slidera\n    \/\/ Najpierw konwertujemy wszystkie godziny do tego samego formatu (string z wiod\u0105cym zerem)\n    const formattedSelectedHour = String(selectedHour).padStart(2, \"0\");\n    const formattedHours = hours.map(h => String(h).padStart(2, \"0\"));\n    \n    \/\/ Teraz szukamy indeksu po sformatowanych godzinach\n    const hourIndex = formattedHours.indexOf(formattedSelectedHour);\n    \n    if (hourIndex !== -1) {\n      document.getElementById(\"forecastHourSlider\").value = hourIndex;\n      \/\/ Aktualizacja wy\u015bwietlanej warto\u015bci\n      if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym\n        document.getElementById(\"hourValue\").textContent = forecastHoursDisplayNames[hour] || hour;\n      } else {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym z sufiksem \"h\"\n        const displayHour = forecastHoursDisplayNames[hour] || hour;\n        document.getElementById(\"hourValue\").textContent = displayHour + \"h\";\n      }\n    }\n    \n    showImages();\n    \n    \/\/ WA\u017bNE: Po zmianie godziny przez klikni\u0119cie przycisku, zaktualizuj preloading\n    setTimeout(() => {\n      preloadForecastImages();\n    }, 10);\n  }, currentHour, forecastHoursDisplayNames);\n  \n  \/\/ Aktualizuj wizualizacj\u0119 za\u0142adowanych godzin\n  updateLoadedHoursVisualization();\n  \n  \/\/ Preload wszystkich obrazk\u00f3w dla wybranego parametru i terminu\n  \/\/ U\u017cywamy setTimeout aby preloadForecastImages wykona\u0142o si\u0119 po zako\u0144czeniu renderowania\n  setTimeout(() => {\n    preloadForecastImages();\n  }, 10); \/\/ Bardzo szybkie wywo\u0142anie aby obrazy by\u0142y pre\u0142adowane natychmiast\n  \n  \/\/ Inicjalizacja poziomego slidera termin\u00f3w prognozy\n  setupHorizontalSlider(hours);\n  \n  \/\/ Resetuj flag\u0119 wykonywania\n  setTimeout(() => {\n    isRenderingForecastHours = false;\n    isRenderingForecastHoursTimestamp = 0;\n    \n    \/\/ Sprawd\u017a, czy pojawi\u0142o si\u0119 nowe \u017c\u0105danie podczas wykonywania tej funkcji\n    if (pendingRenderRequest) {\n      pendingRenderRequest = false;\n      \n      \/\/ Natychmiast renderuj najnowsze \u017c\u0105danie\n      setTimeout(() => {\n        renderForecastHours();\n      }, 10); \/\/ Bardzo kr\u00f3tki timeout\n    }\n  }, 10); \/\/ Bardzo kr\u00f3tki timeout, aby szybko reagowa\u0107 na zmiany\n  \n  \/\/ Je\u015bli mamy wybrany termin prognozy, pod\u015bwietl odpowiedni przycisk\n  \/\/ hours mo\u017ce zawiera\u0107 liczby [0, 3, 6, ...] lub stringi [\"01\", \"02\", \"03\", ...]\n  \/\/ Sprawdzamy oba typy dla kompatybilno\u015bci\n  const currentHourNum = parseInt(currentHour, 10);\n  const currentHourStr = String(currentHour).padStart(2, \"0\");\n  \n  \/\/ Sprawd\u017a czy hours zawiera liczby czy stringi\n  const hoursContainsNumbers = hours.length > 0 && typeof hours[0] === 'number';\n  const hoursContainsStrings = hours.length > 0 && typeof hours[0] === 'string';\n  \n  const isCurrentHourAvailable = hoursContainsNumbers \n    ? hours.includes(currentHourNum) \n    : hours.includes(currentHourStr);\n  \n  if (currentHour && isCurrentHourAvailable) {\n    setActiveButton(\"hours\", currentHour);\n    selectedHour = currentHour;\n    \n    \/\/ Aktualizuj slider\n    const hourIndex = hoursContainsNumbers ? hours.indexOf(currentHourNum) : hours.indexOf(currentHourStr);\n    if (hourIndex !== -1) {\n      document.getElementById(\"forecastHourSlider\").value = hourIndex;\n      \/\/ Aktualizacja wy\u015bwietlanej warto\u015bci\n      if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym\n        document.getElementById(\"hourValue\").textContent = forecastHoursDisplayNames[currentHour] || currentHour;\n      } else {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym z sufiksem \"h\"\n        const displayHour = forecastHoursDisplayNames[currentHour] || currentHour;\n        document.getElementById(\"hourValue\").textContent = displayHour + \"h\";\n      }\n    }\n    \n    \/\/ Wy\u015bwietl obrazek\n    showImages();\n  } else {\n    \/\/ Fallback: sprawd\u017a selectedHour\n    const selectedHourNum = parseInt(selectedHour, 10);\n    const selectedHourStr = String(selectedHour).padStart(2, \"0\");\n    const isSelectedHourAvailable = hoursContainsNumbers \n      ? hours.includes(selectedHourNum) \n      : hours.includes(selectedHourStr);\n    \n    if (selectedHour && isSelectedHourAvailable) {\n      setActiveButton(\"hours\", selectedHour);\n      \n      \/\/ Aktualizuj slider\n      const hourIndex = hoursContainsNumbers ? hours.indexOf(selectedHourNum) : hours.indexOf(selectedHourStr);\n      if (hourIndex !== -1) {\n        document.getElementById(\"forecastHourSlider\").value = hourIndex;\n        \n        \/\/ Aktualizacja wy\u015bwietlanej warto\u015bci\n        if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n          \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym\n          document.getElementById(\"hourValue\").textContent = forecastHoursDisplayNames[selectedHour] || selectedHour;\n        } else {\n          \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym z sufiksem \"h\"\n          const displayHour = forecastHoursDisplayNames[selectedHour] || selectedHour;\n          document.getElementById(\"hourValue\").textContent = displayHour + \"h\";\n        }\n      }\n      \n      \/\/ Wy\u015bwietl obrazek\n      showImages();\n    } else {\n      \/\/ Je\u015bli nie mamy wybranego terminu prognozy, wybierz domy\u015blny dla danego modelu\n      let defaultHour = defaultSettings[selectedModel].hour;\n      \/\/ Zapewniamy, \u017ce domy\u015blna godzina jest w odpowiednim formacie\n      defaultHour = String(defaultHour).padStart(2, \"0\");\n      \n      \/\/ Sprawd\u017a czy domy\u015blna godzina jest dost\u0119pna w aktualnych godzinach\n      const hourExists = hours.some(h => String(h).padStart(2, \"0\") === defaultHour);\n      if (hourExists) {\n        selectedHour = defaultHour;\n      } else {\n        \/\/ Je\u015bli nie, wybierz pierwsz\u0105 dost\u0119pn\u0105\n        selectedHour = String(hours[0]).padStart(2, \"0\");\n      }\n      \n      \/\/ Ustawiamy aktywny przycisk dla sformatowanej godziny\n      setActiveButton(\"hours\", selectedHour);\n      \n      const hourIndex = hours.findIndex(h => h === selectedHour.padStart(2, \"0\"));\n      document.getElementById(\"forecastHourSlider\").value = hourIndex !== -1 ? hourIndex : 0;\n      \n      \/\/ U\u017cyjemy globalnej listy modeli typu \"podsumowanie\"\n      \n      \/\/ Aktualizacja wy\u015bwietlanej warto\u015bci\n      if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n        document.getElementById(\"hourValue\").textContent = selectedHour;\n      } else {\n        document.getElementById(\"hourValue\").textContent = selectedHour + \"h\";\n      }\n      \n      \/\/ Wy\u015bwietl obrazek\n      showImages();\n    }\n  }\n}\n\n\/\/ Funkcja renderuj\u0105ca przyciski poziom\u00f3w ci\u015bnienia dla urz\u0105dze\u0144 mobilnych\nfunction renderPressureLevelButtons() {\n  const container = document.getElementById(\"pressureLevelButtons\");\n  if (!container) return;\n  \n  container.innerHTML = \"\";\n  \n  \/\/ Zachowaj poprzedni poziom je\u015bli jest dost\u0119pny, w przeciwnym razie ustaw pierwszy\n  if (!selectedPressureLevel || !pressureLevels.includes(selectedPressureLevel)) {\n    selectedPressureLevel = pressureLevels[0];\n  }\n  \n  pressureLevels.forEach(level => {\n    const btn = document.createElement(\"button\");\n    btn.dataset.value = level;\n    btn.textContent = `${level} hPa`;\n    \n    \/\/ Zaznacz aktywny przycisk\n    if (level === selectedPressureLevel) {\n      btn.classList.add(\"active\");\n    }\n    \n    \/\/ Obs\u0142uga klikni\u0119cia\n    btn.onclick = function() {\n      selectedPressureLevel = level;\n      \n      \/\/ Zaktualizuj wy\u015bwietlan\u0105 warto\u015b\u0107\n      document.getElementById(\"pressureValue\").textContent = `${level} hPa`;\n      \n      \/\/ Zaktualizuj slider (dla sp\u00f3jno\u015bci)\n      const levelIndex = pressureLevels.indexOf(level);\n      if (levelIndex !== -1) {\n        document.getElementById(\"pressureLevelSlider\").value = levelIndex;\n      }\n      \n      \/\/ Zaznacz aktywny przycisk\n      container.querySelectorAll(\"button\").forEach(b => b.classList.remove(\"active\"));\n      this.classList.add(\"active\");\n      \n      \/\/ Za\u0142aduj obrazek\n      showImages();\n      preloadForecastImages();\n    };\n    \n    container.appendChild(btn);\n  });\n}\n\n\/\/ Obs\u0142uga zdarzenia zmiany poziomu ci\u015bnienia\ndocument.getElementById(\"pressureLevelSlider\").addEventListener(\"input\", function() {\n  \/\/ Pobranie warto\u015bci z suwaka (0-5) i mapowanie jej na odpowiedni poziom ci\u015bnienia\n  const sliderValue = parseInt(this.value);\n  \/\/ Teraz kolejno\u015b\u0107 jest naturalna: 0=1000, 1=950, 2=850, 3=700, 4=500, 5=300\n  selectedPressureLevel = pressureLevels[sliderValue];\n  \n  \/\/ Aktualizacja wy\u015bwietlanej warto\u015bci\n  document.getElementById(\"pressureValue\").textContent = `${selectedPressureLevel} hPa`;\n  \n  \/\/ Aktualizuj przyciski mobilne\n  const buttonsContainer = document.getElementById(\"pressureLevelButtons\");\n  if (buttonsContainer) {\n    buttonsContainer.querySelectorAll(\"button\").forEach(btn => {\n      btn.classList.toggle(\"active\", btn.dataset.value === selectedPressureLevel);\n    });\n  }\n\n  \/\/ Od\u015bwie\u017cenie obrazka, je\u015bli wszystkie potrzebne parametry s\u0105 ju\u017c wybrane\n  if (selectedModel && selectedTerm && selectedParam && selectedHour && \n      (summaryModels.includes(selectedModel) || selectedStat)) {\n    showImages();\n    \/\/ Preload obrazk\u00f3w dla nowego poziomu ci\u015bnienia\n    preloadForecastImages();\n  }\n});\n\n\/\/ Funkcja do inicjalizacji i konfiguracji poziomego slidera termin\u00f3w prognozy\nfunction setupHorizontalSlider(hours) {\n  if (!hours || hours.length === 0) {\n    document.getElementById(\"horizontalSliderContainer\").style.display = \"none\";\n    return;\n  }\n  \n  \/\/ Poka\u017c kontener slidera\n  document.getElementById(\"horizontalSliderContainer\").style.display = \"block\";\n  \n  \/\/ Ustaw tytu\u0142 slidera w zale\u017cno\u015bci od typu modelu\n  const sliderTitle = document.getElementById(\"sliderTitle\");\n  if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n    sliderTitle.textContent = \"Kolejne prognozy:\";\n  } else {\n    sliderTitle.textContent = \"Godzina prognozy:\";\n  }\n  \n  const slider = document.getElementById(\"forecastHourSlider\");\n  const hourLabelsContainer = document.getElementById(\"hourLabels\");\n  \n  \/\/ Konfiguracja slidera\n  slider.min = 0;\n  slider.max = hours.length - 1;\n  \n  \/\/ Je\u015bli ju\u017c mamy wybran\u0105 godzin\u0119, ustaw slider na t\u0119 warto\u015b\u0107\n  if (selectedHour) {\n    const hourIndex = hours.findIndex(h => h === selectedHour.padStart(2, \"0\"));\n    if (hourIndex !== -1) {\n      slider.value = hourIndex;\n      \/\/ Dla modeli typu podsumowanie w wariancie 12h nie dodajemy \"h\"\n      if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym\n        document.getElementById(\"hourValue\").textContent = forecastHoursDisplayNames[selectedHour] || selectedHour;\n      } else {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym z sufiksem \"h\"\n        const displayHour = forecastHoursDisplayNames[selectedHour] || selectedHour;\n        document.getElementById(\"hourValue\").textContent = displayHour + \"h\";\n      }\n    } else {\n      slider.value = 0;\n      \/\/ Dla modeli typu podsumowanie w wariancie 12h nie dodajemy \"h\"\n      if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym\n        document.getElementById(\"hourValue\").textContent = forecastHoursDisplayNames[hours[0]] || hours[0];\n      } else {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym z sufiksem \"h\"\n        const displayHour = forecastHoursDisplayNames[hours[0]] || hours[0];\n        document.getElementById(\"hourValue\").textContent = displayHour + \"h\";\n      }\n    }\n  } else {\n    \/\/ Domy\u015blnie pierwszy termin\n    slider.value = 0;\n    \/\/ Dla modeli typu podsumowanie w wariancie 12h nie dodajemy \"h\"\n    if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n      \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym\n      document.getElementById(\"hourValue\").textContent = forecastHoursDisplayNames[hours[0]] || hours[0];\n    } else {\n      \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym z sufiksem \"h\"\n      const displayHour = forecastHoursDisplayNames[hours[0]] || hours[0];\n      document.getElementById(\"hourValue\").textContent = displayHour + \"h\";\n    }\n  }\n  \n  \/\/ Wygeneruj etykiety godzin\n  hourLabelsContainer.innerHTML = \"\";\n  \n  \/\/ Dodajemy kilka reprezentatywnych etykiet, nie wszystkie\n  const labelsToShow = Math.min(7, hours.length); \/\/ Zwi\u0119kszona liczba etykiet\n  const step = Math.max(1, Math.floor(hours.length \/ (labelsToShow - 1)));\n  \n  for (let i = 0; i < hours.length; i += step) {\n    if (i > hours.length - 1) break;\n    \n    const label = document.createElement(\"div\");\n    \/\/ Dla modeli typu podsumowanie w wariancie 12h nie dodajemy \"h\"\n    if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n      \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym\n      label.textContent = forecastHoursDisplayNames[hours[i]] || hours[i];\n    } else {\n      \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym z sufiksem \"h\"\n      const displayHour = forecastHoursDisplayNames[hours[i]] || hours[i];\n      label.textContent = displayHour + \"h\";\n    }\n    hourLabelsContainer.appendChild(label);\n    \n    \/\/ Je\u015bli to ostatnia iteracja, ale nie doszli\u015bmy do ko\u0144ca, dodaj ostatni\u0105 etykiet\u0119\n    if (i + step > hours.length - 1 && i !== hours.length - 1) {\n      const lastLabel = document.createElement(\"div\");\n      \/\/ Dla modeli typu podsumowanie w wariancie 12h nie dodajemy \"h\"\n      if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym\n        lastLabel.textContent = forecastHoursDisplayNames[hours[hours.length - 1]] || hours[hours.length - 1];\n      } else {\n        \/\/ U\u017cywamy mapowania do wy\u015bwietlenia godziny w formacie dwucyfrowym z sufiksem \"h\"\n        const displayHour = forecastHoursDisplayNames[hours[hours.length - 1]] || hours[hours.length - 1];\n        lastLabel.textContent = displayHour + \"h\";\n      }\n      hourLabelsContainer.appendChild(lastLabel);\n    }\n  }\n  \n  \/\/ Usu\u0144 wcze\u015bniejsze nas\u0142uchiwanie event\u00f3w, aby unikn\u0105\u0107 duplikacji\n  const oldSlider = slider.cloneNode(true);\n  slider.parentNode.replaceChild(oldSlider, slider);\n  \n  \/\/ Zmienna do op\u00f3\u017anienia aktualizacji obrazk\u00f3w przy szybkim przesuwaniu suwaka\n  let sliderUpdateTimeout = null;\n  let lastSliderPosition = -1;\n  \n  \/\/ Dodaj obs\u0142ug\u0119 zdarzenia zmiany warto\u015bci slidera\n  oldSlider.addEventListener(\"input\", function() {\n    const index = parseInt(this.value);\n    \n    \/\/ Je\u015bli pozycja si\u0119 nie zmieni\u0142a, nie r\u00f3b nic\n    if (index === lastSliderPosition) return;\n    lastSliderPosition = index;\n    \n    const realHourValue = hours[index]; \/\/ Rzeczywista warto\u015b\u0107 u\u017cywana w nazwach plik\u00f3w\n    \n    \/\/ Formatuj godzin\u0119 jako ci\u0105g z wiod\u0105cym zerem\n    const formattedHour = String(realHourValue).padStart(2, \"0\");\n    \n    \/\/ Okre\u015blenie warto\u015bci do wy\u015bwietlania z u\u017cyciem mapowania dla formatu dwucyfrowego\n    let displayHourValue = forecastHoursDisplayNames[realHourValue] || formattedHour;\n    \n    \/\/ Aktualizacja wy\u015bwietlanej warto\u015bci\n    if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n      document.getElementById(\"hourValue\").textContent = displayHourValue;\n    } else {\n      document.getElementById(\"hourValue\").textContent = displayHourValue + \"h\";\n    }\n    \n    \/\/ Aktualizacja wybranej godziny (u\u017cywamy rzeczywistej warto\u015bci)\n    selectedHour = formattedHour;\n    \n    \/\/ Aktualizacja aktywnego przycisku w lewym panelu - szukamy przycisku z odpowiedni\u0105 warto\u015bci\u0105\n    setActiveButton(\"hours\", formattedHour);\n    \n    \/\/ Wyczy\u015b\u0107 poprzedni timeout, je\u015bli istnieje\n    if (sliderUpdateTimeout) {\n      clearTimeout(sliderUpdateTimeout);\n    }\n    \n    \/\/ Wywo\u0142aj showImages od razu bez op\u00f3\u017anienia - obrazy z cache \u0142aduj\u0105 si\u0119 natychmiast\n    \/\/ Dzi\u0119ki temu prze\u0142\u0105czanie jest p\u0142ynne bez migania\n    showImages();\n    \n    \/\/ WA\u017bNE: Po zmianie godziny, zaktualizuj preloading dla nowej pozycji\n    \/\/ Dzi\u0119ki temu kolejne obrazy b\u0119d\u0105 pre\u0142adowane wok\u00f3\u0142 nowo wybranej godziny\n    setTimeout(() => {\n      preloadForecastImages();\n    }, 10); \/\/ Bardzo ma\u0142e op\u00f3\u017anienie \u017ceby nie blokowa\u0107 UI, ale uruchomi\u0107 szybko\n  });\n}\n\n\/\/ Funkcja do generowania nazwy pliku na podstawie parametr\u00f3w\nfunction generateFileName(model, term, param, hour, stat, pressureLevel = null, region = null) {\n  \/\/ U\u017cywamy webp\n  let fileExtension = \"webp\";\n  \n  \/\/ Dodaj poziom ci\u015bnienia do parametru, je\u015bli istnieje\n  let paramToUse = param;\n  if (pressureLevel) {\n    \/\/ Dla 1000 hPa nie dodajemy 0 przed poziomem (T1000), dla innych dodajemy (T0850)\n    if (pressureLevel === \"1000\") {\n      paramToUse = param + pressureLevel;\n    } else {\n      paramToUse = param + \"0\" + pressureLevel;\n    }\n  }\n  \n  \/\/ Dodaj region do parametru, je\u015bli istnieje (dla modeli z podsumowania)\n  \/\/ Format: sf_sudety_01.webp, sf_tatry_01.webp, sf_bieszczady_01.webp\n  if (region) {\n    paramToUse = paramToUse + \"_\" + region;\n  }\n  \n  \/\/ Formatuj godzin\u0119 jako dwucyfrow\u0105 lub bez wiod\u0105cego zera, zale\u017cnie od modelu i parametru\n  let formattedHour;\n  \n  \/\/ Dla a-laef i cosmo_ens zawsze dodajemy wiod\u0105ce zero (01, 02, 03 itd.)\n  if (model === \"a-laef\" || model === \"cosmo_ens\") {\n    formattedHour = String(hour).padStart(2, \"0\");\n  }\n  \/\/ Dla ecmwf_ifs_ens wariant 3h - godziny s\u0105 ju\u017c dwucyfrowe (09, 12, 15, ..., 72)\n  else if (model === \"ecmwf_ifs_ens\" && term === \"3h\") {\n    formattedHour = String(hour).padStart(2, \"0\");\n  }\n  \/\/ Dla icon_ens, ecmwf_ifs_ens, ecmwf_aifs_ens w wariancie 12h zawsze dodajemy wiod\u0105ce zero\n  else if ((model === \"icon_ens\" || model === \"ecmwf_ifs_ens\" || model === \"ecmwf_aifs_ens\") && term === \"12h\") {\n    formattedHour = String(hour).padStart(2, \"0\");\n  } \n  \/\/ Dla wszystkich pozosta\u0142ych modeli i wariant\u00f3w dodajemy wiod\u0105ce zero\n  else {\n    formattedHour = String(hour).padStart(2, \"0\");\n  }\n  \n  let fileName;\n  if (model === \"a-laef\") {\n  \/\/ dla a-laef: PARAMETR_TERMIN.webp (np. tp_06.webp lub tp_1.webp dla 12h)\n    fileName = `${paramToUse}_${formattedHour}.${fileExtension}`;\n  } else if (model === \"cosmo_ens\" || model === \"icon_ens\" || model === \"ecmwf_ifs_ens\" || model === \"ecmwf_aifs_ens\") {\n  \/\/ dla nowych modeli typu \"podsumowanie\": PARAMETR_TERMIN.webp (zawsze z wiod\u0105cym zerem dla godzin < 10)\n    fileName = `${paramToUse}_${formattedHour}.${fileExtension}`;\n  } else if (model === \"icon_lagged\") {\n    \/\/ dla modelu icon_lagged: termin_parametr_statystyka.webp (ma\u0142e litery)\n    fileName = `${formattedHour}_${paramToUse}_${stat}.${fileExtension}`;\n  } else {\n    \/\/ dla pozosta\u0142ych standardowych modeli: TERMIN_PARAMETR_STATYSTYKA.webp (np. 03_TOT_PREC_mean.webp)\n    fileName = `${formattedHour}_${paramToUse}_${stat}.${fileExtension}`;\n  }\n  \n  return fileName;\n}\n\n\/\/ Zmienne do obs\u0142ugi kolejki \u0142adowania obraz\u00f3w\nlet imageLoadQueue = [];\nlet isLoadingImages = false;\nconst MAX_CONCURRENT_LOADS = 5; \/\/ Maksymalna liczba jednocze\u015bnie \u0142adowanych obraz\u00f3w (zwi\u0119kszone dla szybszego preloadingu)\n\n\/\/ Funkcja do preloadowania obrazk\u00f3w dla wszystkich termin\u00f3w prognozy\n\/\/ Zmienna do \u015bledzenia, czy funkcja preloadForecastImages jest aktualnie wykonywana\nlet isPreloadingImages = false;\nlet isPreloadingImagesTimestamp = 0;\n\n\/\/ Zmienna do przechowywania listy pre\u0142adowanych obraz\u00f3w - zdefiniowana poza funkcj\u0105 \n\/\/ \u017ceby by\u0142a dost\u0119pna w addImageToLoadQueue\nlet preloadImagesSet = null;\n\n\/\/ Lista obraz\u00f3w do za\u0142adowania w kolejno\u015bci priorytetowej - zdefiniowana globalnie\n\/\/ \u017ceby by\u0142a dost\u0119pna w addImageToLoadQueue\nlet imagesToLoad = [];\n\n\/\/ Zapami\u0119taj ostatni\u0105 godzin\u0119 dla kt\u00f3rej by\u0142o preloadowanie\nlet lastPreloadedHour = null;\n\nfunction preloadForecastImages() {\n  if (!selectedModel || !selectedTerm || !selectedParam) {\n    return;\n  }\n  \n  if (!summaryModels.includes(selectedModel) && !selectedStat) {\n    return;\n  }\n  \n  \/\/ Je\u015bli funkcja ju\u017c jest wykonywana DLA TEJ SAMEJ godziny, przerwij\n  if (isPreloadingImages && lastPreloadedHour === selectedHour) {\n    return;\n  }\n  \n  \/\/ Je\u015bli zmieniono godzin\u0119, zresetuj flag\u0119\n  if (lastPreloadedHour !== selectedHour) {\n    isPreloadingImages = false;\n    isPreloadingImagesTimestamp = 0;\n  }\n  \n  lastPreloadedHour = selectedHour;\n  \n  \/\/ Ustaw flag\u0119 wykonywania\n  isPreloadingImages = true;\n  isPreloadingImagesTimestamp = Date.now();\n  \n  try {\n    \/\/ Pobranie dost\u0119pnych godzin prognozy\n    let hours = [];\n    \n    \/\/ Specjalna obs\u0142uga dla modeli, kt\u00f3re maj\u0105 r\u00f3\u017cne godziny dla r\u00f3\u017cnych parametr\u00f3w\n    if ((selectedModel === \"cosmo_ens\" || selectedModel === \"icon_ens\" || selectedModel === \"ecmwf_ifs_ens\" || selectedModel === \"ecmwf_aifs_ens\" || selectedModel === \"a-laef\") && \n        paramHours[selectedModel] && \n        paramHours[selectedModel][selectedParam] &&\n        paramHours[selectedModel][selectedParam].length > 0) {\n      hours = paramHours[selectedModel][selectedParam];\n    }\n    else if (forecastHours[selectedModel] && forecastHours[selectedModel][selectedTerm]) {\n      hours = forecastHours[selectedModel][selectedTerm];\n    } else {\n      hours = forecastHours[\"default\"];\n    }\n    \n    \/\/ Znajd\u017a indeks aktualnie wybranej godziny\n    const selectedHourIndex = hours.findIndex(h => String(h).padStart(2, \"0\") === String(selectedHour).padStart(2, \"0\"));\n    if (selectedHourIndex === -1) {\n      return;\n    }\n    \n    \/\/ Tablica do \u015bledzenia ju\u017c za\u0142adowanych obrazk\u00f3w - inicjalizacja globalnej zmiennej\n    preloadImagesSet = new Set();\n    \n    \/\/ Resetuj list\u0119 obraz\u00f3w do za\u0142adowania (u\u017cywamy globalnej zmiennej)\n    imagesToLoad = [];\n    \n    \/\/ Priorytetowo Preload najbli\u017csze godziny\n    let maxOffset = Math.max(selectedHourIndex, hours.length - selectedHourIndex - 1);\n    \n    for (let offset = 0; offset <= maxOffset; offset++) {\n      \/\/ Preload aktualn\u0105 godzin\u0119 (offset=0)\n      if (offset === 0) {\n        addImageToLoadQueue(selectedHourIndex, hours);\n        continue;\n      }\n      \n      \/\/ Preload godzin\u0119 po aktualnej (+offset)\n      if (selectedHourIndex + offset < hours.length) {\n        addImageToLoadQueue(selectedHourIndex + offset, hours);\n      }\n      \n      \/\/ Preload godzin\u0119 przed aktualn\u0105 (-offset)\n      if (selectedHourIndex - offset >= 0) {\n        addImageToLoadQueue(selectedHourIndex - offset, hours);\n      }\n    }\n    \n    \/\/ Dodaj obrazy do g\u0142\u00f3wnej kolejki \u0142adowania\n    if (imagesToLoad.length > 0) {\n      imageLoadQueue = imageLoadQueue.concat(imagesToLoad);\n      if (!isLoadingImages) {\n        processImageQueue();\n      }\n    }\n  } catch (error) {\n    console.error(\"B\u0142\u0105d podczas preloadowania obrazk\u00f3w:\", error);\n  } finally {\n    \/\/ Zawsze resetuj flag\u0119 wykonywania, nawet w przypadku b\u0142\u0119du\n    isPreloadingImages = false;\n    isPreloadingImagesTimestamp = 0;\n    \n    \/\/ Dodajemy bezpiecznik - dodatkowy reset flagi po 5 sekundach, na wypadek gdyby co\u015b posz\u0142o nie tak\n    setTimeout(() => {\n      if (isPreloadingImages) {\n        isPreloadingImages = false;\n        isPreloadingImagesTimestamp = 0;\n      }\n    }, 5000);\n  }\n  \n  \/\/ Funkcja pomocnicza do dodawania obrazu do kolejki \u0142adowania\n  function addImageToLoadQueue(hourIndex, hoursArray) {\n    const hour = hoursArray[hourIndex];\n    const formattedHour = String(hour);\n    \n    \/\/ Dla modeli podsumowania wymuszamy selectedPressureLevel na null\n    const pressureLevelToUse = summaryModels.includes(selectedModel) ? null : selectedPressureLevel;\n    \n    \/\/ Efektywny region - tylko dla modeli z podsumowania i parametr\u00f3w z regionami\n    const effectiveRegion = (summaryModels.includes(selectedModel) && hasRegions(selectedModel, selectedTerm, selectedParam)) ? selectedRegion : null;\n    \n    \/\/ Wygeneruj nazw\u0119 pliku\n    const fileName = generateFileName(\n      selectedModel, \n      selectedTerm, \n      selectedParam, \n      formattedHour, \n      selectedStat, \n      pressureLevelToUse,\n      effectiveRegion\n    );\n    \n    \/\/ Unikaj duplikat\u00f3w\n    const imagePath = `\/wp-content\/uploads\/production\/prognozy_wiazkowe\/${selectedModel}\/${selectedTerm}\/${fileName}`;\n    if (preloadImagesSet && preloadImagesSet.has(imagePath)) {\n      return;\n    }\n    \n    \/\/ Generuj unikalne ID dla obrazu do buforowania\n    const imageId = `img_${selectedModel}_${selectedTerm}_${selectedParam}_${formattedHour}_${selectedStat}_${pressureLevelToUse}_${effectiveRegion}`.replace(\/\\s+\/g, '_');\n    \n    \/\/ Je\u015bli obraz jest ju\u017c w buforze, pomijamy\n    if (imageCache[imageId]) {\n      return;\n    }\n    \n    \/\/ Dodanie do listy preloadowanych\n    if (preloadImagesSet) preloadImagesSet.add(imagePath);\n    \n    \/\/ Dodaj do listy do za\u0142adowania\n    imagesToLoad.push({\n      url: imagePath,\n      id: imageId,\n      fileName: fileName,\n      \/\/ Metadane dla markHourAsLoaded\n      model: selectedModel,\n      term: selectedTerm,\n      param: selectedParam,\n      hour: parseInt(formattedHour),\n      pressureLevel: pressureLevelToUse,\n      region: effectiveRegion\n    });\n  }\n}\n\nfunction markHourAsLoaded(model, term, param, hour, pressureLevel) {\n  const configKey = `${model}_${term}_${param}_${pressureLevel}`;\n  if (!loadedHours[configKey]) {\n    loadedHours[configKey] = new Set();\n  }\n  const hourStr = String(hour).padStart(2, \"0\");\n  loadedHours[configKey].add(hourStr);\n  \n  \/\/ Aktualizuj wizualizacj\u0119 je\u015bli to aktualnie wybrany model\/term\/param\/level\n  if (model === selectedModel && term === selectedTerm && \n      param === selectedParam && pressureLevel === selectedPressureLevel) {\n    updateLoadedHoursVisualization();\n  }\n}\n\n\/\/ Funkcja do aktualizacji wizualizacji za\u0142adowanych godzin\nfunction updateLoadedHoursVisualization() {\n  const configKey = `${selectedModel}_${selectedTerm}_${selectedParam}_${selectedPressureLevel}`;\n  const hoursLoaded = loadedHours[configKey] || new Set();\n  \n  \/\/ Aktualizacja przycisk\u00f3w godzin\n  const hourButtons = document.querySelectorAll('#hours button');\n  hourButtons.forEach(button => {\n    const hour = button.dataset.value;\n    if (hoursLoaded.has(hour)) {\n      button.classList.add('loaded');\n    } else {\n      button.classList.remove('loaded');\n    }\n  });\n  \n  \/\/ Aktualizacja slidera - oblicz procent za\u0142adowanych godzin\n  let hours = [];\n  \n  \/\/ Dla a-laef 1h u\u017cywamy forecastHours[\"a-laef\"][\"1h\"] (sprawdzamy PRZED summaryModels)\n  if (selectedModel === \"a-laef\" && selectedTerm === \"1h\") {\n    hours = forecastHours[\"a-laef\"][\"1h\"];\n  }\n  \/\/ Dla modeli podsumowania u\u017cywamy paramHours\n  else if (summaryModels.includes(selectedModel)) {\n    if (paramHours[selectedModel] && paramHours[selectedModel][selectedParam]) {\n      hours = paramHours[selectedModel][selectedParam];\n    }\n  } \n  \/\/ Dla pozosta\u0142ych modeli u\u017cywamy forecastHours z terminem\n  else if (forecastHours[selectedModel] && forecastHours[selectedModel][selectedTerm]) {\n    hours = forecastHours[selectedModel][selectedTerm];\n  } else {\n    hours = forecastHours[\"default\"];\n  }\n  \n  const slider = document.getElementById('forecastHourSlider');\n  if (!slider) {\n    return;\n  }\n  \n  \/\/ Resetuj t\u0142o slidera na domy\u015blne\n  slider.style.background = '#e0e0e0';\n  \n  if (hours.length > 0) {\n    \/\/ Znajd\u017a najd\u0142u\u017cszy ci\u0105g\u0142y ci\u0105g za\u0142adowanych godzin OD POCZ\u0104TKU\n    let continuousLoadedIndex = -1;\n    for (let index = 0; index < hours.length; index++) {\n      const hourStr = String(hours[index]).padStart(2, \"0\");\n      if (hoursLoaded.has(hourStr)) {\n        continuousLoadedIndex = index;\n      } else {\n        \/\/ Przerwij gdy napotkamy pierwsz\u0105 nieza\u0142adowan\u0105 godzin\u0119\n        break;\n      }\n    }\n    \n    if (continuousLoadedIndex >= 0) {\n      const percentage = ((continuousLoadedIndex + 1) \/ hours.length) * 100;\n      slider.style.background = `linear-gradient(to right, #b0b0b0 0%, #b0b0b0 ${percentage}%, #e0e0e0 ${percentage}%, #e0e0e0 100%)`;\n    }\n  }\n}\n\n\/\/ Funkcja do przetwarzania kolejki obraz\u00f3w z limitowaniem jednoczesnych pobra\u0144\nfunction processImageQueue() {\n  if (imageLoadQueue.length === 0) {\n    isLoadingImages = false;\n    return;\n  }\n  \n  isLoadingImages = true;\n  const currentBatch = imageLoadQueue.splice(0, MAX_CONCURRENT_LOADS);\n  let loadedCount = 0;\n  \n  currentBatch.forEach(imageInfo => {\n    \/\/ Sprawd\u017a czy obraz ju\u017c istnieje w buforze\n    if (imageCache[imageInfo.id]) {\n      loadedCount++;\n      if (loadedCount === currentBatch.length) {\n        setTimeout(processImageQueue, 10); \/\/ Szybsze przej\u015bcie do kolejnej partii\n      }\n      return;\n    }\n    \n    \/\/ Wczytaj obrazek w tle\n    const preloadImg = new Image();\n    \n    preloadImg.onload = () => {\n      \/\/ Zapisz obrazek w buforze\n      imageCache[imageInfo.id] = preloadImg;\n      \n      \/\/ U\u017cyj metadanych z imageInfo zamiast parsowania ID\n      if (imageInfo.model && imageInfo.term && imageInfo.param && imageInfo.hour !== undefined) {\n        \/\/ Oznacz godzin\u0119 jako za\u0142adowan\u0105\n        markHourAsLoaded(imageInfo.model, imageInfo.term, imageInfo.param, imageInfo.hour, imageInfo.pressureLevel);\n      }\n      \n      loadedCount++;\n      if (loadedCount === currentBatch.length) {\n        \/\/ Kiedy wszystkie obrazki z tej partii s\u0105 za\u0142adowane, przejd\u017a do nast\u0119pnej\n        setTimeout(processImageQueue, 10); \/\/ Szybsze przej\u015bcie do kolejnej partii\n      }\n    };\n    \n    preloadImg.onerror = () => {\n      loadedCount++;\n      if (loadedCount === currentBatch.length) {\n        \/\/ Nawet je\u015bli wyst\u0105pi\u0142 b\u0142\u0105d, przejd\u017a do nast\u0119pnej partii\n        setTimeout(processImageQueue, 10); \/\/ Szybsze przej\u015bcie do kolejnej partii\n      }\n    };\n    \n    preloadImg.src = imageInfo.url;\n  });\n}\n\n\/\/ Funkcja do aktualizacji interfejsu na podstawie wybranych warto\u015bci\nfunction updateTermLabel() {\n  \/\/ Znalezienie etykiety dla terminu\n  const sections = document.querySelectorAll('.left-column .section');\n  if (sections.length >= 2) {\n    const termLabel = sections[1].querySelector('h3');\n    if (termLabel) {\n      \/\/ Sprawdzenie czy model jest jednym z modeli \"podsumowanie\"\n      termLabel.textContent = summaryModels.includes(selectedModel) ? \"Zestawienie:\" : \"Termin startu:\";\n    }\n  }\n}\n\nfunction updateTermButtons() {\n  if (!selectedModel) return;\n  \n  \/\/ Aktualizacja etykiety na podstawie wybranego modelu\n  updateTermLabel();\n  \n  \/\/ Ustawienie domy\u015blnych warto\u015bci z ustawie\u0144 dla danego modelu\n  if (!selectedTerm) {\n    selectedTerm = defaultSettings[selectedModel].term;\n  }\n  if (!selectedParam) {\n    selectedParam = defaultSettings[selectedModel].param;\n  }\n  if (!selectedHour) {\n    selectedHour = defaultSettings[selectedModel].hour;\n  }\n  if (!selectedStat && defaultSettings[selectedModel].stat) {\n    selectedStat = defaultSettings[selectedModel].stat;\n  }\n  \n  \/\/ Renderowanie przycisk\u00f3w terminu\n  \n  if (summaryModels.includes(selectedModel)) {\n    \/\/ Dla a-laef pokazujemy 1h i 12h\n    if (selectedModel === \"a-laef\") {\n      renderButtons(\"terms\", alaefVariants, (variant) => {\n        selectedTerm = variant;\n        \n        \/\/ Wyczy\u015b\u0107 cache paramHours dla a-laef przy zmianie wariantu\n        if (paramHours[\"a-laef\"]) {\n          paramHours[\"a-laef\"] = {};\n        }\n        \n        if (variant === \"1h\") {\n          const allowedParams = paramsForModels[\"a-laef\"][\"1h\"] || [];\n          \/\/ Zachowaj wybrany parametr je\u015bli jest dozwolony, inaczej domy\u015blny\n          selectedParam = allowedParams.includes(selectedParam) ? selectedParam : \"t2m\";\n          \/\/ Zachowaj godzin\u0119 je\u015bli jest dost\u0119pna, inaczej pierwsza dost\u0119pna\n          const allowedHours = forecastHours[\"a-laef\"][\"1h\"].map(h => String(h).padStart(2, \"0\"));\n          if (!allowedHours.includes(selectedHour)) {\n            selectedHour = allowedHours[0];\n          }\n          \/\/ Dla 1h nie u\u017cywamy determineAvailableHours, poniewa\u017c wszystkie godziny s\u0105 dost\u0119pne (6-72)\n          \/\/ i s\u0105 ju\u017c zdefiniowane w forecastHours\n          renderParams();\n          renderRegions();\n          renderForecastHours();\n        } else { \/\/ 12h\n          const allowedParams = paramsForModels[\"a-laef\"][\"12h\"] || [];\n          \/\/ Zachowaj wybrany parametr je\u015bli jest dozwolony, inaczej domy\u015blny\n          selectedParam = allowedParams.includes(selectedParam) ? selectedParam : \"t2m_max\";\n          \/\/ Zachowaj godzin\u0119 je\u015bli jest dost\u0119pna, inaczej pierwsza dost\u0119pna\n          const allowedHours = forecastHours[\"a-laef\"][\"12h\"].map(h => String(h).padStart(2, \"0\"));\n          if (!allowedHours.includes(selectedHour)) {\n            selectedHour = allowedHours[0];\n          }\n          \n          \/\/ Wyczy\u015b\u0107 interfejs\n          document.getElementById(\"hours\").innerHTML = \"\";\n          document.getElementById(\"images\").innerHTML = \"\";\n          \n          \/\/ Sprawd\u017a dost\u0119pne godziny dla nowego wariantu i parametru (tylko dla 12h)\n          determineAvailableHours(selectedModel, variant, selectedParam)\n            .then(() => {\n              renderParams();\n              renderRegions();\n              renderForecastHours();\n            });\n        }\n      }, selectedTerm);\n    } else if (selectedModel === \"ecmwf_ifs_ens\") {\n      \/\/ Dla ecmwf_ifs_ens pokazujemy 3h i 12h\n      const variants = [\"3h\", \"12h\"];\n      renderButtons(\"terms\", variants, (variant) => {\n        selectedTerm = variant;\n        \n        const allowedParams = paramsForModels[\"ecmwf_ifs_ens\"][variant] || [];\n        selectedParam = allowedParams.includes(selectedParam) ? selectedParam : (variant === \"3h\" ? \"vis_3h\" : \"t2m_max\");\n        \n        \/\/ Ustaw odpowiedni\u0105 godzin\u0119 dla wariantu\n        const allowedHours = forecastHours[\"ecmwf_ifs_ens\"][variant] || [];\n        if (variant === \"3h\") {\n          selectedHour = allowedHours.length > 0 ? String(allowedHours[0]).padStart(2, \"0\") : \"09\";\n        } else {\n          selectedHour = allowedHours.length > 0 ? String(allowedHours[0]).padStart(2, \"0\") : \"01\";\n        }\n        \n        \/\/ Wyczy\u015b\u0107 interfejs\n        document.getElementById(\"hours\").innerHTML = \"\";\n        document.getElementById(\"images\").innerHTML = \"\";\n        \n        \/\/ Sprawd\u017a dost\u0119pne godziny dla nowego wariantu i parametru\n        determineAvailableHours(selectedModel, variant, selectedParam)\n          .then(() => {\n            renderParams();\n            renderRegions();\n            renderForecastHours();\n          });\n      }, selectedTerm);\n    } else {\n      \/\/ Dla pozosta\u0142ych modeli typu \"podsumowanie\", tylko wariant 12h\n      const variants = [\"12h\"];\n      renderButtons(\"terms\", variants, (variant) => {\n        selectedTerm = variant;\n        \n        \/\/ Przypisanie odpowiedniego domy\u015blnego parametru dla wybranego modelu typu \"podsumowanie\"\n        if (selectedModel === \"cosmo_ens\") {\n          const allowedParams = paramsForModels[\"cosmo_ens\"][variant] || [];\n          selectedParam = allowedParams.includes(selectedParam) ? selectedParam : \"t2m_max\"; \/\/ Domy\u015blny parametr dla cosmo_ens\n          \n          \/\/ Aktualizujemy godziny dla wszystkich parametr\u00f3w przy zmianie terminu\n          document.getElementById(\"params\").innerHTML = '<div class=\"loading-hours\">Aktualizacja parametr\u00f3w...<\/div>';\n          \n          \/\/ Aktualizowanie parametr\u00f3w\n          determineAvailableHours(selectedModel, variant, \"t2m_max\")\n            .then(() => {\n              return Promise.all([\n                determineAvailableHours(selectedModel, variant, \"t2m_min\"),\n                determineAvailableHours(selectedModel, variant, \"t0m_min\"),\n                determineAvailableHours(selectedModel, variant, \"tp\"),\n                determineAvailableHours(selectedModel, variant, \"gst_max\"),\n                determineAvailableHours(selectedModel, variant, \"ws_max\"),\n                selectedParam && ![\"t2m_max\",\"t2m_min\",\"t0m_min\",\"tp\",\"gst_max\",\"ws_max\"].includes(selectedParam)\n                  ? determineAvailableHours(selectedModel, variant, selectedParam)\n                  : Promise.resolve()\n              ]);\n            })\n            .then(() => {\n              const hoursForParam = paramHours[selectedModel][selectedParam] || paramHours[selectedModel][\"t2m_max\"];\n              selectedHour = hoursForParam ? String(hoursForParam[0]) : \"01\";\n              renderParams();\n              renderRegions();\n            });\n          return; \/\/ Przerywanie wykonania\n          \n        } else if (selectedModel === \"icon_ens\") {\n          const allowedParams = paramsForModels[\"icon_ens\"][variant] || [];\n          selectedParam = allowedParams.includes(selectedParam) ? selectedParam : \"t2m_max\"; \/\/ Domy\u015blny parametr dla icon_ens\n          \n          \/\/ Aktualizujemy godziny dla t2m_max przy zmianie terminu\n          document.getElementById(\"params\").innerHTML = '<div class=\"loading-hours\">Aktualizacja parametr\u00f3w...<\/div>';\n          determineAvailableHours(selectedModel, variant, \"t2m_max\")\n            .then(() => {\n              const hoursForParam = paramHours[selectedModel][selectedParam] || paramHours[selectedModel][\"t2m_max\"];\n              selectedHour = hoursForParam ? String(hoursForParam[0]) : \"01\";\n              renderParams();\n              renderRegions();\n            });\n          return; \/\/ Przerywanie wykonania\n          \n        } else {\n          const allowedParams = paramsForModels[selectedModel][variant] || [];\n          selectedParam = allowedParams.includes(selectedParam) ? selectedParam : \"tp\"; \/\/ Domy\u015blny parametr dla ecmwf_ifs_ens i ecmwf_aifs_ens\n          selectedHour = \"1\"; \/\/ Domy\u015blna godzina dla modeli typu \"podsumowanie\"\n        }\n        \n        renderParams();\n        renderRegions();\n      }, \"12h\"); \/\/ Wymuszamy aktywne \"12h\"\n    }\n  } else {\n    \/\/ Dla standardowych modeli pokazujemy standardowe terminy startu\n    renderButtons(\"terms\", termsDefault, (term) => {\n      selectedTerm = term;\n      renderParams();\n    }, selectedTerm);\n  }\n  \n  renderParams();\n}\n\n\/\/ Funkcja do optymalizacji pobierania element\u00f3w DOM - buforuje znalezione elementy\nconst domCache = {};\nfunction getElementCached(id) {\n  if (!domCache[id]) {\n    domCache[id] = document.getElementById(id);\n  }\n  return domCache[id];\n}\n\n\/\/ Funkcja do resetowania wszystkich flag wykonywania\nfunction resetAllExecutionFlags() {\n  \/\/ Resetowanie flag wykonywania\n  isShowImagesRunning = false;\n  isShowImagesRunningTimestamp = 0;\n  isRenderingForecastHours = false;\n  isRenderingForecastHoursTimestamp = 0;\n  isPreloadingImages = false;\n  isPreloadingImagesTimestamp = 0;\n  isLoadingImages = false;\n  \n  \/\/ Resetowanie zmiennej ostatniej preloadowanej godziny\n  lastPreloadedHour = null;\n  \n  \/\/ Wyczy\u015b\u0107 wszystkie timery\n  if (loaderTimeout) {\n    clearTimeout(loaderTimeout);\n    loaderTimeout = null;\n  }\n  \n  if (imageLoadTimeout) {\n    clearTimeout(imageLoadTimeout);\n    imageLoadTimeout = null;\n  }\n  \n  \/\/ Opcjonalnie - zresetuj kolejk\u0119 obraz\u00f3w\n  imageLoadQueue = [];\n}\n\n\/\/ Funkcja resetuj\u0105ca stan przy zmianie modelu\nfunction resetStateForModelChange() {\n  \/\/ Resetuj wszystkie flagi wykonywania\n  resetAllExecutionFlags();\n  \n  \/\/ Resetuj tylko selectedRegion, ale NIE ukrywaj sekcji - renderRegions() to zrobi\n  selectedRegion = null;\n  \n  \/\/ Resetuj bufor obraz\u00f3w - czy\u015bcimy zawarto\u015b\u0107 obiektu zamiast przypisywa\u0107 nowy\n  Object.keys(imageCache).forEach(key => {\n    delete imageCache[key];\n  });\n  \n  \/\/ Resetuj ostatnio wy\u015bwietlany obraz\n  lastDisplayedImage = {\n    model: \"\",\n    term: \"\",\n    param: \"\",\n    hour: \"\",\n    stat: \"\",\n    pressureLevel: \"\",\n    region: \"\"\n  };\n  \n  \/\/ Wyczy\u015b\u0107 aktywny timeout loadera je\u015bli istnieje\n  if (loaderTimeout) {\n    clearTimeout(loaderTimeout);\n    loaderTimeout = null;\n  }\n  \n  \/\/ Wyczy\u015b\u0107 kontenery obraz\u00f3w\n  const imagesContainer = getElementCached(\"images\");\n  if (imagesContainer) {\n    imagesContainer.innerHTML = \"\";\n  }\n  \n  \/\/ Wyczy\u015b\u0107 kolejk\u0119 \u0142adowania obraz\u00f3w\n  imageLoadQueue = [];\n  isLoadingImages = false;\n  \n  \/\/ Wyczy\u015b\u0107 timeout \u0142adowania obrazu je\u015bli istnieje\n  if (imageLoadTimeout) {\n    clearTimeout(imageLoadTimeout);\n    imageLoadTimeout = null;\n  }\n}\n\n\/\/ Timeout \u0142adowania obrazu\nlet imageLoadTimeout = null;\n\n\/\/ Zmienna do \u015bledzenia ostatniego wy\u015bwietlonego obrazu\nlet lastDisplayedImage = {\n  model: \"\",\n  term: \"\",\n  param: \"\",\n  hour: \"\",\n  stat: \"\",\n  pressureLevel: \"\",\n  region: \"\"\n};\n\n\/\/ Bufor za\u0142adowanych obraz\u00f3w dla optymalizacji\nconst imageCache = {};\n\n\/\/ \u015aledzenie za\u0142adowanych godzin dla wizualizacji\nconst loadedHours = {};\n\n\/\/ Zmienna do \u015bledzenia, czy funkcja showImages jest aktualnie wykonywana\nlet isShowImagesRunning = false;\nlet isShowImagesRunningTimestamp = 0;\n\/\/ Zmienne do \u015bledzenia ostatniego \u017c\u0105dania obrazu\nlet latestImageRequest = null;\nlet pendingImageRequest = false;\n\nfunction showImages() {\n  if (!selectedModel || !selectedTerm || !selectedParam || !selectedHour) return;\n  if (!summaryModels.includes(selectedModel) && !selectedStat) return;\n  \n  \/\/ Dla modeli podsumowania wymuszamy selectedPressureLevel na null\n  const effectivePressureLevel = summaryModels.includes(selectedModel) ? null : selectedPressureLevel;\n  \n  \/\/ Efektywny region - tylko dla modeli z podsumowania i parametr\u00f3w z regionami\n  const effectiveRegion = (summaryModels.includes(selectedModel) && hasRegions(selectedModel, selectedTerm, selectedParam)) ? selectedRegion : null;\n  \n  \/\/ Zapami\u0119taj aktualne \u017c\u0105danie\n  latestImageRequest = {\n    model: selectedModel,\n    term: selectedTerm,\n    param: selectedParam,\n    hour: selectedHour,\n    stat: selectedStat,\n    pressureLevel: effectivePressureLevel,\n    region: effectiveRegion\n  };\n  \n  \/\/ Je\u015bli funkcja jest ju\u017c w trakcie wykonywania, zaznacz, \u017ce jest nowe \u017c\u0105danie oczekuj\u0105ce\n  if (isShowImagesRunning) {\n    pendingImageRequest = true;\n    return;\n  }\n  \n  \/\/ Ustaw flag\u0119 na true - funkcja jest teraz wykonywana\n  isShowImagesRunning = true;\n  isShowImagesRunningTimestamp = Date.now();\n  \n  try {\n    \/\/ Generujemy unikalny identyfikator obrazu na podstawie parametr\u00f3w zanim cokolwiek zrobimy\n    const imageId = `img_${selectedModel}_${selectedTerm}_${selectedParam}_${selectedHour}_${selectedStat}_${effectivePressureLevel}_${effectiveRegion}`.replace(\/\\s+\/g, '_');\n  \n  \/\/ Sprawd\u017a, czy te same parametry obrazu s\u0105 ju\u017c wy\u015bwietlane (zapobieganie duplikacji)\n  if (lastDisplayedImage && \n      lastDisplayedImage.model === selectedModel &&\n      lastDisplayedImage.term === selectedTerm &&\n      lastDisplayedImage.param === selectedParam &&\n      lastDisplayedImage.hour === selectedHour &&\n      lastDisplayedImage.stat === selectedStat &&\n      lastDisplayedImage.pressureLevel === effectivePressureLevel &&\n      lastDisplayedImage.region === effectiveRegion) {\n    \/\/ Sprawd\u017a, czy obraz faktycznie istnieje w DOM\n    if (document.getElementById(imageId)) {\n      \/\/ Resetuj flag\u0119 wykonywania\n      isShowImagesRunning = false;\n      return; \/\/ Obraz ju\u017c istnieje, przerywamy\n    }\n    \/\/ Je\u015bli obraz nie istnieje mimo \u017ce parametry si\u0119 zgadzaj\u0105, kontynuujemy aby go doda\u0107\n  }\n  \n  \/\/ U\u017cywamy zoptymalizowanego dost\u0119pu do element\u00f3w DOM\n  const container = getElementCached(\"images\");\n  \n  \/\/ Dodatkowe sprawdzenie czy obraz z tym ID ju\u017c istnieje w DOM\n  if (document.getElementById(imageId)) {\n    \/\/ Resetuj flag\u0119 wykonywania\n    isShowImagesRunning = false;\n    return; \/\/ Przerywamy funkcj\u0119 je\u015bli obraz ju\u017c istnieje\n  }\n  \n  \/\/ Sprawd\u017a, czy mamy ju\u017c za\u0142adowany ten obraz w buforze\n  const isCachedImage = imageCache[imageId];\n  \n  \/\/ Tworzymy znacznik identyfikuj\u0105cy to konkretne wywo\u0142anie funkcji\n  const requestId = Date.now();\n  container.dataset.lastRequestId = requestId;\n  \n  \/\/ Je\u015bli obraz jest w buforze, dodajmy go natychmiast bez pokazywania animacji \u0142adowania\n  if (isCachedImage) {\n    \/\/ Sprawd\u017a czy przypadkiem nie jest to dok\u0142adnie ten sam obraz, kt\u00f3ry ju\u017c jest wy\u015bwietlany\n    const existingImages = container.querySelectorAll('img');\n    if (existingImages.length === 1 && existingImages[0].id === imageId) {\n      isShowImagesRunning = false;\n      return;\n    }\n    \n    \/\/ Sprawd\u017a czy ju\u017c jest jaki\u015b obraz w kontenerze\n    if (existingImages.length > 0) {\n      \/\/ Je\u015bli jest, po prostu zamie\u0144 jego src i id - to jest najszybsze i bez migania\n      const existingImg = existingImages[0];\n      existingImg.src = imageCache[imageId].src;\n      existingImg.id = imageId;\n      \n      \/\/ Usu\u0144 pozosta\u0142e obrazy je\u015bli s\u0105 (nie powinny by\u0107, ale dla pewno\u015bci)\n      for (let i = 1; i < existingImages.length; i++) {\n        existingImages[i].remove();\n      }\n    } else {\n      \/\/ Je\u015bli nie ma obrazu, usu\u0144 loadery i dodaj nowy\n      container.innerHTML = '';\n      const cachedImg = imageCache[imageId].cloneNode(true);\n      cachedImg.style.display = \"block\";\n      cachedImg.id = imageId;\n      container.appendChild(cachedImg);\n    }\n    \n    \/\/ Zapisz stan ostatniego wy\u015bwietlonego obrazu\n    lastDisplayedImage = {\n      model: selectedModel,\n      term: selectedTerm,\n      param: selectedParam,\n      hour: selectedHour,\n      stat: selectedStat,\n      pressureLevel: effectivePressureLevel,\n      region: effectiveRegion,\n      imageId: imageId\n    };\n    \n    \/\/ WA\u017bNE: Resetuj flag\u0119 wykonywania, poniewa\u017c sko\u0144czyli\u015bmy \u0142adowa\u0107 obraz z cache\n    isShowImagesRunning = false;\n    \n    return; \/\/ Ko\u0144czymy funkcj\u0119 wcze\u015bniej - bez pokazywania loadera\n  }\n  \n  \/\/ Je\u015bli obrazu NIE MA w buforze, teraz czy\u015bcimy kontener i pokazujemy loader\n  container.innerHTML = \"\";\n  \n  \/\/ Dodanie identyfikatora i timeout aby usun\u0105\u0107 animacj\u0119 je\u015bli utkn\u0119\u0142a\n  const loaderId = \"loader-\" + new Date().getTime();\n  \n  \/\/ Dodanie loadera do kontenera z unikalnym ID (u\u017cywamy DocumentFragment dla lepszej wydajno\u015bci)\n  const fragment = document.createDocumentFragment();\n  const loaderContainer = document.createElement(\"div\");\n  loaderContainer.className = \"loader-container\";\n  loaderContainer.id = loaderId;\n  \n  const loader = document.createElement(\"div\");\n  loader.className = \"loader\";\n  \n  loaderContainer.appendChild(loader);\n  fragment.appendChild(loaderContainer);\n  container.appendChild(fragment);\n\n  \/\/ Resetowanie timeouta je\u015bli istnieje\n  if (loaderTimeout) {\n    clearTimeout(loaderTimeout);\n  }\n\n  \/\/ je\u015bli animacja \u0142adowania nie zostanie usuni\u0119ta w ci\u0105gu 15 sekund,\n  \/\/ to pokazany b\u0119dzie komunikat o b\u0142\u0119dzie\n  loaderTimeout = setTimeout(function() {\n    const loaderElement = document.getElementById(loaderId);\n    if (loaderElement) {\n      container.innerHTML = \"\";\n      \n      const errorMessage = document.createElement(\"div\");\n      errorMessage.innerHTML = '<h3 style=\"color: red; text-align: center;\">Nie uda\u0142o si\u0119 za\u0142adowa\u0107 obrazu<\/h3>' +\n                               '<p style=\"text-align: center;\">Sprawd\u017a po\u0142\u0105czenie internetowe lub spr\u00f3buj ponownie p\u00f3\u017aniej.<\/p>';\n      container.appendChild(errorMessage);\n      \n      \/\/ Resetuj lastDisplayedImage aby nie blokowa\u0107 ponownych pr\u00f3b wczytania obrazu\n      lastDisplayedImage = {\n        model: \"\",\n        term: \"\",\n        param: \"\",\n        hour: \"\",\n        stat: \"\",\n        pressureLevel: \"\",\n        region: \"\"\n      };\n    }\n  }, 15000); \/\/ 15 sekund\n  \n  \/\/ Generowanie nazwy pliku\n  const fileName = generateFileName(\n    selectedModel, \n    selectedTerm, \n    selectedParam, \n    selectedHour, \n    selectedStat, \n    effectivePressureLevel,\n    effectiveRegion\n  );\n  \n  \/\/ Formatowanie godziny do wy\u015bwietlenia w nazwie pliku\n  let formattedHour;\n  if (selectedModel === \"a-laef\" && selectedTerm === \"12h\") {\n    formattedHour = selectedHour; \/\/ Format bez wiod\u0105cego zera dla a-laef 12h\n  } else {\n    formattedHour = String(selectedHour).padStart(2, \"0\"); \/\/ Format z wiod\u0105cym zerem dla wszystkich innych\n  }\n  \n  \/\/ Formatowanie godziny do wy\u015bwietlenia w tytule\n  let displayHour = formattedHour;\n  \n  \/\/ Aktualizowanie tytu\u0142u slidera w zale\u017cno\u015bci od typu modelu\n  const sliderTitle = document.getElementById(\"sliderTitle\");\n  if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n    sliderTitle.textContent = \"Kolejne prognozy:\";\n  } else {\n    sliderTitle.textContent = \"Godzina prognozy:\";\n  }\n  \n  \/\/ Aktualizowanie tytu\u0142u sekcji w lewej kolumnie dla sp\u00f3jno\u015bci\n  const hoursTitle = document.getElementById(\"hoursTitle\");\n  if (hoursTitle) {\n    if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n      hoursTitle.textContent = \"Kolejne prognozy:\";\n    } else {\n      hoursTitle.textContent = \"Godziny prognozy:\";\n    }\n  }\n  \n  \/\/ Ukrywanie g\u0142\u00f3wnego tytu\u0142u sekcji obrazka\n  document.getElementById(\"imageTitle\").textContent = \"\";\n  \n  \/\/ Aktualizacja opisu parametru - u\u017cyj wsp\u00f3lnego opisu EPS dla wszystkich parametr\u00f3w\n  const descriptionContainer = document.getElementById(\"parameterDescription\");\n  descriptionContainer.innerHTML = epsDescription;\n  \n  \/\/ Sprawdzamy, czy obraz jest ju\u017c w buforze\n  if (imageCache[imageId]) {\n    \/\/ Usuni\u0119cie animacji \u0142adowania\n    const loaderElements = container.querySelectorAll('.loader-container');\n    for (let i = 0; i < loaderElements.length; i++) {\n      if (loaderElements[i] && loaderElements[i].parentNode) {\n        container.removeChild(loaderElements[i]);\n      }\n    }\n    \n    \/\/ Zapisz stan ostatniego wy\u015bwietlonego obrazu\n    lastDisplayedImage = {\n      model: selectedModel,\n      term: selectedTerm,\n      param: selectedParam,\n      hour: selectedHour,\n      stat: selectedStat,\n      pressureLevel: effectivePressureLevel,\n      region: effectiveRegion,\n      imageId: imageId\n    };\n    \n    \/\/ Klonowanie obrazu z bufora i dodanie go do DOM\n    const cachedImg = imageCache[imageId].cloneNode(true);\n    cachedImg.style.display = \"block\";\n    container.appendChild(cachedImg);\n    \n    \/\/ Wyczy\u015b\u0107 timeout je\u015bli istnieje\n    if (imageLoadTimeout) {\n      clearTimeout(imageLoadTimeout);\n      imageLoadTimeout = null;\n    }\n    \n    return; \/\/ Ko\u0144czymy funkcj\u0119 wcze\u015bniej\n  }\n  \n  \/\/ Utworzenie elementu obrazu\n  const img = new Image();\n  img.id = imageId; \/\/ Ustawienie ID dla obrazu\n  img.style.display = \"none\"; \/\/ Ukrycie obrazka podczas \u0142adowania\n  \n  \/\/ Zapami\u0119tujemy ID \u017c\u0105dania w obrazku\n  img.dataset.requestId = requestId;\n\n  \/\/ Ustawienie callback\u00f3w przed ustawieniem src!\n  img.onload = function() {\n    \/\/ Czyszczenie timeoutu je\u015bli istnieje\n    if (imageLoadTimeout) {\n      clearTimeout(imageLoadTimeout);\n      imageLoadTimeout = null;\n    }\n    \n    \/\/ Sprawd\u017a, czy to jest ostatnie \u017c\u0105danie (czy kontener nie zosta\u0142 zast\u0105piony przez nowsze \u017c\u0105danie)\n    if (container.dataset.lastRequestId !== this.dataset.requestId) {\n      return;\n    }\n    \n    \/\/ Usuni\u0119cie animacji \u0142adowania - bezpieczna metoda\n    const loaderElements = container.querySelectorAll('.loader-container');\n    for (let i = 0; i < loaderElements.length; i++) {\n      if (loaderElements[i] && loaderElements[i].parentNode) {\n        container.removeChild(loaderElements[i]);\n      }\n    }\n    \n    \/\/ Wyczy\u015b\u0107 kontener przed dodaniem obrazka, aby upewni\u0107 si\u0119, \u017ce nie ma kilku obraz\u00f3w\n    container.innerHTML = '';\n    \n    \/\/ Pokazanie za\u0142adowanego obrazka\n    img.style.display = \"block\";\n    container.appendChild(img);\n    \n    \/\/ Zapisz stan ostatniego wy\u015bwietlonego obrazu\n    lastDisplayedImage = {\n      model: selectedModel,\n      term: selectedTerm,\n      param: selectedParam,\n      hour: selectedHour,\n      stat: selectedStat,\n      pressureLevel: effectivePressureLevel,\n      region: effectiveRegion,\n      imageId: imageId\n    };\n    \n    \/\/ Zapisanie obrazu do bufora (zachowujemy maksymalnie 20 obraz\u00f3w)\n    if (Object.keys(imageCache).length >= 20) {\n      \/\/ Usuwamy pierwszy element (najstarszy)\n      const firstKey = Object.keys(imageCache)[0];\n      delete imageCache[firstKey];\n    }\n    imageCache[imageId] = img.cloneNode(true);\n  };\n  \n  \/\/ Dodanie obs\u0142ugi b\u0142\u0119d\u00f3w dla obrazka\n  img.onerror = function() {\n    this.onerror = null;\n    \n    \/\/ Czyszczenie timeoutu je\u015bli istnieje\n    if (imageLoadTimeout) {\n      clearTimeout(imageLoadTimeout);\n      imageLoadTimeout = null;\n    }\n    \n    \/\/ Sprawd\u017a, czy to jest ostatnie \u017c\u0105danie (czy kontener nie zosta\u0142 zast\u0105piony przez nowsze \u017c\u0105danie)\n    if (container.dataset.lastRequestId !== this.dataset.requestId) {\n      return;\n    }\n    \n    \/\/ Resetowanie wszystkich flag wykonywania w przypadku b\u0142\u0119du\n    isShowImagesRunning = false;\n    isShowImagesRunningTimestamp = 0;\n    isRenderingForecastHours = false;\n    isRenderingForecastHoursTimestamp = 0;\n    isPreloadingImages = false;\n    isPreloadingImagesTimestamp = 0;\n    isLoadingImages = false;\n    \n    \/\/ Natychmiastowe czyszczenie kontenera\n    container.innerHTML = '';\n    \n    \/\/ Reset stanu ostatnio wy\u015bwietlonego obrazu w przypadku b\u0142\u0119du - ustawiamy pusty obiekt zamiast null\n    lastDisplayedImage = {\n      model: \"\",\n      term: \"\",\n      param: \"\",\n      hour: \"\",\n      stat: \"\",\n      pressureLevel: \"\",\n      region: \"\"\n    };\n    \n    try {\n      \/\/ Tworzenie obrazka zast\u0119pczego\n      const errorImg = document.createElement(\"img\");\n      errorImg.src = \"\/wp-content\/uploads\/production\/prognozy_wiazkowe\/configuration_not.png\";\n      errorImg.alt = \"Konfiguracja niedost\u0119pna\";\n      errorImg.style.maxWidth = \"100%\";\n      errorImg.style.display = \"block\";\n      container.appendChild(errorImg);\n      \/\/ Dodanie komunikatu o b\u0142\u0119dzie\n      const errorMsg = document.createElement(\"p\");\n      errorMsg.textContent = \"Brak dost\u0119pnych prognoz dla wybranej konfiguracji.\";\n      errorMsg.style.color = \"red\";\n      errorMsg.style.textAlign = \"center\";\n      container.appendChild(errorMsg);\n      \/\/ Dodanie obs\u0142ugi b\u0142\u0119du dla obrazka zast\u0119pczego\n      errorImg.onerror = function() {\n        this.style.display = \"none\";\n      };\n    } catch (e) {\n      console.error(\"B\u0142\u0105d podczas obs\u0142ugi b\u0142\u0119du \u0142adowania obrazka:\", e);\n      \/\/ Ostateczne rozwi\u0105zanie\n      container.innerHTML = '<div style=\"color: red; text-align: center; padding: 20px;\"><h3>Brak dost\u0119pnej prognozy<\/h3><p>Nie znaleziono prognozy dla wybranych parametr\u00f3w.<\/p><\/div>';\n    }\n  }\n  \n  \/\/ Ustawienie timeoutu dla obrazka\n  if (imageLoadTimeout) {\n    clearTimeout(imageLoadTimeout);\n  }\n  \n  \/\/ Ustawienie nowego timeoutu na 20 sekund\n  imageLoadTimeout = setTimeout(function() {\n    \/\/ Je\u015bli obrazek jeszcze si\u0119 \u0142aduje, wywo\u0142ujemy b\u0142\u0105d\n    if (!img.complete) {\n      img.onerror(); \/\/ Wywo\u0142ujemy funkcj\u0119 obs\u0142ugi b\u0142\u0119du r\u0119cznie\n    }\n  }, 20000); \/\/ 20 sekund na za\u0142adowanie\n  \n  \/\/ Ustawienie src na ko\u0144cu\n  img.src = `\/wp-content\/uploads\/production\/prognozy_wiazkowe\/${selectedModel}\/${selectedTerm}\/${fileName}`;\n  img.alt = fileName;\n  } finally {\n    \/\/ Zawsze resetuj flag\u0119 wykonywania na ko\u0144cu, nawet w przypadku b\u0142\u0119du\n    setTimeout(() => {\n      isShowImagesRunning = false;\n      isShowImagesRunningTimestamp = 0;\n      \n      \/\/ Sprawd\u017a, czy pojawi\u0142o si\u0119 nowe \u017c\u0105danie podczas wykonywania tej funkcji\n      if (pendingImageRequest) {\n        pendingImageRequest = false;\n        \n        \/\/ Natychmiast wczytaj najnowszy \u017c\u0105dany obraz\n        setTimeout(() => {\n          showImages();\n        }, 10); \/\/ Bardzo kr\u00f3tki timeout\n      }\n    }, 10); \/\/ Bardzo kr\u00f3tki timeout, aby szybko reagowa\u0107 na zmiany\n  }\n}\n\n\/\/ Funkcja do wst\u0119pnego \u0142adowania danych dla modelu\nfunction preloadModelData(model) {\n  return new Promise((resolve) => {\n    \/\/ Zapisz, \u017ce model jest preloadowany\n    preloadedModels[model] = true;\n    \n    \/\/ Ustal domy\u015blne warto\u015bci dla tego modelu\n    const term = defaultSettings[model].term;\n    const param = defaultSettings[model].param;\n    const stat = defaultSettings[model].stat;\n    \n    \/\/ Sprawd\u017a dost\u0119pne godziny, je\u015bli to model podsumowania\n    if (summaryModels.includes(model)) {\n      determineAvailableHours(model, term, param)\n        .then((hours) => {\n          if (hours && hours.length > 0) {\n            \/\/ Dla ka\u017cdej godziny preloaduj pierwszy obrazek\n            const firstHour = hours[0];\n            \/\/ Utw\u00f3rz \u015bcie\u017ck\u0119 obrazka\n            const imgPrefix = (summaryModels.includes(model)) ? \"summary\" : \"standard\";\n            const imgSuffix = (summaryModels.includes(model)) ? \"\" : `_${stat}`;\n            const fileName = `${imgPrefix}_${model}_${term}_${param}${imgSuffix}_${firstHour}.png`;\n            \n            \/\/ Wst\u0119pnie za\u0142aduj tylko pierwszy obrazek\n            const img = new Image();\n            img.src = `\/wp-content\/uploads\/production\/prognozy_wiazkowe\/${model}\/${term}\/${fileName}`;\n            \n            \/\/ Zawsze zwracaj sukces, nawet je\u015bli obrazek si\u0119 nie za\u0142aduje\n            setTimeout(resolve, 500);\n          } else {\n            resolve();\n          }\n        })\n        .catch(() => resolve()); \/\/ Zawsze resolve, nawet w przypadku b\u0142\u0119du\n    } else {\n      \/\/ Dla modeli standardowych, preloaduj pierwszy obrazek\n      const hours = forecastHours[model]?.[term] || forecastHours[\"default\"];\n      if (hours && hours.length > 0) {\n        const firstHour = String(hours[0]).padStart(2, \"0\");\n        const fileName = `standard_${model}_${term}_${param}_${stat}_${firstHour}.png`;\n        \n        \/\/ Wst\u0119pnie za\u0142aduj tylko pierwszy obrazek\n        const img = new Image();\n        img.src = `\/wp-content\/uploads\/production\/prognozy_wiazkowe\/${model}\/${term}\/${fileName}`;\n        \n        \/\/ Zawsze zwracaj sukces, nawet je\u015bli obrazek si\u0119 nie za\u0142aduje\n        setTimeout(resolve, 500);\n      } else {\n        resolve();\n      }\n    }\n  });\n}\n\n\/\/ Funkcja do aktualizacji wszystkich aktywnych przycisk\u00f3w\nfunction updateAllActiveButtons() {\n  \/\/ Usuni\u0119cie starych przycisk\u00f3w i renderowanie nowych z aktywnym zaznaczeniem\n  renderButtons(\"models\", models, (model) => {\n    const previousModel = selectedModel;\n    \n    \/\/ Zapisz poprzednie warto\u015bci\n    let prevParam = isParameterWithPressureLevel(selectedParam) ? getBaseParam(selectedParam) : selectedParam;\n    const prevStat = selectedStat;\n    const prevHour = selectedHour;\n    const prevTerm = selectedTerm;\n    \n    selectedModel = model;\n    \n    \/\/ Sprawd\u017a czy zmieniamy typ modelu\n    const prevModelWasSummary = summaryModels.includes(previousModel);\n    const newModelIsSummary = summaryModels.includes(model);\n    const changingModelType = prevModelWasSummary !== newModelIsSummary;\n    \n    \/\/ Zachowaj termin dla zwyk\u0142ych modeli\n    if (!summaryModels.includes(model)) {\n      if (prevTerm && termsDefault.includes(prevTerm)) {\n        selectedTerm = prevTerm;\n      } else {\n        selectedTerm = defaultSettings[model].term;\n      }\n    } else {\n      selectedTerm = defaultSettings[model].term;\n    }\n    \n    \/\/ Sprawd\u017a czy poprzedni parametr jest dost\u0119pny w nowym modelu\n    \/\/ Pobierz list\u0119 parametr\u00f3w dla nowego modelu\n    let availableParams = [];\n    if (summaryModels.includes(model)) {\n      \/\/ Dla modeli typu podsumowanie pobieramy parametry zale\u017cnie od wariantu (1h lub 12h)\n      availableParams = paramsForModels[model][selectedTerm] || [];\n    } else {\n      \/\/ Dla pozosta\u0142ych modeli parametry s\u0105 takie same niezale\u017cnie od terminu\n      availableParams = paramsForModels[model] || [];\n    }\n    \n    \/\/ Filtruj parametry (usu\u0144 te z poziomem ci\u015bnienia)\n    let filteredParams = [];\n    let baseParamsAdded = new Set();\n    availableParams.forEach(param => {\n      if (\/^[A-Z]$\/.test(param)) {\n        if (!baseParamsAdded.has(param)) {\n          filteredParams.push(param);\n          baseParamsAdded.add(param);\n        }\n      } else if (isParameterWithPressureLevel(param)) {\n        const baseParam = getBaseParam(param);\n        if (!baseParamsAdded.has(baseParam)) {\n          filteredParams.push(baseParam);\n          baseParamsAdded.add(baseParam);\n        }\n      } else {\n        filteredParams.push(param);\n      }\n    });\n    \n    \/\/ Je\u015bli poprzedni parametr jest dost\u0119pny w nowym modelu, zachowaj go\n    if (prevParam && filteredParams.includes(prevParam)) {\n      selectedParam = prevParam;\n    } else {\n      \/\/ W przeciwnym razie u\u017cyj domy\u015blnego parametru\n      selectedParam = defaultSettings[model].param;\n    }\n    \n    \/\/ Okre\u015bl dost\u0119pne godziny dla nowego modelu\n    let availableHours = [];\n    if (forecastHours[model] && forecastHours[model][selectedTerm]) {\n      availableHours = forecastHours[model][selectedTerm].map(h => String(h).padStart(2, \"0\"));\n    } else {\n      availableHours = forecastHours[\"default\"].map(h => String(h).padStart(2, \"0\"));\n    }\n    \n    \/\/ Zachowaj godzin\u0119 TYLKO je\u015bli pozostajemy w tym samym typie modelu\n    if (!changingModelType && prevHour) {\n      const prevHourNormalized = String(prevHour).padStart(2, \"0\");\n      const prevHourInt = parseInt(String(prevHour), 10);\n      \n      \/\/ Sprawd\u017a czy poprzednia godzina jest dost\u0119pna\n      const isAvailable = availableHours.some(h => {\n        const hNormalized = String(h).padStart(2, \"0\");\n        const hInt = parseInt(String(h), 10);\n        return hNormalized === prevHourNormalized || hInt === prevHourInt;\n      });\n      \n      if (isAvailable) {\n        selectedHour = prevHourNormalized;\n      } else {\n        selectedHour = String(defaultSettings[model].hour).padStart(2, \"0\");\n      }\n    } else {\n      \/\/ Zmiana typu modelu - u\u017cyj domy\u015blnej godziny\n      selectedHour = String(defaultSettings[model].hour).padStart(2, \"0\");\n    }\n    \n    \/\/ Zachowaj statystyk\u0119 dla zwyk\u0142ych modeli\n    if (!summaryModels.includes(model)) {\n      const availableStats = suffixConfig[selectedParam] || suffixConfig[\"default\"];\n      \n      if (prevStat && availableStats.includes(prevStat)) {\n        selectedStat = prevStat;\n      } else {\n        selectedStat = defaultSettings[model].stat;\n      }\n    } else {\n      selectedStat = null; \/\/ Modele podsumowania nie maj\u0105 statystyk\n    }\n\n    \/\/ Zresetuj region przy zmianie modelu\n    const regionSection = document.getElementById(\"regionSection\");\n    const regionsContainer = document.getElementById(\"regions\");\n    selectedRegion = null;\n    if (!summaryModels.includes(model)) {\n      if (regionsContainer) regionsContainer.innerHTML = \"\";\n      if (regionSection) regionSection.style.display = \"none\";\n    } else {\n      renderRegions();\n    }\n    \n    updateTermLabel();\n    \n    \/\/ Wyczy\u015b\u0107 obrazy i poka\u017c animacj\u0119 \u0142adowania, je\u015bli jest to zmiana modelu\n    if (previousModel !== model) {\n      resetStateForModelChange();\n      \n      const imagesContainer = document.getElementById(\"images\");\n      if (imagesContainer) {\n        imagesContainer.innerHTML = \"\";\n        if (!preloadedModels[model]) {\n          const loaderContainer = document.createElement(\"div\");\n          loaderContainer.className = \"loader-container\";\n          const loader = document.createElement(\"div\");\n          loader.className = \"loader\";\n          loaderContainer.appendChild(loader);\n          const loadingText = document.createElement(\"p\");\n          loadingText.textContent = \"\u0141adowanie prognozy...\";\n          loadingText.style.marginTop = \"10px\";\n          loaderContainer.appendChild(loadingText);\n          imagesContainer.appendChild(loaderContainer);\n        }\n      }\n    }\n\n    \/\/ Je\u015bli to model podsumowania, potrzebujemy sprawdzi\u0107 dost\u0119pne godziny\n    if (summaryModels.includes(model)) {\n      \/\/ Je\u015bli model by\u0142 preloadowany, mo\u017cemy przyspieszy\u0107 proces\n      if (preloadedModels[model]) {\n        \/\/ Szybkie prze\u0142\u0105czenie bez dodatkowego sprawdzania\n        updateTermButtons();\n        renderRegions();\n        renderForecastHours();\n      } else {\n        \/\/ Standardowe sprawdzanie godzin\n        determineAvailableHours(model, selectedTerm, selectedParam)\n          .then(() => {\n            updateTermButtons();\n            renderRegions();\n            \/\/ Po sprawdzeniu dost\u0119pnych godzin, renderuj interfejs\n            renderForecastHours();\n            \n            \/\/ Dodajemy model do listy preloadowanych\n            preloadedModels[model] = true;\n          });\n      }\n    } else {\n      \/\/ Dla zwyk\u0142ych modeli (nie-podsumowania) r\u00f3wnie\u017c musimy zaktualizowa\u0107 interfejs\n      updateTermButtons();\n      renderForecastHours(); \/\/ WA\u017bNE: Renderuj godziny aby pokaza\u0107 zachowan\u0105 godzin\u0119!\n      \n      \/\/ Dodajemy model do listy preloadowanych\n      preloadedModels[model] = true;\n    }\n  }, selectedModel, modelDisplayNames);\n  \n  \/\/ Je\u015bli mamy slider z poziomami ci\u015bnienia i ustawiony poziom, aktualizuje jego warto\u015b\u0107\n  if (document.getElementById(\"pressureLevelSliderSection\").style.display !== \"none\" && selectedPressureLevel) {\n    const levelIndex = pressureLevels.indexOf(selectedPressureLevel);\n    if (levelIndex !== -1) {\n      document.getElementById(\"pressureLevelSlider\").value = levelIndex;\n      document.getElementById(\"pressureValue\").textContent = selectedPressureLevel + \" hPa\";\n    }\n  }\n}\n\n\/\/ Funkcja do parsowania parametr\u00f3w URL\nfunction parseURLParams() {\n  const urlParams = new URLSearchParams(window.location.search);\n  const params = {};\n  \n  \/\/ Pobierz wszystkie parametry z URL\n  for (const [key, value] of urlParams) {\n    params[key] = value;\n  }\n  \n  return params;\n}\n\n\/\/ Funkcja do walidacji i zastosowania parametr\u00f3w z URL\nfunction validateAndApplyURLParams() {\n  const params = parseURLParams();\n  \n  \/\/ W przypadku braku parametr\u00f3w w URL, stosowane s\u0105 domy\u015blne warto\u015bci\n  if (Object.keys(params).length === 0) {\n    return false;\n  }\n  \n  \/\/ Walidacja modelu\n  if (params.model && isValidModel(params.model)) {\n    selectedModel = params.model;\n  } else if (params.model) {\n    console.warn(`Nieprawid\u0142owy model: ${params.model}, u\u017cywam domy\u015blnego.`);\n  }\n  \n  \/\/ Walidacja terminu startu\n  if (params.term && isValidTerm(params.term, selectedModel)) {\n    selectedTerm = params.term;\n  } else if (params.term) {\n    console.warn(`Nieprawid\u0142owy termin startu: ${params.term}, u\u017cywam domy\u015blnego dla modelu ${selectedModel}.`);\n    selectedTerm = defaultSettings[selectedModel].term;\n  } else {\n    selectedTerm = defaultSettings[selectedModel].term;\n  }\n  \n  \/\/ Walidacja parametru\n  if (params.param && isValidParam(params.param, selectedModel, selectedTerm)) {\n    selectedParam = params.param;\n  } else if (params.param) {\n    console.warn(`Nieprawid\u0142owy parametr: ${params.param}, u\u017cywam domy\u015blnego dla modelu ${selectedModel}.`);\n    selectedParam = defaultSettings[selectedModel].param;\n  } else {\n    selectedParam = defaultSettings[selectedModel].param;\n  }\n  \n  \/\/ Walidacja statystyki (je\u015bli nie jest to model a-laef)\n  if (selectedModel !== \"a-laef\") {\n    if (params.stat && isValidStat(params.stat, selectedParam)) {\n      selectedStat = params.stat;\n    } else if (params.stat) {\n      console.warn(`Nieprawid\u0142owa statystyka: ${params.stat}, u\u017cywam domy\u015blnej dla parametru ${selectedParam}.`);\n      selectedStat = defaultSettings[selectedModel].stat;\n    } else {\n      selectedStat = defaultSettings[selectedModel].stat;\n    }\n  }\n  \n  \/\/ Walidacja terminu prognozy\n  if (params.hour && isValidHour(params.hour, selectedModel, selectedTerm)) {\n    selectedHour = params.hour;\n  } else if (params.hour) {\n    console.warn(`Nieprawid\u0142owy termin prognozy: ${params.hour}, u\u017cywam domy\u015blnego.`);\n    selectedHour = defaultSettings[selectedModel].hour;\n  } else {\n    selectedHour = defaultSettings[selectedModel].hour;\n  }\n  \n  \/\/ Poziom ci\u015bnienia (opcjonalny)\n  if (params.level && pressureLevels.includes(params.level)) {\n    selectedPressureLevel = params.level;\n  }\n  \n  return true;\n}\n\n\/\/ Funkcja sprawdzaj\u0105ca czy model jest prawid\u0142owy\nfunction isValidModel(model) {\n  return models.includes(model);\n}\n\n\/\/ Funkcja sprawdzaj\u0105ca czy termin jest prawid\u0142owy dla wybranego modelu\nfunction isValidTerm(term, model) {\n  if (model === \"a-laef\") {\n    return alaefVariants.includes(term);\n  }\n  return termsDefault.includes(term);\n}\n\n\/\/ Funkcja sprawdzaj\u0105ca czy parametr jest prawid\u0142owy dla wybranego modelu\nfunction isValidParam(param, model, term) {\n  if (model === \"a-laef\") {\n    return (paramsForModels[model] && paramsForModels[model][term] && paramsForModels[model][term].includes(param));\n  }\n  return (paramsForModels[model] && paramsForModels[model].includes(param));\n}\n\n\/\/ Funkcja sprawdzaj\u0105ca czy statystyka jest prawid\u0142owa dla wybranego parametru\nfunction isValidStat(stat, param) {\n  const stats = suffixConfig[param] || suffixConfig[\"default\"];\n  return stats.includes(stat);\n}\n\n\/\/ Funkcja sprawdzaj\u0105ca czy termin prognozy jest prawid\u0142owy dla wybranego modelu\nfunction isValidHour(hour, model, term) {\n  let hours = [];\n  if (model === \"a-laef\") {\n    hours = forecastHours[\"a-laef\"][term] || [];\n  } else {\n    hours = forecastHours[\"default\"];\n  }\n  return hours.map(String).includes(hour);\n}\n\n\/\/ Zmienne do \u015bledzenia stanu gotowo\u015bci aplikacji\nlet isAppReady = false;\nlet preloadedModels = {};\nlet initialLoadDone = false;\n\n\/\/ Inicjalizacja po za\u0142adowaniu strony\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n  \/\/ Dodanie wska\u017anika \u0142adowania na pocz\u0105tku\n  const mainContent = document.querySelector(\".container\");\n  const initialLoader = document.createElement(\"div\");\n  initialLoader.id = \"initialLoader\";\n  initialLoader.className = \"loader-container\";\n  initialLoader.style.position = \"fixed\";\n  initialLoader.style.top = \"0\";\n  initialLoader.style.left = \"0\";\n  initialLoader.style.width = \"100%\";\n  initialLoader.style.height = \"100%\";\n  initialLoader.style.backgroundColor = \"rgba(255, 255, 255, 0.8)\";\n  initialLoader.style.zIndex = \"9999\";\n  initialLoader.innerHTML = `\n    <div class=\"loader\"><\/div>\n    <p style=\"margin-top: 10px; font-weight: bold;\">\u0141adowanie aplikacji...<\/p>\n  `;\n  document.body.appendChild(initialLoader);\n  \n  \/\/ Dodanie mo\u017cliwo\u015bci resetowania wszystkich flag przez podw\u00f3jne klikni\u0119cie na nag\u0142\u00f3wek\n  document.querySelector(\"h1\")?.addEventListener(\"dblclick\", function() {\n    resetAllExecutionFlags();\n    alert(\"Flagi wykonywania zosta\u0142y zresetowane\");\n  });\n  \n  \/\/ Ustawienie bezpiecznika czasowego dla resetowania flag\n  \/\/ Sprawdza co 30 sekund, czy flagi s\u0105 aktywne d\u0142u\u017cej ni\u017c 60 sekund (prawdziwy deadlock)\n  setInterval(() => {\n    const now = Date.now();\n    const DEADLOCK_TIMEOUT = 60000; \/\/ 60 sekund\n    \n    let shouldReset = false;\n    \n    \/\/ Sprawd\u017a ka\u017cd\u0105 flag\u0119 osobno z jej timestampem\n    if (isShowImagesRunning && isShowImagesRunningTimestamp > 0 && (now - isShowImagesRunningTimestamp) > DEADLOCK_TIMEOUT) {\n      shouldReset = true;\n    }\n    \n    if (isRenderingForecastHours && isRenderingForecastHoursTimestamp > 0 && (now - isRenderingForecastHoursTimestamp) > DEADLOCK_TIMEOUT) {\n      shouldReset = true;\n    }\n    \n    if (isPreloadingImages && isPreloadingImagesTimestamp > 0 && (now - isPreloadingImagesTimestamp) > DEADLOCK_TIMEOUT) {\n      shouldReset = true;\n    }\n    \n    if (shouldReset) {\n      resetAllExecutionFlags();\n    }\n  }, 30000);\n  \n  \/\/ Dodanie mechanizmu automatycznego naprawiania interfejsu\n  setInterval(function() {\n    const imagesContainer = document.getElementById(\"images\");\n    \n    \/\/ Sprawd\u017a czy kontener obraz\u00f3w jest pusty, a powinien zawiera\u0107 obrazy\n    if (imagesContainer && \n        isAppReady && \n        selectedModel && \n        selectedTerm && \n        selectedParam && \n        selectedHour && \n        (summaryModels.includes(selectedModel) || selectedStat) && \n        imagesContainer.innerHTML.trim() === \"\") {\n      \n      \/\/ Dodaj animacj\u0119 \u0142adowania\n      const loaderContainer = document.createElement(\"div\");\n      loaderContainer.className = \"loader-container\";\n      const loader = document.createElement(\"div\");\n      loader.className = \"loader\";\n      loaderContainer.appendChild(loader);\n      const loadingText = document.createElement(\"p\");\n      loadingText.textContent = \"Od\u015bwie\u017canie prognozy...\";\n      loadingText.style.marginTop = \"10px\";\n      loaderContainer.appendChild(loadingText);\n      imagesContainer.appendChild(loaderContainer);\n      \n      \/\/ Po kr\u00f3tkim op\u00f3\u017anieniu spr\u00f3buj za\u0142adowa\u0107 obrazy ponownie\n      setTimeout(() => {\n        showImages();\n      }, 100);\n    }\n  }, 2000); \/\/ Sprawdzaj co 2 sekundy\n\n  \/\/ Pr\u00f3ba za\u0142adowania parametr\u00f3w z URL\n  if (validateAndApplyURLParams()) {\n    updateTermButtons();\n    \n    \/\/ Renderuj list\u0119 godzin dla modeli podsumowania\n    if (summaryModels.includes(selectedModel)) {\n      determineAvailableHours(selectedModel, selectedTerm, selectedParam)\n        .then(() => {\n          renderForecastHours();\n        });\n    }\n  } else {\n    initializeInterface();\n  }\n  \n  \/\/ Rozpocznij preload domy\u015blnego modelu i kilku alternatywnych\n  Promise.all([\n    preloadModelData(selectedModel),\n    \/\/ Preload najcz\u0119\u015bciej u\u017cywanych modeli w tle\n    preloadModelData(\"EPS28km\")\n  ]).then(() => {\n    initialLoadDone = true;\n    \n    \/\/ Usu\u0144 wska\u017anik \u0142adowania\n    const loader = document.getElementById(\"initialLoader\");\n    if (loader) {\n      loader.style.opacity = \"0\";\n      loader.style.transition = \"opacity 0.5s\";\n      setTimeout(() => {\n        loader.remove();\n      }, 500);\n    }\n    \n    \/\/ Oznacz aplikacj\u0119 jako gotow\u0105\n    isAppReady = true;\n  });\n  \n  \/\/ Dodatkowa weryfikacja poprawnego pod\u015bwietlenia przycisk\u00f3w i za\u0142adowania obrazka\n  setTimeout(function() {\n    updateAllActiveButtons();\n    \/\/ Zapewnienie poprawnego za\u0142adowania obrazka\n    showImages();\n    \/\/ Zaktualizuj wizualizacj\u0119 za\u0142adowanych godzin (slider i przyciski)\n    updateLoadedHoursVisualization();\n  }, 1000);\n  \n  \/\/ Obs\u0142uga przycisku animacji\n  let animationInterval = null;\n  let isAnimating = false;\n  \n  document.getElementById(\"animateBtn\")?.addEventListener(\"click\", function() {\n    const animateBtn = this;\n    \n    if (isAnimating) {\n      \/\/ Zatrzymaj animacj\u0119\n      clearInterval(animationInterval);\n      animationInterval = null;\n      isAnimating = false;\n      animateBtn.classList.remove(\"active\");\n      animateBtn.textContent = \"\u25b6\";\n      animateBtn.title = \"Animuj godziny prognozy\";\n    } else {\n      \/\/ Rozpocznij animacj\u0119\n      isAnimating = true;\n      animateBtn.classList.add(\"active\");\n      animateBtn.textContent = \"\u23f8\";\n      animateBtn.title = \"Zatrzymaj animacj\u0119\";\n      \n      \/\/ Pobierz dost\u0119pne godziny\n      let hours = [];\n      \n      \/\/ Specjalna obs\u0142uga dla modeli z r\u00f3\u017cnymi godzinami dla r\u00f3\u017cnych parametr\u00f3w\n      if ((selectedModel === \"cosmo_ens\" || selectedModel === \"icon_ens\" || selectedModel === \"ecmwf_ifs_ens\" || selectedModel === \"ecmwf_aifs_ens\" || selectedModel === \"a-laef\") && \n          paramHours[selectedModel] && \n          paramHours[selectedModel][selectedParam] &&\n          paramHours[selectedModel][selectedParam].length > 0) {\n        hours = paramHours[selectedModel][selectedParam];\n      } else if (forecastHours[selectedModel] && forecastHours[selectedModel][selectedTerm]) {\n        hours = forecastHours[selectedModel][selectedTerm];\n      } else {\n        hours = forecastHours[\"default\"];\n      }\n      \n      if (hours.length === 0) {\n        isAnimating = false;\n        animateBtn.classList.remove(\"active\");\n        animateBtn.textContent = \"\u25b6\";\n        return;\n      }\n      \n      animationInterval = setInterval(() => {\n        \/\/ Znajd\u017a aktualny indeks\n        const currentIndex = hours.findIndex(h => String(h).padStart(2, \"0\") === String(selectedHour).padStart(2, \"0\"));\n        \n        \/\/ Przejd\u017a do nast\u0119pnej godziny (zap\u0119tlenie)\n        const nextIndex = (currentIndex + 1) % hours.length;\n        const newHour = String(hours[nextIndex]).padStart(2, \"0\");\n        selectedHour = newHour;\n        \n        \/\/ Zaktualizuj interfejs\n        setActiveButton(\"hours\", newHour);\n        \n        \/\/ Zaktualizuj slider\n        const slider = document.getElementById(\"forecastHourSlider\");\n        if (slider) {\n          slider.value = nextIndex;\n          \/\/ Aktualizacja wy\u015bwietlanej warto\u015bci\n          if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n            document.getElementById(\"hourValue\").textContent = forecastHoursDisplayNames[newHour] || newHour;\n          } else {\n            const displayHour = forecastHoursDisplayNames[newHour] || newHour;\n            document.getElementById(\"hourValue\").textContent = displayHour + \"h\";\n          }\n        }\n        \n        \/\/ Za\u0142aduj obrazek\n        showImages();\n      }, 1000); \/\/ Zmiana co 1 sekund\u0119\n    }\n  });\n  \n  \/\/ Obs\u0142uga klawiszy strza\u0142ek do przesuwania slidera prognoz\n  \/\/ Zmienne do kontroli op\u00f3\u017anienia skr\u00f3t\u00f3w klawiszowych\n  let lastKeyPressTime = 0;\n  const keyPressDelay = 130; \/\/ Op\u00f3\u017anienie w milisekundach (130ms)\n\n  document.addEventListener(\"keydown\", function(event) {\n    \/\/ Ignoruj tylko je\u015bli u\u017cytkownik pisze w polu tekstowym lub textarea\n    if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {\n      return;\n    }\n    \n    \/\/ Najpierw zablokuj domy\u015blne zachowanie dla klawiszy nawigacyjnych\n    const paramsSelect = document.getElementById('paramsSelect');\n    const statsSelect = document.getElementById('statsSelect');\n    const isParamsSelectActive = document.activeElement === paramsSelect;\n    const isStatsSelectActive = document.activeElement === statsSelect;\n    const isAnySelectActive = isParamsSelectActive || isStatsSelectActive;\n    \n    \/\/ Blokuj przewijanie strony dla strza\u0142ek (chyba \u017ce edytujemy list\u0119 parametr\u00f3w lub statystyk)\n    if ((event.key === \"ArrowLeft\" || event.key === \"ArrowRight\" || \n         event.key === \"ArrowUp\" || event.key === \"ArrowDown\") && !isAnySelectActive) {\n      event.preventDefault();\n    }\n    \n    \/\/ Sprawd\u017a op\u00f3\u017anienie dla wszystkich skr\u00f3t\u00f3w klawiszowych\n    const currentTime = Date.now();\n    if (currentTime - lastKeyPressTime < keyPressDelay) {\n      return; \/\/ Ignoruj je\u015bli min\u0119\u0142o za ma\u0142o czasu\n    }\n    lastKeyPressTime = currentTime;\n    \n    \/\/ Strza\u0142ki lewo\/prawo - zmiana godzin prognozy\n    if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n      \n      \/\/ Pobierz dost\u0119pne godziny\n      let hours = [];\n      \n      \/\/ Dla modeli podsumowania u\u017cyj paramHours (specyficzne dla parametru)\n      if (summaryModels.includes(selectedModel)) {\n        \/\/ Dla a-laef 1h u\u017cyj bezpo\u015brednio forecastHours (poniewa\u017c wszystkie godziny 6-72 s\u0105 dost\u0119pne)\n        if (selectedModel === \"a-laef\" && selectedTerm === \"1h\") {\n          hours = forecastHours[selectedModel][selectedTerm] || [];\n        } else if (paramHours[selectedModel] && paramHours[selectedModel][selectedParam]) {\n          hours = paramHours[selectedModel][selectedParam];\n        } else if (forecastHours[selectedModel] && forecastHours[selectedModel][selectedTerm]) {\n          hours = forecastHours[selectedModel][selectedTerm];\n        } else {\n          hours = forecastHours[\"default\"];\n        }\n      } else {\n        \/\/ Dla zwyk\u0142ych modeli u\u017cyj forecastHours\n        if (forecastHours[selectedModel] && forecastHours[selectedModel][selectedTerm]) {\n          hours = forecastHours[selectedModel][selectedTerm];\n        } else {\n          hours = forecastHours[\"default\"];\n        }\n      }\n      \n      if (hours.length === 0) return;\n      \n      \/\/ Znajd\u017a aktualny indeks\n      const currentIndex = hours.findIndex(h => String(h).padStart(2, \"0\") === String(selectedHour).padStart(2, \"0\"));\n      \n      let newIndex;\n      if (event.key === \"ArrowLeft\") {\n        \/\/ Strza\u0142ka w lewo - poprzednia godzina (zatrzymaj si\u0119 na pocz\u0105tku)\n        newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex;\n      } else {\n        \/\/ Strza\u0142ka w prawo - nast\u0119pna godzina (zatrzymaj si\u0119 na ko\u0144cu)\n        newIndex = currentIndex < hours.length - 1 ? currentIndex + 1 : currentIndex;\n      }\n      \n      \/\/ Je\u015bli indeks si\u0119 nie zmieni\u0142, nie r\u00f3b nic\n      if (newIndex === currentIndex) {\n        return;\n      }\n      \n      \/\/ Ustaw now\u0105 godzin\u0119\n      const newHour = String(hours[newIndex]).padStart(2, \"0\");\n      selectedHour = newHour;\n      \n      \/\/ Zaktualizuj interfejs\n      setActiveButton(\"hours\", newHour);\n      \n      \/\/ Zaktualizuj slider\n      const slider = document.getElementById(\"forecastHourSlider\");\n      if (slider) {\n        slider.value = newIndex;\n        const displayHour = forecastHoursDisplayNames[newHour] || newHour;\n        if (summaryModels.includes(selectedModel) && selectedTerm === \"12h\") {\n          document.getElementById(\"hourValue\").textContent = displayHour;\n        } else {\n          document.getElementById(\"hourValue\").textContent = displayHour + \"h\";\n        }\n      }\n      \n      \/\/ Za\u0142aduj obrazek\n      showImages();\n      preloadForecastImages();\n    }\n    \n    \/\/ Strza\u0142ki g\u00f3ra\/d\u00f3\u0142 - zmiana poziom\u00f3w ci\u015bnienia\n    if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n      \/\/ Je\u015bli lista parametr\u00f3w lub statystyk ma focus, pozw\u00f3l domy\u015blnej akcji (nawigacja po li\u015bcie)\n      if (isAnySelectActive) {\n        return; \/\/ Nie blokuj domy\u015blnego zachowania\n      }\n      \n      \/\/ W przeciwnym razie - steruj suwakiem ci\u015bnienia\n      \/\/ Sprawd\u017a czy suwak poziom\u00f3w jest widoczny\n      const sliderSection = document.getElementById(\"pressureLevelSliderSection\");\n      if (!sliderSection || sliderSection.style.display === \"none\") {\n        return;\n      }\n      \n      \/\/ W testy.html u\u017cywamy sta\u0142ej tablicy pressureLevels\n      if (!selectedPressureLevel) return;\n      \n      \/\/ Znajd\u017a aktualny indeks\n      const currentIndex = pressureLevels.indexOf(selectedPressureLevel);\n      if (currentIndex === -1) return;\n      \n      let newIndex;\n      if (event.key === \"ArrowUp\") {\n        \/\/ Strza\u0142ka w g\u00f3r\u0119 - ni\u017csze ci\u015bnienie (wy\u017cszy indeks w tablicy posortowanej malej\u0105co)\n        \/\/ Zatrzymaj si\u0119 na ko\u0144cu\n        newIndex = currentIndex < pressureLevels.length - 1 ? currentIndex + 1 : currentIndex;\n      } else {\n        \/\/ Strza\u0142ka w d\u00f3\u0142 - wy\u017csze ci\u015bnienie (ni\u017cszy indeks)\n        \/\/ Zatrzymaj si\u0119 na pocz\u0105tku\n        newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex;\n      }\n      \n      \/\/ Je\u015bli indeks si\u0119 nie zmieni\u0142, nie r\u00f3b nic\n      if (newIndex === currentIndex) {\n        return;\n      }\n      \n      \/\/ Ustaw nowy poziom\n      selectedPressureLevel = pressureLevels[newIndex];\n      \n      \/\/ Zaktualizuj slider\n      const slider = document.getElementById(\"pressureLevelSlider\");\n      if (slider) {\n        slider.value = newIndex;\n        document.getElementById(\"pressureValue\").textContent = selectedPressureLevel + \" hPa\";\n      }\n      \n      \/\/ Za\u0142aduj obrazek\n      showImages();\n      preloadForecastImages();\n    }\n    \n    \/\/ Cyfry 1-9 - szybki wyb\u00f3r modelu\n    if (event.key >= '1' && event.key <= '9') {\n      event.preventDefault();\n      \n      const modelIndex = parseInt(event.key) - 1;\n      if (modelIndex < models.length) {\n        const newModel = models[modelIndex];\n        \n        \/\/ Symuluj klikni\u0119cie w przycisk modelu\n        const modelButtons = document.querySelectorAll('#models button');\n        modelButtons.forEach(btn => {\n          if (btn.textContent.trim() === modelDisplayNames[newModel] || btn.textContent.trim() === newModel) {\n            btn.click();\n          }\n        });\n      }\n    }\n    \n    \/\/ Tab - rozwini\u0119cie\/zwini\u0119cie listy parametr\u00f3w\n    if (event.key === 'Tab') {\n      event.preventDefault();\n      \n      const paramsSelect = document.getElementById('paramsSelect');\n      \n      \/\/ Je\u015bli lista parametr\u00f3w jest ju\u017c otwarta, zamknij j\u0105\n      if (paramsSelect && paramsSelect.size > 1) {\n        paramsSelect.size = 0;\n        \/\/ Przywr\u00f3\u0107 aktualnie wybrany parametr\n        paramsSelect.value = selectedParam || '';\n        paramsSelect.blur();\n        return;\n      }\n      \n      \/\/ W przeciwnym razie otw\u00f3rz list\u0119 parametr\u00f3w i zamknij statystyki\n      \/\/ Zamknij list\u0119 statystyk je\u015bli jest otwarta\n      const statsSelect = document.getElementById('statsSelect');\n      if (statsSelect && statsSelect.size > 1) {\n        statsSelect.size = 0;\n        \/\/ Przywr\u00f3\u0107 aktualnie wybran\u0105 statystyk\u0119\n        statsSelect.value = selectedStat || '';\n        statsSelect.blur();\n      }\n      \n      if (paramsSelect) {\n        paramsSelect.focus();\n        \/\/ Symuluj klikni\u0119cie aby rozwin\u0105\u0107 list\u0119\n        paramsSelect.click();\n        \/\/ Alternatywnie: programowo rozwi\u0144 select\n        paramsSelect.size = Math.min(paramsSelect.options.length, 10);\n      }\n    }\n    \n    \/\/ A - w\u0142\u0105cz\/wy\u0142\u0105cz animacj\u0119 godzin prognozy\n    if (event.key === 'a' || event.key === 'A') {\n      event.preventDefault();\n      const animateBtn = document.getElementById(\"animateBtn\");\n      if (animateBtn) {\n        animateBtn.click();\n      }\n    }\n    \n    \/\/ Q - rozwini\u0119cie\/zwini\u0119cie listy statystyk (tylko dla zwyk\u0142ych modeli, nie dla modeli podsumowania)\n    if (event.key === 'q' || event.key === 'Q') {\n      event.preventDefault();\n      \/\/ Sprawd\u017a czy to model podsumowania\n      if (summaryModels.includes(selectedModel)) {\n        \/\/ Dla modeli podsumowania nie ma statystyk, wi\u0119c nic nie r\u00f3b\n        return;\n      }\n      \n      const statsSelect = document.getElementById('statsSelect');\n      \n      \/\/ Je\u015bli lista statystyk jest ju\u017c otwarta, zamknij j\u0105\n      if (statsSelect && statsSelect.size > 1) {\n        statsSelect.size = 0;\n        \/\/ Przywr\u00f3\u0107 aktualnie wybran\u0105 statystyk\u0119\n        statsSelect.value = selectedStat || '';\n        statsSelect.blur();\n        return;\n      }\n      \n      \/\/ W przeciwnym razie otw\u00f3rz list\u0119 statystyk i zamknij parametry\n      \/\/ Zamknij list\u0119 parametr\u00f3w je\u015bli jest otwarta\n      const paramsSelect = document.getElementById('paramsSelect');\n      if (paramsSelect && paramsSelect.size > 1) {\n        paramsSelect.size = 0;\n        \/\/ Przywr\u00f3\u0107 aktualnie wybrany parametr\n        paramsSelect.value = selectedParam || '';\n        paramsSelect.blur();\n      }\n      \n      if (statsSelect && statsSelect.options.length > 1) { \/\/ Sprawd\u017a czy ma opcje opr\u00f3cz domy\u015blnej\n        statsSelect.focus();\n        \/\/ Symuluj klikni\u0119cie aby rozwin\u0105\u0107 list\u0119\n        statsSelect.click();\n        \/\/ Alternatywnie: programowo rozwi\u0144 select\n        statsSelect.size = Math.min(statsSelect.options.length, 10);\n      }\n    }\n    \n    \/\/ , i . - zmiana terminu inicjacji (, = lewo\/wcze\u015bniej, . = prawo\/p\u00f3\u017aniej)\n    if (event.key === ',' || event.key === '<' || event.key === '.' || event.key === '>') {\n      event.preventDefault();\n      \n      \/\/ Pobierz dost\u0119pne terminy dla aktualnego modelu\n      let availableTerms;\n      if (selectedModel === \"a-laef\") {\n        availableTerms = alaefVariants; \/\/ [\"1h\", \"12h\"]\n      } else if (summaryModels.includes(selectedModel)) {\n        \/\/ Modele podsumowania: ecmwf_ifs_ens ma 3h i 12h, reszta tylko 12h\n        if (selectedModel === \"ecmwf_ifs_ens\") {\n          availableTerms = [\"3h\", \"12h\"];\n        } else {\n          availableTerms = [\"12h\"];\n        }\n      } else {\n        \/\/ Zwyk\u0142e modele\n        availableTerms = termsDefault; \/\/ [\"00\", \"06\", \"12\", \"18\"]\n      }\n      \n      if (availableTerms.length === 0) return;\n      \n      \/\/ Znajd\u017a aktualny indeks\n      const currentIndex = availableTerms.indexOf(selectedTerm);\n      if (currentIndex === -1) return;\n      \n      let newIndex;\n      if (event.key === ',' || event.key === '<') {\n        \/\/ , lub < - poprzedni termin (zatrzymaj si\u0119 na pocz\u0105tku)\n        newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex;\n      } else {\n        \/\/ . lub > - nast\u0119pny termin (zatrzymaj si\u0119 na ko\u0144cu)\n        newIndex = currentIndex < availableTerms.length - 1 ? currentIndex + 1 : currentIndex;\n      }\n      \n      \/\/ Je\u015bli indeks si\u0119 nie zmieni\u0142, nie r\u00f3b nic\n      if (newIndex === currentIndex) {\n        return;\n      }\n      \n      \/\/ Ustaw nowy termin\n      const newTerm = availableTerms[newIndex];\n      \n      \/\/ Symuluj klikni\u0119cie w przycisk terminu\n      const termButtons = document.querySelectorAll('#terms button');\n      termButtons.forEach(btn => {\n        if (btn.dataset.value === newTerm || btn.textContent.includes(newTerm)) {\n          btn.click();\n        }\n      });\n    }\n  });\n});\nconsole.log(\"Autor Bart\u0142omiej Sobczyk 2025\");\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: Termin startu: Parametr: Statystyka: Obszar: Godziny prognozy: *Je\u015bli przy formacie czasu nie widnieje adnotacja UTC to znaczy, \u017ce obraz generowany jest na wskazany termin wg czasu urz\u0119dowego. \ud83d\udca1 Skr\u00f3ty klawiszowe: 1-5 Wyb\u00f3r modelu &lt; &gt; Zmiana terminu startu \u2190 \u2192 Zmiana godzin prognozy Tab Rozwini\u0119cie parametr\u00f3w A Animacja godzin prognozy Poziom ci\u015bnienia: 1000 hPa [&hellip;]<\/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>Prognozy wi\u0105zkowe IMGW<\/title>\n<meta name=\"description\" content=\"Wyniki modeli numerycznych prognoz wi\u0105zkowych 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=46288\" \/>\n<meta property=\"og:locale\" content=\"pl_PL\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Prognozy wi\u0105zkowe\" \/>\n<meta property=\"og:description\" content=\"Prognozy wi\u0105zkowe\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cmm.imgw.pl\/?page_id=46288\" \/>\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=\"2025-12-16T10:56:35+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:title\" content=\"Prognozy wi\u0105zkowe\" \/>\n<meta name=\"twitter:description\" content=\"Prognozy wi\u0105zkowe\" \/>\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=46288\",\"url\":\"https:\/\/cmm.imgw.pl\/?page_id=46288\",\"name\":\"Prognozy wi\u0105zkowe IMGW\",\"isPartOf\":{\"@id\":\"https:\/\/cmm.imgw.pl\/#website\"},\"datePublished\":\"2025-09-04T08:36:09+00:00\",\"dateModified\":\"2025-12-16T10:56:35+00:00\",\"description\":\"Wyniki modeli numerycznych prognoz wi\u0105zkowych IMGW-PIB\",\"breadcrumb\":{\"@id\":\"https:\/\/cmm.imgw.pl\/?page_id=46288#breadcrumb\"},\"inLanguage\":\"pl-PL\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cmm.imgw.pl\/?page_id=46288\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cmm.imgw.pl\/?page_id=46288#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cmm.imgw.pl\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"IMGW-PIB LMM: Prognozy wi\u0105zkowe\"}]},{\"@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":"Prognozy wi\u0105zkowe IMGW","description":"Wyniki modeli numerycznych prognoz wi\u0105zkowych 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=46288","og_locale":"pl_PL","og_type":"article","og_title":"Prognozy wi\u0105zkowe","og_description":"Prognozy wi\u0105zkowe","og_url":"https:\/\/cmm.imgw.pl\/?page_id=46288","og_site_name":"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","article_publisher":"https:\/\/www.facebook.com\/Meteoimgw\/","article_modified_time":"2025-12-16T10:56:35+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_title":"Prognozy wi\u0105zkowe","twitter_description":"Prognozy wi\u0105zkowe","twitter_site":"@IMGW_CMM","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/cmm.imgw.pl\/?page_id=46288","url":"https:\/\/cmm.imgw.pl\/?page_id=46288","name":"Prognozy wi\u0105zkowe IMGW","isPartOf":{"@id":"https:\/\/cmm.imgw.pl\/#website"},"datePublished":"2025-09-04T08:36:09+00:00","dateModified":"2025-12-16T10:56:35+00:00","description":"Wyniki modeli numerycznych prognoz wi\u0105zkowych IMGW-PIB","breadcrumb":{"@id":"https:\/\/cmm.imgw.pl\/?page_id=46288#breadcrumb"},"inLanguage":"pl-PL","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cmm.imgw.pl\/?page_id=46288"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/cmm.imgw.pl\/?page_id=46288#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cmm.imgw.pl\/"},{"@type":"ListItem","position":2,"name":"IMGW-PIB LMM: Prognozy wi\u0105zkowe"}]},{"@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\/46288"}],"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=46288"}],"version-history":[{"count":384,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/pages\/46288\/revisions"}],"predecessor-version":[{"id":48180,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/pages\/46288\/revisions\/48180"}],"wp:attachment":[{"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=46288"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}