{"id":48626,"date":"2026-01-08T13:27:19","date_gmt":"2026-01-08T12:27:19","guid":{"rendered":"https:\/\/cmm.imgw.pl\/?page_id=48626"},"modified":"2026-04-27T17:32:17","modified_gmt":"2026-04-27T15:32:17","slug":"prognozy-krotkoterminow","status":"publish","type":"page","link":"https:\/\/cmm.imgw.pl\/?page_id=48626","title":{"rendered":"Kr\u00f3tkoterminowe"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"48626\" class=\"elementor elementor-48626\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-3ef8d03d elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"3ef8d03d\" 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-6ce9cc47\" data-id=\"6ce9cc47\" 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-9ef5e01 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"9ef5e01\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<style>\/*! elementor - v3.12.1 - 02-04-2023 *\/\n.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:#69727d;color:#fff}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap{color:#69727d;border:3px solid;background-color:transparent}.elementor-widget-text-editor:not(.elementor-drop-cap-view-default) .elementor-drop-cap{margin-top:8px}.elementor-widget-text-editor:not(.elementor-drop-cap-view-default) .elementor-drop-cap-letter{width:1em;height:1em}.elementor-widget-text-editor .elementor-drop-cap{float:left;text-align:center;line-height:1;font-size:50px}.elementor-widget-text-editor .elementor-drop-cap-letter{display:inline-block}<\/style>\t\t\t\t\t\t\t\t\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<section class=\"elementor-section elementor-top-section elementor-element elementor-element-47ba0a3 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"47ba0a3\" 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-bfc7647\" data-id=\"bfc7647\" 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-d1e0def elementor-widget elementor-widget-html\" data-id=\"d1e0def\" 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 biegu -->\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=\"parameters\" class=\"select-container\">\n            <select id=\"paramSelect\"><\/select>\n          <\/div>\n        <\/div>\n        \n        <!-- Krok 3b: Wyb\u00f3r typu sumy opad\u00f3w (tylko dla precipsum) -->\n        <div class=\"section\" id=\"precipTypeSection\" style=\"display: none;\">\n          <h3>Typ sumy:<\/h3>\n          <div id=\"precipTypes\" class=\"buttons precip-type-buttons\"><\/div>\n        <\/div>\n        \n        <!-- Krok 4: 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-9 Wyb\u00f3r modelu<br>\n          &lt; &gt; Zmiana terminu startu<br>\n          \u2190 \u2192 Zmiana godzin prognozy<br>\n          Tab Rozwini\u0119cie parametr\u00f3w<br>\n          A Animacja godzin prognozy<br>\n          C Por\u00f3wnanie modeli\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                <button id=\"compareBtn\" class=\"control-btn\" title=\"Por\u00f3wnaj 4 modele\">\u229e<\/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=\"dateWarning\" class=\"date-warning\"><\/div>\n          <div id=\"images\" class=\"images\"><\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n  \n  <!-- Lightbox -->\n  <div class=\"lightbox\" id=\"lightbox\">\n    <span class=\"lightbox-close\">&times;<\/span>\n    <img id=\"lightboxImg\" src=\"\" alt=\"Powi\u0119kszony obraz\">\n  <\/div>\n  \n  <!-- Modal wyboru modeli do por\u00f3wnania -->\n  <div id=\"compareModal\" class=\"compare-modal\" style=\"display: none;\">\n    <div class=\"compare-modal-content\">\n      <div class=\"compare-modal-header\">\n        <h3>Wybierz modele do por\u00f3wnania (2 lub 4)<\/h3>\n        <button class=\"compare-modal-close\" id=\"compareModalClose\">\u2715<\/button>\n      <\/div>\n      <div class=\"compare-modal-body\">\n        <div id=\"compareModelsList\" class=\"compare-models-list\"><\/div>\n      <\/div>\n      <div class=\"compare-modal-footer\">\n        <button id=\"confirmCompareBtn\" class=\"confirm-btn\">Por\u00f3wnaj<\/button>\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    .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    .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    .right-column {\n      flex: 2;\n      min-width: 400px;\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-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    .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    .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    .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      min-width: 45px; \/* Minimalna szeroko\u015b\u0107 dla r\u00f3wnych przycisk\u00f3w *\/\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    \/* Wyr\u00f3wnanie przycisk\u00f3w godzin\/zakres\u00f3w do r\u00f3wnych kolumn *\/\n    #hours.buttons {\n      display: grid;\n      grid-template-columns: repeat(3, minmax(0, 1fr));\n      gap: 5px;\n    }\n  \n    #hours.buttons button {\n      width: 100%;\n      min-width: 0;\n      text-align: center;\n      font-variant-numeric: tabular-nums;\n    }\n  \n    \/* Wyr\u00f3wnanie przycisk\u00f3w typu sumy *\/\n    #precipTypes.buttons {\n      display: grid;\n      grid-template-columns: repeat(4, minmax(0, 1fr));\n      gap: 8px;\n    }\n  \n    #precipTypes.buttons button {\n      width: 100%;\n      min-width: 0;\n      text-align: center;\n      font-variant-numeric: tabular-nums;\n    }\n    \n  \n    \n    .section.images {\n      min-height: 600px;\n    }\n    \n    #images.images {\n      min-height: 600px;\n    }\n    \n    .date-warning {\n      display: none;\n      text-align: center;\n      padding: 8px 15px;\n      background: rgba(255, 152, 0, 0.1);\n      border: 1px solid rgba(255, 152, 0, 0.3);\n      border-radius: 6px;\n      font-size: 13px;\n      color: #e65100;\n      margin: 10px 0;\n      font-weight: 500;\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    \/* Style dla za\u0142adowanych przycisk\u00f3w *\/\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;\n    }\n    \n    .models-buttons button {\n      width: 100%;\n      text-align: center;\n      margin: 1px 0;\n      padding: 3px 12px;\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;\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;\n      height: auto;\n      white-space: normal;\n      overflow: visible;\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;\n      min-height: 28px;\n      line-height: 1.4;\n      white-space: normal;\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;\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);\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    \/* Lightbox *\/\n    .lightbox {\n      display: none;\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 100%;\n      background: rgba(0,0,0,0.9);\n      z-index: 1000;\n      justify-content: center;\n      align-items: center;\n      cursor: pointer;\n    }\n    \n    .lightbox.active {\n      display: flex;\n    }\n    \n    .lightbox img {\n      max-width: 95%;\n      max-height: 95%;\n      object-fit: contain;\n    }\n    \n    .lightbox-close {\n      position: absolute;\n      top: 20px;\n      right: 30px;\n      color: white;\n      font-size: 40px;\n      cursor: pointer;\n    }\n    \n    \/* Modal wyboru modeli *\/\n    .compare-modal {\n      position: fixed;\n      top: 0;\n      left: 0;\n      width: 100vw;\n      height: 100vh;\n      background: rgba(0, 0, 0, 0.7);\n      z-index: 9999;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n    }\n    \n    .compare-modal-content {\n      background: white;\n      border-radius: 12px;\n      width: 90%;\n      max-width: 500px;\n      max-height: 80vh;\n      display: flex;\n      flex-direction: column;\n      box-shadow: 0 4px 20px rgba(0,0,0,0.3);\n    }\n    \n    .compare-modal-header {\n      display: flex;\n      justify-content: space-between;\n      align-items: center;\n      padding: 20px;\n      border-bottom: 1px solid #ddd;\n    }\n    \n    .compare-modal-header h3 {\n      margin: 0;\n      color: #333;\n    }\n    \n    .compare-modal-close {\n      background: none;\n      border: none;\n      font-size: 1.5em;\n      cursor: pointer;\n      color: #666;\n    }\n    \n    .compare-modal-close:hover {\n      color: #333;\n    }\n    \n    .compare-modal-body {\n      padding: 20px;\n      overflow-y: auto;\n      flex: 1;\n    }\n    \n    .compare-models-list {\n      display: flex;\n      flex-direction: column;\n      gap: 10px;\n    }\n    \n    .compare-model-item {\n      padding: 12px;\n      border: 2px solid #ddd;\n      border-radius: 8px;\n      cursor: pointer;\n      transition: all 0.2s;\n      background: #f9f9f9;\n    }\n    \n    .compare-model-item:hover {\n      border-color: rgba(86, 221, 208, 0.5);\n      background: #f0f8ff;\n    }\n    \n    .compare-model-item.selected {\n      border-color: rgba(86, 221, 208, 1);\n      background: rgba(86, 221, 208, 0.1);\n      font-weight: bold;\n    }\n    \n    .compare-modal-footer {\n      padding: 20px;\n      border-top: 1px solid #ddd;\n      display: flex;\n      justify-content: flex-end;\n    }\n    \n    .confirm-btn {\n      padding: 10px 30px;\n      background: rgba(86, 221, 208, 1);\n      color: white;\n      border: none;\n      border-radius: 8px;\n      font-size: 1em;\n      cursor: pointer;\n      transition: all 0.2s;\n    }\n    \n    .confirm-btn:hover {\n      background: rgba(56, 191, 178, 1);\n      transform: scale(1.05);\n    }\n    \n    .confirm-btn:disabled {\n      background: #ccc;\n      cursor: not-allowed;\n      transform: none;\n    }\n    \n    \/* Ukryj przycisk por\u00f3wnania na urz\u0105dzeniach mobilnych *\/\n    @media (max-width: 768px) {\n      #compareBtn {\n        display: none !important;\n      }\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 {\n        max-width: 100%;\n        width: 100%;\n      }\n    }\n  <\/style>\n  \n  <script>\n  \/\/ ========================================\n  \/\/ KONFIGURACJA PROGNOZ KR\u00d3TKOTERMINOWYCH\n  \/\/ ========================================\n  \/\/ Wersja: 2026-01-08\n  \n  \/\/ Terminy bieg\u00f3w (foldery)\n  const terms = [\"00\", \"06\", \"12\", \"18\"];\n  \n  \/\/ Konfiguracja modeli z ich warunkami brzegowymi\n  const modelsConfig = {\n    \"ALARO_EUROPA\": { \n      boundary: \"ARPEGE\", \n      region: \"EUROPA\",\n      displayName: \"ALARO Europa (72h)\",\n      maxForecastHour: 72\n    },\n    \"AROME_POLSKA\": { \n      boundary: \"ALARO\", \n      region: \"POLSKA\",\n      displayName: \"AROME Polska (30h)\",\n      maxForecastHour: 30\n    },\n    \"COSMO_EUROPA\": { \n      boundary: \"ICON\", \n      region: \"EUROPA\",\n      displayName: \"COSMO Europa (96h)\",\n      maxForecastHour: 96\n    },\n    \"COSMO_POLSKA\": { \n      boundary: \"COSMO7\", \n      region: \"POLSKA\",\n      displayName: \"COSMO Polska (60h)\",\n      maxForecastHour: 60\n    },\n      \"ICONLAM_POLSKA\": { \n      boundary: \"ICON\", \n      region: \"POLSKA\",\n      displayName: \"ICON-LAM Polska (60h)\",\n      maxForecastHour: 60\n    },\n    \"ICON-EU_EUROPA_EU\": {\n      boundary: \"EUROPA\",\n      region: \"EUROPA\",\n      subRegion: \"EU\",\n      displayName: \"ICON-EU Europa (72h)\",\n      maxForecastHour: 72,\n      fileExtension: \"webp\",\n      isPrecipsumOnly: true\n    },\n    \"ICON-EU_EUROPA_PL\": {\n      boundary: \"EUROPA\",\n      region: \"EUROPA\",\n      subRegion: \"PL\",\n      displayName: \"ICON-EU Polska (72h)\",\n      maxForecastHour: 72,\n      fileExtension: \"webp\",\n      isPrecipsumOnly: true\n    },\n    \"IFS_EUROPA_EU\": {\n      boundary: \"EUROPA\",\n      region: \"EUROPA\",\n      subRegion: \"EU\",\n      displayName: \"IFS Europa (72h)\",\n      maxForecastHour: 72,\n      fileExtension: \"webp\",\n      isPrecipsumOnly: true\n    },\n    \"IFS_EUROPA_PL\": {\n      boundary: \"EUROPA\",\n      region: \"EUROPA\",\n      subRegion: \"PL\",\n      displayName: \"IFS Polska (72h)\",\n      maxForecastHour: 72,\n      fileExtension: \"webp\",\n      isPrecipsumOnly: true\n    }\n  };\n  \n  const models = Object.keys(modelsConfig);\n  \n  \/\/ Parametry dost\u0119pne dla wszystkich modeli\n  const parameters = [\n    \/\/ Temperatura\n    \"temp2m\",\n    \"temp2m_sw\",\n    \"tempd\",\n    \"tsurf\",\n    \/\/ Opady\n    \"precip\",\n    \"precipsum\",\n    \"rain\",\n    \"snow\",\n    \/\/ Wilgotno\u015b\u0107\n    \"rhum2m\",\n    \"rhum2m_sw\",\n    \/\/ Wiatr\n    \"wind10m\",\n    \"wgust\",\n    \/\/ Ci\u015bnienie\n    \"pres\",\n    \/\/ Inne\n    \"cloud\"\n  ];\n  \n  \/\/ Parametry specyficzne dla poszczeg\u00f3lnych modeli\n  \/\/ Je\u015bli model nie ma wpisu, u\u017cywa wszystkich parametr\u00f3w z listy 'parameters'\n  const modelSpecificParameters = {\n    \"WRF_POLSKA\": [\n      \/\/ Temperatura\n      \"temp2m\",\n      \"tempch\",\n      \/\/\"temp2m_sw\",\n      \"tempd\",\n      \"tsurf\",\n      \/\/ Opady\n      \"precip\",\n      \"precipdaily\",\n      \"rain\",\n      \"snow\",\n      \/\/ Wilgotno\u015b\u0107\n      \"rhum2m\",\n      \/\/\"rhum2m_sw\",\n      \/\/ Wiatr\n      \"wind10m\",\n      \"wgust\",\n      \/\/ Ci\u015bnienie\n      \"pres\",\n      \/\/ Inne\n      \"cloud\",\n      \"swdown\",\n      \"msoil10\"\n    ],\n    \"ICON-EU_EUROPA_EU\": [\n      \"precipsum\"\n    ],\n    \"ICON-EU_EUROPA_PL\": [\n      \"precipsum\"\n    ],\n    \"IFS_EUROPA_EU\": [\n      \"precipsum\"\n    ],\n    \"IFS_EUROPA_PL\": [\n      \"precipsum\"\n    ]\n  };\n  \n  \/\/ Przyjazne nazwy parametr\u00f3w\n  const parameterDisplayNames = {\n    \"cloud\": \"Zachmurzenie\",\n    \"msoil10\": \"Wilgotno\u015b\u0107 gleby 10cm\",\n    \"precip\": \"Opad ca\u0142kowity\",\n    \"precipdaily\": \"Suma opadu 24h\",\n    \"precipsum\": \"Suma opad\u00f3w\",\n    \"pres\": \"Ci\u015bnienie\",\n    \"rain\": \"Opad deszczu\",\n    \"rhum2m\": \"Wilgotno\u015b\u0107 wzgl\u0119dna 2m\",\n    \"rhum2m_sw\": \"Wilgotno\u015b\u0107 2m (przyjazne barwy)\",\n    \"snow\": \"Opad \u015bniegu\",\n    \"swdown\": \"Promieniowanie s\u0142oneczne\",\n    \"temp2m\": \"Temperatura 2m\",\n    \"temp2m_sw\": \"Temp. 2m (przyjazne barwy)\",\n    \"tempch\": \"Temperatura odczuwalna\",\n    \"tempd\": \"Temp. punktu rosy\",\n    \"tsurf\": \"Temp. powierzchni\",\n    \"wgust\": \"Porywy wiatru\",\n    \"wind10m\": \"Wiatr 10m\"\n  };\n  \n  \/\/ Opisy parametr\u00f3w\n  const parameterDescriptions = {\n    \"cloud\": \"Zachmurzenie ca\u0142kowite wyra\u017cone w procentach pokrycia nieba przez chmury. Warto\u015bci od 0% (bezchmurnie) do 100% (ca\u0142kowite zachmurzenie).\",\n    \"msoil10\": \"Wilgotno\u015b\u0107 gleby na g\u0142\u0119boko\u015bci 10 cm, wyra\u017cona w procentach obj\u0119to\u015bciowych lub kg\/m\u00b2.\",\n    \"precip\": \"Nat\u0119\u017cenie opadu atmosferycznego w danym momencie, wyra\u017cone w mm\/h.\",\n    \"precipdaily\": \"Suma opadu atmosferycznego za okres 24h (od p\u00f3\u0142nocy do p\u00f3\u0142nocy UTC), wyra\u017cona w mm.\",\n    \"precipsum\": \"Suma opadu atmosferycznego za okre\u015blony okres czasu (3h, 6h, 12h lub 24h), wyra\u017cona w mm.\",\n    \"pres\": \"Ci\u015bnienie atmosferyczne zredukowane do poziomu morza, wyra\u017cone w hPa.\",\n    \"rain\": \"Opad deszczu (ciek\u0142y opad atmosferyczny) wyra\u017cony w mm.\",\n    \"rhum2m\": \"Wilgotno\u015b\u0107 wzgl\u0119dna powietrza na wysoko\u015bci 2m nad powierzchni\u0105 gruntu, wyra\u017cona w procentach.\",\n    \"rhum2m_sw\": \"Wilgotno\u015b\u0107 wzgl\u0119dna 2m - mapa ze specjaln\u0105 skal\u0105 barwn\u0105 dostosowan\u0105 dla os\u00f3b z zaburzeniami rozpoznawania barw (daltonizm).\",\n    \"snow\": \"Opad \u015bniegu (sta\u0142y opad atmosferyczny) wyra\u017cony w mm ekwiwalentu wodnego.\",\n    \"swdown\": \"Promieniowanie s\u0142oneczne docieraj\u0105ce do powierzchni ziemi, wyra\u017cone w W\/m\u00b2.\",\n    \"temp2m\": \"Temperatura powietrza na wysoko\u015bci 2m nad powierzchni\u0105 gruntu, wyra\u017cona w \u00b0C.\",\n    \"temp2m_sw\": \"Temperatura 2m - mapa ze specjaln\u0105 skal\u0105 barwn\u0105 dostosowan\u0105 dla os\u00f3b z zaburzeniami rozpoznawania barw (daltonizm).\",\n    \"tempch\": \"Temperatura odczuwalna uwzgl\u0119dniaj\u0105ca wp\u0142yw wiatru i wilgotno\u015bci (wind chill \/ heat index).\",\n    \"tempd\": \"Temperatura punktu rosy na wysoko\u015bci 2m, czyli temperatura do kt\u00f3rej musi by\u0107 sch\u0142odzone powietrze aby nast\u0105pi\u0142a kondensacja.\",\n    \"tsurf\": \"Temperatura powierzchni ziemi lub wody.\",\n    \"wgust\": \"Maksymalna chwilowa pr\u0119dko\u015b\u0107 wiatru (poryw) wyra\u017cona w km\/h lub m\/s.\",\n    \"wind10m\": \"Pr\u0119dko\u015b\u0107 i kierunek wiatru na wysoko\u015bci 10m nad powierzchni\u0105 gruntu.\"\n  };\n  \n  \/\/ Konfiguracja sum opad\u00f3w dla r\u00f3\u017cnych termin\u00f3w biegu\n  \/\/ Funkcja generuj\u0105ca zakresy precipsum dla danego modelu\n  function getPrecipsumRangesForModel(model) {\n    const maxHour = modelsConfig[model] ? modelsConfig[model].maxForecastHour : 72;\n    \n    const ranges = {\n      \"R03\": [],\n      \"R06\": [],\n      \"R12\": [],\n      \"R24\": []\n    };\n    \n    \/\/ Generuj zakresy R03 (co 3h)\n    for (let i = 0; i < maxHour; i += 3) {\n      const start = i;\n      const end = Math.min(i + 3, maxHour);\n      if (start < maxHour) {\n        ranges.R03.push(String(start).padStart(2, \"0\") + \"-\" + String(end).padStart(2, \"0\"));\n      }\n    }\n    \n    \/\/ Generuj zakresy R06 (co 6h)\n    for (let i = 0; i < maxHour; i += 6) {\n      const start = i;\n      const end = Math.min(i + 6, maxHour);\n      if (start < maxHour) {\n        ranges.R06.push(String(start).padStart(2, \"0\") + \"-\" + String(end).padStart(2, \"0\"));\n      }\n    }\n    \n    \/\/ Generuj zakresy R12 (co 12h)\n    for (let i = 0; i < maxHour; i += 12) {\n      const start = i;\n      const end = Math.min(i + 12, maxHour);\n      if (start < maxHour) {\n        ranges.R12.push(String(start).padStart(2, \"0\") + \"-\" + String(end).padStart(2, \"0\"));\n      }\n    }\n    \n    \/\/ Generuj zakresy R24 (co 24h)\n    for (let i = 0; i < maxHour; i += 24) {\n      const start = i;\n      const end = Math.min(i + 24, maxHour);\n      if (start < maxHour) {\n        ranges.R24.push(String(start).padStart(2, \"0\") + \"-\" + String(end).padStart(2, \"0\"));\n      }\n    }\n    \n    return ranges;\n  }\n  \n  function isPrecipsumOnlyModel(model) {\n    return !!(modelsConfig[model] && modelsConfig[model].isPrecipsumOnly);\n  }\n  \n  function getAvailablePrecipsumTypesForModel(model) {\n    return [\"R03\", \"R06\", \"R12\", \"R24\"];\n  }\n  \n  function getFilteredPrecipsumRangesForModel(model, precipType) {\n    const modelToUse = model || \"ALARO_EUROPA\";\n    const modelRanges = getPrecipsumRangesForModel(modelToUse);\n    let ranges = (modelRanges[precipType] || []).slice();\n  \n    \/\/ AROME 30h: dla R12 i R24 usu\u0144 ko\u0144cowy zakres do 30h (brak pasuj\u0105cego pliku).\n    \/\/ Dla R06 zakres do 30h jest poprawny i zostaje.\n    if (modelToUse === \"AROME_POLSKA\" && (precipType === \"R12\" || precipType === \"R24\")) {\n      ranges = ranges.filter(function(range) {\n        return range.split(\"-\")[1] !== \"30\";\n      });\n    }\n  \n    \/\/ ICONLAM\/COSMO Polska: dla sumy 24h usu\u0144 ko\u0144cowy zakres do 60h.\n    if ((modelToUse === \"ICONLAM_POLSKA\" || modelToUse === \"COSMO_POLSKA\") && precipType === \"R24\") {\n      ranges = ranges.filter(function(range) {\n        return range.split(\"-\")[1] !== \"60\";\n      });\n    }\n  \n    return ranges;\n  }\n  \n  \/\/ Backup - stara konfiguracja sum opad\u00f3w (zachowana dla referencji)\n  const precipsumRanges = {\n    \"00\": {\n      \"R06\": [\"00-06\", \"06-12\", \"12-18\", \"18-24\", \"24-30\", \"30-36\", \"36-42\", \"42-48\", \"48-54\", \"54-60\", \"60-66\", \"66-72\"],\n      \"R12\": [\"00-12\", \"12-24\", \"24-36\", \"36-48\", \"48-60\", \"60-72\"],\n      \"R24\": [\"00-24\", \"24-48\", \"48-72\"]\n    },\n    \"06\": {\n      \"R06\": [\"00-06\", \"06-12\", \"12-18\", \"18-24\", \"24-30\", \"30-36\", \"36-42\", \"42-48\", \"48-54\", \"54-60\", \"60-66\", \"66-72\"],\n      \"R12\": [\"00-12\", \"12-24\", \"24-36\", \"36-48\", \"48-60\", \"60-72\"],\n      \"R24\": [\"00-24\", \"24-48\", \"48-72\"]\n    },\n    \"12\": {\n      \"R06\": [\"00-06\", \"06-12\", \"12-18\", \"18-24\", \"24-30\", \"30-36\", \"36-42\", \"42-48\", \"48-54\", \"54-60\", \"60-66\", \"66-72\"],\n      \"R12\": [\"00-12\", \"12-24\", \"24-36\", \"36-48\", \"48-60\", \"60-72\"],\n      \"R24\": [\"00-24\", \"24-48\", \"48-72\"]\n    },\n    \"18\": {\n      \"R06\": [\"00-06\", \"06-12\", \"12-18\", \"18-24\", \"24-30\", \"30-36\", \"36-42\", \"42-48\", \"48-54\", \"54-60\", \"60-66\", \"66-72\"],\n      \"R12\": [\"00-12\", \"12-24\", \"24-36\", \"36-48\", \"48-60\", \"60-72\"],\n      \"R24\": [\"00-24\", \"24-48\", \"48-72\"]\n    }\n  };\n  \n  \/\/ Funkcja zwracaj\u0105ca dost\u0119pne parametry dla danego modelu\n  function getAvailableParametersForModel(model) {\n    if (modelSpecificParameters[model]) {\n      return modelSpecificParameters[model];\n    }\n    return parameters;\n  }\n  \n  function getMappedParameterForModel(previousParam, targetModel) {\n    const availableParams = getAvailableParametersForModel(targetModel);\n  \n    if (!previousParam) {\n      return null;\n    }\n  \n    \/\/ 1) Najpierw spr\u00f3buj zachowa\u0107 dok\u0142adnie ten sam parametr.\n    if (availableParams.includes(previousParam)) {\n      return previousParam;\n    }\n  \n    \/\/ 2) Mapowanie nazw\/odpowiednik\u00f3w mi\u0119dzy modelami.\n    const parameterAliases = {\n      \"precipsum\": [\"precipdaily\", \"precip\"],\n      \"precipdaily\": [\"precipsum\", \"precip\"],\n      \"precip\": [\"precipsum\", \"precipdaily\"],\n      \"temp2m_sw\": [\"temp2m\"],\n      \"temp2m\": [\"temp2m_sw\"],\n      \"rhum2m_sw\": [\"rhum2m\"],\n      \"rhum2m\": [\"rhum2m_sw\"]\n    };\n  \n    const aliases = parameterAliases[previousParam] || [];\n    for (let i = 0; i < aliases.length; i++) {\n      if (availableParams.includes(aliases[i])) {\n        return aliases[i];\n      }\n    }\n  \n    return null;\n  }\n  \n  \/\/ Godziny prognozy dla zwyk\u0142ych parametr\u00f3w (co 3h od 00 do maxForecastHour dla danego modelu)\n  \/\/ Funkcja generuj\u0105ca godziny prognozy dla danego modelu\n  function getForecastHoursForModel(model, param) {\n    const maxHour = modelsConfig[model] ? modelsConfig[model].maxForecastHour : 72;\n    const hours = [];\n    \n    \/\/ Dla precipdaily - tylko pe\u0142ne dni (co 24h od 24h)\n    if (param === \"precipdaily\") {\n      for (let i = 24; i <= maxHour; i += 24) {\n        hours.push(String(i).padStart(2, \"0\"));\n      }\n      return hours;\n    }\n    \n    \/\/ Dla WRF i parametru precip - godziny co 1h\n    if (model === \"WRF_POLSKA\" && param === \"precip\") {\n      for (let i = 1; i <= maxHour; i += 1) {\n        hours.push(String(i).padStart(2, \"0\"));\n      }\n      return hours;\n    }\n    \n    \/\/ Dla parametr\u00f3w opadowych (rain, snow, precip) i poryw\u00f3w wiatru pomijamy godzin\u0119 00h\n    const isRainParameter = param && (param === 'rain' || param === 'snow' || param === 'precip' || param === 'wgust');\n    const startHour = isRainParameter ? 3 : 0;\n    \n    for (let i = startHour; i <= maxHour; i += 3) {\n      hours.push(String(i).padStart(2, \"0\"));\n    }\n    return hours;\n  }\n  \n  \/\/ Funkcja do znajdowania maksymalnego zakresu godzin spo\u015br\u00f3d wybranych modeli (dla trybu por\u00f3wnania)\n  function getMaxForecastHoursForCompareModels(param) {\n    if (!isCompareMode || selectedModelsForCompare.length === 0) {\n      return selectedModel ? getForecastHoursForModel(selectedModel, param) : getForecastHoursForModel(\"ALARO_EUROPA\", param);\n    }\n    \n    let maxHour = 0;\n    selectedModelsForCompare.forEach(function(modelKey) {\n      const modelMaxHour = modelsConfig[modelKey] ? modelsConfig[modelKey].maxForecastHour : 72;\n      if (modelMaxHour > maxHour) {\n        maxHour = modelMaxHour;\n      }\n    });\n    \n    const hours = [];\n    \n    \/\/ Dla precipdaily - tylko pe\u0142ne dni (co 24h od 24h)\n    if (param === \"precipdaily\") {\n      for (let i = 24; i <= maxHour; i += 24) {\n        hours.push(String(i).padStart(2, \"0\"));\n      }\n      return hours;\n    }\n    \n    \/\/ Dla parametr\u00f3w opadowych (rain, snow, precip) i poryw\u00f3w wiatru pomijamy godzin\u0119 00h\n    \/\/ W trybie por\u00f3wnania u\u017cywamy standardowych godzin co 3h (bez specjalnych przypadk\u00f3w WRF)\n    const isRainParameter = param && (param === 'rain' || param === 'snow' || param === 'precip' || param === 'wgust');\n    const startHour = isRainParameter ? 3 : 0;\n    \n    for (let i = startHour; i <= maxHour; i += 3) {\n      hours.push(String(i).padStart(2, \"0\"));\n    }\n    return hours;\n  }\n  \n  \/\/ ========================================\n  \/\/ ZMIENNE STANU\n  \/\/ ========================================\n  let selectedTerm = null;\n  let selectedModel = null;\n  let selectedParam = null;\n  let selectedHour = null;\n  let selectedPrecipsumType = \"R06\"; \/\/ Domy\u015blny typ sumy opad\u00f3w (mo\u017cna zmieni\u0107 przyciskami)\n  let selectedPrecipsumRange = null;\n  \n  \/\/ Wersja konfiguracji - inkrementowana przy ka\u017cdej zmianie modelu\/terminu\/parametru\n  \/\/ U\u017cywana do anulowania starych \u017c\u0105da\u0144\n  let configVersion = 0;\n  \n  \/\/ Cache obrazk\u00f3w\n  const imageCache = {};\n  let preloadedImages = new Set();\n  const loadedHours = {}; \/\/ \u015aledzi za\u0142adowane godziny dla danej konfiguracji\n  \n  \/\/ Bazowa data dla prognozy - obiekt przechowuj\u0105cy daty dla ka\u017cdej konfiguracji\n  let forecastBaseDates = {}; \/\/ Klucz: configKey, warto\u015b\u0107: YYYY-MM-DD\n  \n  \/\/ Zmienne dla trybu por\u00f3wnania\n  var isCompareMode = false;\n  var selectedModelsForCompare = [];\n  let isCheckingForecastAvailability = false;\n  \n  \/\/ Zmienne do obs\u0142ugi kolejki \u0142adowania obraz\u00f3w\n  let imageLoadQueue = [];\n  let isLoadingImages = false;\n  const MAX_CONCURRENT_LOADS = 5; \/\/ Maksymalna liczba jednocze\u015bnie \u0142adowanych obraz\u00f3w\n  let currentLoadingImages = []; \/\/ Aktualnie \u0142adowane obrazy\n  \n  \/\/ ========================================\n  \/\/ FUNKCJE POMOCNICZE\n  \/\/ ========================================\n  \n  function getDateString(daysBack) {\n    daysBack = daysBack || 0;\n    const now = new Date();\n    now.setDate(now.getDate() - daysBack);\n    const year = now.getFullYear();\n    const month = String(now.getMonth() + 1).padStart(2, \"0\");\n    const day = String(now.getDate()).padStart(2, \"0\");\n    return year + \"-\" + month + \"-\" + day;\n  }\n  \n  function getCurrentDate() {\n    return getDateString(0);\n  }\n  \n  function generateImagePath(model, term, param, hour, dateString) {\n    const config = modelsConfig[model];\n    const boundary = config.boundary;\n    const region = config.region;\n    const baseDate = dateString || getCurrentDate();\n    const fileExtension = config.fileExtension || \"jpg\";\n    \n    \/\/ Dla modeli ICON-EU i IFS z precipsum - specjalny format\n    if (param === \"precipsum\" && config.isPrecipsumOnly && selectedPrecipsumRange) {\n      \/\/ Format: ICON-EU_EUROPA_PRECIP_EU_R03_00z_00-03_2026-02-19_03_00_00.webp\n      const modelName = model.split(\"_\")[0]; \/\/ ICON-EU lub IFS\n      const subRegion = config.subRegion; \/\/ EU lub PL\n      \n      \/\/ Parsuj zakres (np. \"00-03\" -> start=0, end=3)\n      const rangeParts = selectedPrecipsumRange.split(\"-\");\n      const rangeStart = parseInt(rangeParts[0]);\n      const rangeEnd = parseInt(rangeParts[1]);\n      \n      \/\/ Oblicz rzeczywist\u0105 dat\u0119 i czas ko\u0144ca przedzia\u0142u\n      const termHour = parseInt(term);\n      const totalEndHour = termHour + rangeEnd;\n      \n      \/\/ Oblicz offset dni i godzin\u0119 ko\u0144cow\u0105\n      const daysOffset = Math.floor(totalEndHour \/ 24);\n      const endHour = totalEndHour % 24;\n      \n      \/\/ Oblicz dat\u0119 ko\u0144ca przedzia\u0142u\n      const dateParts = baseDate.split(\"-\");\n      const baseYear = parseInt(dateParts[0]);\n      const baseMonth = parseInt(dateParts[1]) - 1;\n      const baseDay = parseInt(dateParts[2]);\n      const baseTimestamp = Date.UTC(baseYear, baseMonth, baseDay);\n      const endDateTime = new Date(baseTimestamp);\n      endDateTime.setUTCDate(endDateTime.getUTCDate() + daysOffset);\n      \n      const endYear = endDateTime.getUTCFullYear();\n      const endMonth = String(endDateTime.getUTCMonth() + 1).padStart(2, \"0\");\n      const endDay = String(endDateTime.getUTCDate()).padStart(2, \"0\");\n      const endDate = endYear + \"-\" + endMonth + \"-\" + endDay;\n      const endTime = String(endHour).padStart(2, \"0\") + \"_00_00\";\n      \n      const fileName = modelName + \"_EUROPA_PRECIP_\" + subRegion + \"_\" + selectedPrecipsumType + \"_\" + term + \"z_\" + selectedPrecipsumRange + \"_\" + endDate + \"_\" + endTime + \".\" + fileExtension;\n      return \"\/wp-content\/uploads\/production\/\" + term + \"\/\" + fileName;\n    }\n    \n    \/\/ Dla precipsum (inne modele) mamy specjalny format\n    if (param === \"precipsum\" && selectedPrecipsumRange) {\n      \/\/ Format: MODEL_precipsum_BOUNDARY_R06_00z_00-06_2026-04-03_06_00_00.jpg\n      const rangeParts = selectedPrecipsumRange.split(\"-\");\n      const rangeEnd = parseInt(rangeParts[1], 10);\n      const termHour = parseInt(term, 10);\n      const totalEndHour = termHour + rangeEnd;\n  \n      const daysOffset = Math.floor(totalEndHour \/ 24);\n      const endHour = totalEndHour % 24;\n  \n      const dateParts = baseDate.split(\"-\");\n      const baseYear = parseInt(dateParts[0], 10);\n      const baseMonth = parseInt(dateParts[1], 10) - 1;\n      const baseDay = parseInt(dateParts[2], 10);\n      const baseTimestamp = Date.UTC(baseYear, baseMonth, baseDay);\n      const endDateTime = new Date(baseTimestamp);\n      endDateTime.setUTCDate(endDateTime.getUTCDate() + daysOffset);\n  \n      const endYear = endDateTime.getUTCFullYear();\n      const endMonth = String(endDateTime.getUTCMonth() + 1).padStart(2, \"0\");\n      const endDay = String(endDateTime.getUTCDate()).padStart(2, \"0\");\n      const endDate = endYear + \"-\" + endMonth + \"-\" + endDay;\n      const endTime = String(endHour).padStart(2, \"0\") + \"_00_00\";\n  \n      const fileName = model + \"_\" + param + \"_\" + boundary + \"_\" + selectedPrecipsumType + \"_\" + term + \"z_\" + selectedPrecipsumRange + \"_\" + endDate + \"_\" + endTime + \".\" + fileExtension;\n      return \"\/wp-content\/uploads\/production\/\" + term + \"\/\" + fileName;\n    }\n    \n    \/\/ Format standardowy - oblicz rzeczywist\u0105 godzin\u0119 UTC\n    \/\/ Godzina prognozy to offset od terminu biegu\n    const termHour = parseInt(term); \/\/ 00, 06, 12, 18\n    const forecastHour = parseInt(hour); \/\/ 00, 03, 06, 09...\n    const totalHour = termHour + forecastHour; \/\/ np. 06 + 18 = 24\n    \n    \/\/ Oblicz ile dni do przodu i jak\u0105 godzin\u0105 UTC\n    const daysOffset = Math.floor(totalHour \/ 24);\n    const fileHour = totalHour % 24;\n    \n    \/\/ Oblicz dat\u0119 pliku - u\u017cywamy bazowej daty + offset dni\n    \/\/ Parsuj dat\u0119 bezpiecznie (format: YYYY-MM-DD) z u\u017cyciem UTC\n    const dateParts = baseDate.split(\"-\");\n    const baseYear = parseInt(dateParts[0]);\n    const baseMonth = parseInt(dateParts[1]) - 1; \/\/ Miesi\u0105ce 0-11 w JS\n    const baseDay = parseInt(dateParts[2]);\n    \n    \/\/ U\u017cyj Date.UTC aby unikn\u0105\u0107 problem\u00f3w ze strefami czasowymi\n    const baseTimestamp = Date.UTC(baseYear, baseMonth, baseDay);\n    const baseDateTime = new Date(baseTimestamp);\n    \n    \/\/ Dodaj offset dni\n    baseDateTime.setUTCDate(baseDateTime.getUTCDate() + daysOffset);\n    \n    const year = baseDateTime.getUTCFullYear();\n    const month = String(baseDateTime.getUTCMonth() + 1).padStart(2, \"0\");\n    const day = String(baseDateTime.getUTCDate()).padStart(2, \"0\");\n    const fileDate = year + \"-\" + month + \"-\" + day;\n    \n    const formattedHour = String(fileHour).padStart(2, \"0\");\n    \n    \/\/ Specjalny format dla WRF - WRF_POLSKA_PARAM_ICON-EU_ML_00z_DATA_GODZINA_00_00.webp\n    if (config.useRegionInFilename) {\n      const modelName = model.split(\"_\")[0]; \/\/ WRF\n      \n      \/\/ Mapowanie nazw parametr\u00f3w dla WRF\n      const wrfParamMapping = {\n        \"precip\": \"PRECIP\",\n        \"wgust\": \"WIND_GUST\",\n        \"wind10m\": \"WIND10M\",\n        \"temp2m\": \"TEMP2M\",\n        \"rhum2m\": \"RHUM2M\",\n        \"msoil10\": \"MSOIL10\",\n        \"tempch\": \"TEMPCH\",\n        \"tempd\": \"TEMPD\",\n        \"tsurf\": \"TSURF\",\n        \"swdown\": \"SWDOWN\",\n        \"precipdaily\": \"PRECIP_DAILY\"\n      };\n      \n      const paramUpper = wrfParamMapping[param] || param.toUpperCase();\n      let fileName;\n      if (param === \"precipdaily\") {\n        \/\/ Dla precipdaily: data z offsetu dni, ale zawsze godzina 00_00_00 w nazwie pliku\n        \/\/ fileDate jest ju\u017c obliczony poprawnie (baseDate + daysOffset)\n        fileName = modelName + \"_\" + region + \"_\" + paramUpper + \"_\" + boundary + \"_\" + term + \"z_\" + fileDate + \"_00_00_00.\" + fileExtension;\n      } else {\n        fileName = modelName + \"_\" + region + \"_\" + paramUpper + \"_\" + boundary + \"_\" + term + \"z_\" + fileDate + \"_\" + formattedHour + \"_00_00.\" + fileExtension;\n      }\n      return \"\/wp-content\/uploads\/production\/\" + term + \"\/\" + fileName;\n    }\n    \n    \/\/ Format standardowy dla innych modeli\n    const fileName = model + \"_\" + param + \"_\" + boundary + \"_\" + term + \"z_\" + fileDate + \"_\" + formattedHour + \"_00_00.\" + fileExtension;\n    return \"\/wp-content\/uploads\/production\/\" + term + \"\/\" + fileName;\n  }\n  \n  \/\/ Funkcja do sprawdzenia dost\u0119pno\u015bci prognozy dla danej konfiguracji\n  \/\/ Sprawdza po dacie modyfikacji pliku (HTTP Last-Modified header)\n  function checkForecastAvailability(model, term, param, onComplete) {\n    const configKey = model + \"_\" + term + \"_\" + param;\n    \n    \/\/ Je\u015bli ju\u017c sprawdzali\u015bmy t\u0119 konfiguracj\u0119, u\u017cyj zapisanej daty\n    if (forecastBaseDates[configKey]) {\n      onComplete(forecastBaseDates[configKey]);\n      return;\n    }\n    \n    \/\/ Sprawd\u017a pierwsz\u0105 godzin\u0119 prognozy\n    \/\/ Dla termin\u00f3w 00, 06, 12: u\u017cywamy godziny 03, poniewa\u017c godzina 00 mo\u017ce by\u0107 offsetem z poprzedniego biegu\n    \/\/ Dla terminu 18: u\u017cywamy godziny 00\n    \/\/ Dla WRF u\u017cywamy w\u0142a\u015bciwej pierwszej dost\u0119pnej godziny\n    let testHour = \"00\";\n    if (param === \"precipsum\") {\n      testHour = null;\n    } else if (param === \"precipdaily\") {\n      testHour = \"24\"; \/\/ Pierwsza dost\u0119pna godzina dla precipdaily\n    } else if (term === \"00\" || term === \"06\" || term === \"12\") {\n      \/\/ Dla WRF u\u017cywaj w\u0142a\u015bciwej pierwszej godziny w zale\u017cno\u015bci od parametru\n      if (model === \"WRF_POLSKA\") {\n        if (param === \"precip\") {\n          testHour = \"01\"; \/\/ WRF precip zaczyna od godziny 01\n        } else {\n          \/\/ Dla innych parametr\u00f3w WRF - sprawd\u017a czy to parametr opadowy\/porywowy\n          const isRainParameter = param && (param === 'rain' || param === 'snow' || param === 'wgust');\n          testHour = isRainParameter ? \"03\" : \"00\";\n        }\n      } else {\n        testHour = \"03\"; \/\/ Standardowo dla innych modeli\n      }\n    }\n    \n    \/\/ Dla terminu 18z pliki s\u0105 gotowe nast\u0119pnego dnia, wi\u0119c szukamy wczoraj\/przedwczoraj\n    \/\/ Dla termin\u00f3w 00, 06, 12 szukamy dzisiaj\/wczoraj\n    \/\/ WYJ\u0104TEK: Dla WRF terminu 12z u\u017cywamy tej samej logiki co dla terminu 18z\n    if (term === \"18\" || (model === \"WRF_POLSKA\" && term === \"12\")) {\n      \/\/ Termin 18z lub WRF 12z: najpierw wczoraj, potem przedwczoraj\n      const yesterdayPath = generateImagePath(model, term, param, testHour, getDateString(1));\n      \n      fetch(yesterdayPath, { method: 'HEAD' })\n        .then(function(response) {\n          if (!response.ok) {\n            throw new Error('File not found');\n          }\n          \n          \/\/ Sprawd\u017a dat\u0119 modyfikacji pliku\n          const lastModified = response.headers.get('Last-Modified');\n          \n          if (lastModified) {\n            const fileDate = new Date(lastModified);\n            const fileDateStr = fileDate.getFullYear() + \"-\" + \n                               String(fileDate.getMonth() + 1).padStart(2, \"0\") + \"-\" + \n                               String(fileDate.getDate()).padStart(2, \"0\");\n            \n            \/\/ Dla terminu 18z lub WRF 12z, pliki s\u0105 produkowane DZISIAJ (po godzinie biegu z wczoraj + czas produkcji)\n            \/\/ Wi\u0119c sprawdzamy czy plik powsta\u0142 DZISIAJ (co oznacza bieg z wczoraj)\n            const todayDateStr = getDateString(0);\n            const yesterdayDateStr = getDateString(1);\n            \n            if (fileDateStr === todayDateStr || fileDateStr === yesterdayDateStr) {\n              \/\/ Plik stworzony dzisiaj lub wczoraj = bieg z wczoraj (\u015bwie\u017cy)\n              forecastBaseDates[configKey] = getDateString(1);\n              onComplete(getDateString(1));\n            } else {\n              \/\/ Plik jest starszy - spr\u00f3buj przedwczoraj\n              tryDayBeforeYesterday();\n            }\n          } else {\n            \/\/ Brak headera, u\u017cyj wczoraj jako domy\u015blne\n            forecastBaseDates[configKey] = getDateString(1);\n            onComplete(getDateString(1));\n          }\n        })\n        .catch(function() {\n          \/\/ Nie ma wczoraj, spr\u00f3buj przedwczoraj\n          tryDayBeforeYesterday();\n        });\n      \n      function tryDayBeforeYesterday() {\n        const dayBeforePath = generateImagePath(model, term, param, testHour, getDateString(2));\n        \n        fetch(dayBeforePath, { method: 'HEAD' })\n          .then(function(response) {\n            if (response.ok) {\n              forecastBaseDates[configKey] = getDateString(2);\n              onComplete(getDateString(2));\n            } else {\n              forecastBaseDates[configKey] = null;\n              onComplete(null);\n            }\n          })\n          .catch(function() {\n            forecastBaseDates[configKey] = null;\n            onComplete(null);\n          });\n      }\n    } else {\n      \/\/ Terminy 00, 06, 12: najpierw dzisiaj, potem wczoraj\n      const todayPath = generateImagePath(model, term, param, testHour, getDateString(0));\n      \n      fetch(todayPath, { method: 'HEAD' })\n        .then(function(response) {\n          if (!response.ok) {\n            throw new Error('File not found');\n          }\n          \n          \/\/ Sprawd\u017a dat\u0119 modyfikacji pliku\n          const lastModified = response.headers.get('Last-Modified');\n          \n          if (lastModified) {\n            const fileDate = new Date(lastModified);\n            const fileDateStr = fileDate.getFullYear() + \"-\" + \n                               String(fileDate.getMonth() + 1).padStart(2, \"0\") + \"-\" + \n                               String(fileDate.getDate()).padStart(2, \"0\");\n            \n            \/\/ Sprawd\u017a czy plik zosta\u0142 stworzony DZISIAJ\n            \/\/ Je\u015bli tak, to jest bieg z dzisiaj\n            \/\/ Je\u015bli nie, to plik z dzisiejsz\u0105 dat\u0105 walidacji jest cz\u0119\u015bci\u0105 wczorajszego biegu (offset >24h)\n            const todayDateStr = getDateString(0);\n            \n            if (fileDateStr === todayDateStr) {\n              \/\/ Plik stworzony dzisiaj = bieg z dzisiaj\n              forecastBaseDates[configKey] = getDateString(0);\n              onComplete(getDateString(0));\n            } else {\n              \/\/ Plik stworzony wczoraj lub wcze\u015bniej = to offset z wczorajszego biegu\n              \/\/ Szukaj wczorajszej bazy\n              tryYesterdayForecast();\n            }\n          } else {\n            \/\/ Brak headera, sprawd\u017a czas - je\u015bli jeste\u015bmy przed terminem, u\u017cyj wczoraj\n            const now = new Date();\n            const termHour = parseInt(term);\n            if (now.getHours() < termHour + 2) {\n              \/\/ Jeste\u015bmy przed terminem + 2h bufor - prawdopodobnie nie ma jeszcze dzisiejszego biegu\n              tryYesterdayForecast();\n            } else {\n              forecastBaseDates[configKey] = getDateString(0);\n              onComplete(getDateString(0));\n            }\n          }\n        })\n        .catch(function() {\n          \/\/ Nie ma dzisiaj, spr\u00f3buj wczoraj\n          tryYesterdayForecast();\n        });\n      \n      function tryYesterdayForecast() {\n        const yesterdayPath = generateImagePath(model, term, param, testHour, getDateString(1));\n        \n        fetch(yesterdayPath, { method: 'HEAD' })\n          .then(function(response) {\n            if (response.ok) {\n              forecastBaseDates[configKey] = getDateString(1);\n              onComplete(getDateString(1));\n            } else {\n              forecastBaseDates[configKey] = null;\n              onComplete(null);\n            }\n          })\n          .catch(function() {\n            forecastBaseDates[configKey] = null;\n            onComplete(null);\n          });\n      }\n    }\n  }\n  \n  function tryLoadImageWithFallback(model, term, param, hour, onSuccess, onError) {\n    \/\/ U\u017cyj ustalonej daty bazowej dla ca\u0142ej prognozy\n    const configKey = model + \"_\" + term + \"_\" + param;\n    const baseDate = forecastBaseDates[configKey];\n    \n    if (baseDate) {\n      const imagePath = generateImagePath(model, term, param, hour, baseDate);\n      const img = new Image();\n      \n      img.onload = function() {\n        onSuccess(img, imagePath, baseDate);\n      };\n      \n      img.onerror = function() {\n        onError(imagePath);\n      };\n      \n      img.src = imagePath;\n      img.alt = \"Prognoza \" + param + \" - \" + model + \" - \" + term + \"z\";\n    } else {\n      \/\/ Brak dost\u0119pnej prognozy\n      onError(\"Brak dost\u0119pnych danych prognozy\");\n    }\n  }\n  \n  function renderButtons(containerId, items, onClick, activeItem, displayNames) {\n    const container = document.getElementById(containerId);\n    container.innerHTML = \"\";\n    \n    items.forEach(function(item) {\n      const btn = document.createElement(\"button\");\n      const displayText = displayNames && displayNames[item] ? displayNames[item] : item;\n      btn.textContent = displayText;\n      btn.dataset.value = item;\n      \n      \/\/ W trybie por\u00f3wnania pod\u015bwietl wybrane modele dla kontenera \"models\"\n      if (containerId === \"models\" && isCompareMode && selectedModelsForCompare.indexOf(item) !== -1) {\n        btn.classList.add(\"active\");\n      } else if (activeItem && item === activeItem) {\n        btn.classList.add(\"active\");\n      }\n      \n      btn.addEventListener(\"click\", function() {\n        \/\/ W trybie por\u00f3wnania nie zmieniaj aktywnych przycisk\u00f3w modeli\n        if (!(containerId === \"models\" && isCompareMode)) {\n          container.querySelectorAll(\"button\").forEach(function(b) { b.classList.remove(\"active\"); });\n          btn.classList.add(\"active\");\n        }\n        onClick(item);\n      });\n      \n      container.appendChild(btn);\n    });\n  }\n  \n  function renderSelect(selectId, items, onChange, displayNames) {\n    const select = document.getElementById(selectId);\n    select.innerHTML = \"\";\n    \n    items.forEach(function(item) {\n      const option = document.createElement(\"option\");\n      option.value = item;\n      option.textContent = displayNames && displayNames[item] ? displayNames[item] : item;\n      select.appendChild(option);\n    });\n    \n    \/\/ Nadpisuj handler zamiast dok\u0142ada\u0107 kolejne listenery przy ka\u017cdym prze\u0142adowaniu listy.\n    select.onchange = function(e) {\n      onChange(e.target.value);\n    };\n  }\n  \n  function setActiveButton(containerId, value) {\n    const container = document.getElementById(containerId);\n    container.querySelectorAll(\"button\").forEach(function(btn) {\n      if (btn.dataset.value === value) {\n        btn.classList.add(\"active\");\n      } else {\n        btn.classList.remove(\"active\");\n      }\n    });\n  }\n  \n  function getPrecipsumRangeLabel(model, range) {\n    if (!range) return \"\";\n    return range;\n  }\n  \n  function getPrecipsumHourValueText(model, range) {\n    return getPrecipsumRangeLabel(model, range);\n  }\n  \n  function isAccumulatedRangeMode(model, param) {\n    return param === \"precipsum\" || (model === \"WRF_POLSKA\" && param === \"precipdaily\");\n  }\n  \n  function getAccumulatedRangeLabel(endHour, spanHours) {\n    const end = parseInt(endHour, 10);\n    if (isNaN(end)) {\n      return String(endHour);\n    }\n    const start = Math.max(0, end - spanHours);\n    return String(start).padStart(2, \"0\") + \"-\" + String(end).padStart(2, \"0\");\n  }\n  \n  function getForecastHourDisplayText(model, param, hour) {\n    if (model === \"WRF_POLSKA\" && param === \"precipdaily\") {\n      return getAccumulatedRangeLabel(hour, 24);\n    }\n    return String(hour).padStart(2, \"0\") + \"h\";\n  }\n  \n  \/\/ ========================================\n  \/\/ FUNKCJE G\u0141\u00d3WNE\n  \/\/ ========================================\n  \n  function initTerms() {\n    renderButtons(\"terms\", terms, function(term) {\n      \/\/ Resetuj stan przy zmianie terminu\n      if (selectedTerm !== term) {\n        resetStateForModelChange();\n      }\n      \n      selectedTerm = term;\n      if (selectedParam === \"precipsum\") {\n        renderPrecipsumRanges();\n      } else {\n        renderForecastHours();\n      }\n      updateCurrentConfig();\n      if (selectedModel && selectedParam) {\n        if (selectedParam === \"precipsum\" && selectedPrecipsumRange) {\n          showImages();\n        } else if (selectedParam !== \"precipsum\" && selectedHour) {\n          showImages();\n        }\n      }\n      preloadForecastImages();\n    });\n    \n    \/\/ Domy\u015blnie wybierz termin 00\n    selectedTerm = \"00\";\n    setActiveButton(\"terms\", \"00\");\n  }\n  \n  function initModels() {\n    const modelDisplayNames = {};\n    models.forEach(function(m) {\n      modelDisplayNames[m] = modelsConfig[m].displayName;\n    });\n    \n    renderButtons(\"models\", models, function(model) {\n      \/\/ Resetuj stan przy zmianie modelu\n      if (selectedModel !== model) {\n        resetStateForModelChange();\n      }\n      \n      selectedModel = model;\n      \n      \/\/ Prze\u0142aduj parametry dla nowego modelu (mog\u0105 by\u0107 inne)\n      initParameters();\n      \n      updateCurrentConfig();\n      if (selectedParam && selectedHour) {\n        showImages();\n      }\n      preloadForecastImages();\n    }, null, modelDisplayNames);\n    \n    selectedModel = models[0];\n    setActiveButton(\"models\", selectedModel);\n  }\n  \n  function renderPrecipTypeButtons() {\n    const precipTypes = getAvailablePrecipsumTypesForModel(selectedModel);\n    const displayNames = {\n      \"R03\": \"3h\",\n      \"R06\": \"6h\",\n      \"R12\": \"12h\",\n      \"R24\": \"24h\"\n    };\n    \n    \/\/ Upewnij si\u0119, \u017ce selectedPrecipsumType ma warto\u015b\u0107 domy\u015bln\u0105\n    if (!selectedPrecipsumType || !precipTypes.includes(selectedPrecipsumType)) {\n      selectedPrecipsumType = precipTypes[0];\n    }\n    \n    renderButtons(\"precipTypes\", precipTypes, function(type) {\n      selectedPrecipsumType = type;\n      renderPrecipsumRanges();\n      preloadForecastImages();\n    }, selectedPrecipsumType, displayNames);\n  \n    \/\/ Dynamiczna liczba kolumn: 3 lub 4 w zale\u017cno\u015bci od dost\u0119pnych typ\u00f3w.\n    const precipTypesContainer = document.getElementById(\"precipTypes\");\n    if (precipTypesContainer) {\n      precipTypesContainer.style.gridTemplateColumns = \"repeat(\" + precipTypes.length + \", minmax(0, 1fr))\";\n    }\n    \n    \/\/ Explicite ustaw aktywny przycisk\n    setActiveButton(\"precipTypes\", selectedPrecipsumType);\n  }\n  \n  function initParameters() {\n    \/\/ Pobierz parametry dost\u0119pne dla wybranego modelu\n    const availableParams = selectedModel ? getAvailableParametersForModel(selectedModel) : parameters;\n    const previousParam = selectedParam;\n    const queryParams = new URLSearchParams(window.location.search);\n    const preferAccessibleTemperature = queryParams.get(\"sw\") === \"1\";\n    \n    renderSelect(\"paramSelect\", availableParams, function(param) {\n      selectedParam = param;\n      \n      \/\/ Wy\u0142\u0105cz tryb por\u00f3wnania przy zmianie parametru\n      if (isCompareMode) {\n        isCompareMode = false;\n        selectedModelsForCompare = [];\n        document.getElementById(\"compareBtn\").classList.remove(\"active\");\n        \n        \/\/ Przywr\u00f3\u0107 normalny uk\u0142ad\n        var container = document.getElementById(\"images\");\n        container.style.display = \"block\";\n        container.style.gridTemplateColumns = \"\";\n        container.style.gap = \"\";\n      }\n      \n      \/\/ Wyczy\u015b\u0107 zapami\u0119tan\u0105 dat\u0119 bazow\u0105 dla nowego parametru\n      \/\/ aby wymusi\u0107 ponowne sprawdzenie dost\u0119pno\u015bci\n      const configKey = selectedModel + \"_\" + selectedTerm + \"_\" + param;\n      if (forecastBaseDates[configKey]) {\n        delete forecastBaseDates[configKey];\n      }\n      \n      if (param === \"precipsum\") {\n        document.getElementById(\"precipTypeSection\").style.display = \"block\";\n        renderPrecipTypeButtons();\n        renderPrecipsumRanges();\n      } else {\n        document.getElementById(\"precipTypeSection\").style.display = \"none\";\n        selectedPrecipsumRange = null;\n        renderForecastHours();\n      }\n      \n      updateCurrentConfig();\n      preloadForecastImages();\n    }, parameterDisplayNames);\n    \n    \/\/ Zachowaj aktualny parametr (lub jego najbli\u017cszy odpowiednik) przy zmianie modelu.\n    const mappedParam = getMappedParameterForModel(previousParam, selectedModel);\n    if (mappedParam) {\n      selectedParam = mappedParam;\n      document.getElementById(\"paramSelect\").value = selectedParam;\n    } else if (preferAccessibleTemperature && availableParams.includes(\"temp2m_sw\")) {\n      selectedParam = \"temp2m_sw\";\n      document.getElementById(\"paramSelect\").value = \"temp2m_sw\";\n    } else if (availableParams.includes(\"temp2m\")) {\n      \/\/ Domy\u015blnie temp2m je\u015bli dost\u0119pny\n      selectedParam = \"temp2m\";\n      document.getElementById(\"paramSelect\").value = \"temp2m\";\n    } else if (availableParams.length > 0) {\n      \/\/ W ostateczno\u015bci pierwszy z listy\n      selectedParam = availableParams[0];\n      document.getElementById(\"paramSelect\").value = availableParams[0];\n    }\n    \n    \/\/ Ukryj sekcj\u0119 typu sumy domy\u015blnie\n    document.getElementById(\"precipTypeSection\").style.display = \"none\";\n    \n    \/\/ Poka\u017c sekcj\u0119 typu sumy tylko dla precipsum\n    if (selectedParam === \"precipsum\" && availableParams.includes(\"precipsum\")) {\n      document.getElementById(\"precipTypeSection\").style.display = \"block\";\n      renderPrecipTypeButtons();\n      renderPrecipsumRanges();\n    } else {\n      \/\/ Dla innych parametr\u00f3w renderuj normalne godziny\n      renderForecastHours();\n    }\n  }\n  \n  function renderForecastHours() {\n    const hoursTitle = document.getElementById(\"hoursTitle\");\n    const isRangeMode = isAccumulatedRangeMode(selectedModel, selectedParam);\n    hoursTitle.textContent = isRangeMode ? \"Zakres sumy:\" : \"Godziny prognozy:\";\n    \n    \/\/ W trybie por\u00f3wnania u\u017cyj maksymalnego zakresu godzin spo\u015br\u00f3d wybranych modeli\n    const hours = getMaxForecastHoursForCompareModels(selectedParam);\n  \n    const displayNames = {};\n    if (selectedModel === \"WRF_POLSKA\" && selectedParam === \"precipdaily\") {\n      hours.forEach(function(hour) {\n        displayNames[hour] = getAccumulatedRangeLabel(hour, 24);\n      });\n    }\n  \n    const hourDisplayNames = Object.keys(displayNames).length ? displayNames : null;\n    \n    renderButtons(\"hours\", hours, function(hour) {\n      selectedHour = hour;\n      if (isCompareMode) {\n        showCompareImages();\n      } else {\n        showImages();\n      }\n      \n      const slider = document.getElementById(\"forecastHourSlider\");\n      const index = hours.indexOf(hour);\n      if (index !== -1) {\n        slider.value = index;\n        document.getElementById(\"hourValue\").textContent = getForecastHourDisplayText(selectedModel, selectedParam, hour);\n      }\n      \n      if (isCompareMode) {\n        preloadCompareImages();\n      } else {\n        preloadForecastImages();\n      }\n    }, selectedHour, hourDisplayNames);\n    \n    if (!selectedHour || hours.indexOf(selectedHour) === -1) {\n      selectedHour = hours[0];\n      setActiveButton(\"hours\", selectedHour);\n    }\n    \n    setupHorizontalSlider(hours, hourDisplayNames);\n    if (isCompareMode) {\n      showCompareImages();\n    } else {\n      showImages();\n    }\n  }\n  \n  function renderPrecipsumRanges() {\n    const hoursTitle = document.getElementById(\"hoursTitle\");\n    hoursTitle.textContent = \"Zakres sumy:\";\n    \n    const rangeButtons = [];\n    const displayNames = {};\n    \n    \/\/ Renderuj tylko zakresy dla wybranego typu sumy\n    const typeRanges = getFilteredPrecipsumRangesForModel(selectedModel, selectedPrecipsumType);\n    typeRanges.forEach(function(range) {\n      const key = selectedPrecipsumType + \"_\" + range;\n      rangeButtons.push(key);\n      displayNames[key] = getPrecipsumRangeLabel(selectedModel, range);\n    });\n    \n    renderButtons(\"hours\", rangeButtons, function(key) {\n      const parts = key.split(\"_\");\n      selectedPrecipsumType = parts[0];\n      selectedPrecipsumRange = parts[1];\n      \n      if (isCompareMode) {\n        showCompareImages();\n        preloadCompareImages();\n      } else {\n        showImages();\n        preloadForecastImages();\n      }\n      \n      const slider = document.getElementById(\"forecastHourSlider\");\n      const index = rangeButtons.indexOf(key);\n      if (index !== -1) {\n        slider.value = index;\n        document.getElementById(\"hourValue\").textContent = getPrecipsumHourValueText(selectedModel, selectedPrecipsumRange);\n      }\n    }, null, displayNames);\n    \n    if (rangeButtons.length > 0) {\n      const firstKey = rangeButtons[0];\n      const parts = firstKey.split(\"_\");\n      selectedPrecipsumType = parts[0];\n      selectedPrecipsumRange = parts[1];\n      setActiveButton(\"hours\", firstKey);\n      if (isCompareMode) {\n        showCompareImages();\n      } else {\n        showImages();\n      }\n    }\n    \n    \/\/ Skonfiguruj slider dla zakres\u00f3w czasowych\n    setupHorizontalSlider(rangeButtons, displayNames);\n  }\n  \n  function setupHorizontalSlider(items, displayNames) {\n    const container = document.getElementById(\"horizontalSliderContainer\");\n    const slider = document.getElementById(\"forecastHourSlider\");\n    const hourLabels = document.getElementById(\"hourLabels\");\n    const sliderTitle = document.getElementById(\"sliderTitle\");\n    \n    if (!items || items.length === 0) {\n      container.style.display = \"none\";\n      return;\n    }\n    \n    container.style.display = \"block\";\n    sliderTitle.textContent = isAccumulatedRangeMode(selectedModel, selectedParam) ? \"Zakres sumy:\" : \"Godzina prognozy:\";\n    \n    slider.min = 0;\n    slider.max = items.length - 1;\n    \n    let initialIndex = 0;\n    if (selectedParam === \"precipsum\" && selectedPrecipsumRange) {\n      const key = selectedPrecipsumType + \"_\" + selectedPrecipsumRange;\n      initialIndex = items.indexOf(key);\n    } else if (selectedHour) {\n      initialIndex = items.indexOf(selectedHour);\n    }\n    \n    if (initialIndex < 0) initialIndex = 0;\n    slider.value = initialIndex;\n    \n    let displayValue;\n    if (selectedParam === \"precipsum\") {\n      const selectedKey = items[initialIndex] || \"\";\n      const selectedRange = selectedKey.split(\"_\")[1] || selectedPrecipsumRange;\n      displayValue = getPrecipsumHourValueText(selectedModel, selectedRange);\n    } else {\n      displayValue = displayNames ? (displayNames[items[initialIndex]] || items[initialIndex]) : items[initialIndex] + \"h\";\n    }\n    document.getElementById(\"hourValue\").textContent = displayValue;\n    \n    hourLabels.innerHTML = \"\";\n    const labelsToShow = Math.min(8, items.length);\n    const step = Math.max(1, Math.floor(items.length \/ (labelsToShow - 1)));\n    \n    for (let i = 0; i < items.length; i += step) {\n      const label = document.createElement(\"div\");\n      if (displayNames) {\n        label.textContent = displayNames[items[i]] || items[i];\n      } else {\n        label.textContent = items[i] + \"h\";\n      }\n      hourLabels.appendChild(label);\n    }\n    \n    const lastIndex = items.length - 1;\n    if (lastIndex % step !== 0) {\n      const label = document.createElement(\"div\");\n      if (displayNames) {\n        label.textContent = displayNames[items[lastIndex]] || items[lastIndex];\n      } else {\n        label.textContent = items[lastIndex] + \"h\";\n      }\n      hourLabels.appendChild(label);\n    }\n    \n    slider.oninput = function() {\n      const index = parseInt(this.value);\n      const item = items[index];\n      \n      if (selectedParam === \"precipsum\") {\n        const parts = item.split(\"_\");\n        selectedPrecipsumType = parts[0];\n        selectedPrecipsumRange = parts[1];\n        document.getElementById(\"hourValue\").textContent = getPrecipsumHourValueText(selectedModel, selectedPrecipsumRange);\n      } else {\n        selectedHour = item;\n        document.getElementById(\"hourValue\").textContent = displayNames ? (displayNames[item] || item) : item + \"h\";\n      }\n      \n      setActiveButton(\"hours\", item);\n      \n      if (isCompareMode) {\n        showCompareImages();\n        preloadCompareImages();\n      } else {\n        showImages();\n        preloadForecastImages();\n      }\n    };\n  }\n  \n  \/\/ ===== FUNKCJE DLA TRYBU POR\u00d3WNANIA =====\n  \n  function showCompareImages() {\n    var container = document.getElementById(\"images\");\n    container.innerHTML = \"\";\n    container.style.display = \"grid\";\n    \n    \/\/ Zawsze 2 kolumny - zar\u00f3wno dla 2 jak i 4 modeli\n    \/\/ 2 modele = 1 wiersz x 2 kolumny\n    \/\/ 4 modele = 2 wiersze x 2 kolumny\n    container.style.gridTemplateColumns = \"1fr 1fr\";\n    container.style.gap = \"10px\";\n    \n    \/\/ Sprawd\u017a czy kt\u00f3rykolwiek z modeli ma wczorajsz\u0105 dat\u0119 i poka\u017c odpowiednie ostrze\u017cenie\n    var dateWarning = document.getElementById(\"dateWarning\");\n    if (dateWarning) {\n      var hasYesterdayData = false;\n      var oldestDate = null;\n      \n      selectedModelsForCompare.forEach(function(modelKey) {\n        var configKey = modelKey + \"_\" + selectedTerm + \"_\" + selectedParam;\n        var baseDate = forecastBaseDates[configKey];\n        if (baseDate && baseDate !== getDateString(0)) {\n          hasYesterdayData = true;\n          if (!oldestDate || baseDate < oldestDate) {\n            oldestDate = baseDate;\n          }\n        }\n      });\n      \n      if (hasYesterdayData && oldestDate) {\n        dateWarning.textContent = \"\u26a0 Prognoza z dnia \" + oldestDate + \" (najnowsza dost\u0119pna)\";\n        dateWarning.style.display = \"block\";\n      } else {\n        dateWarning.style.display = \"none\";\n      }\n    }\n    \n    \/\/ Sprawd\u017a czy wszystkie modele maj\u0105 ustalon\u0105 dat\u0119 bazow\u0105\n    var modelsNeedingCheck = [];\n    selectedModelsForCompare.forEach(function(modelKey) {\n      var configKey = modelKey + \"_\" + selectedTerm + \"_\" + selectedParam;\n      if (!forecastBaseDates[configKey]) {\n        modelsNeedingCheck.push(modelKey);\n      }\n    });\n    \n    if (modelsNeedingCheck.length > 0) {\n      \/\/ Najpierw sprawd\u017a dost\u0119pno\u015b\u0107 dla brakuj\u0105cych modeli\n      container.innerHTML = '<div class=\"loader-container\"><div class=\"loader\"><\/div><p>Sprawdzanie dost\u0119pno\u015bci...<\/p><\/div>';\n      \n      var checksCompleted = 0;\n      modelsNeedingCheck.forEach(function(modelKey) {\n        checkForecastAvailability(modelKey, selectedTerm, selectedParam, function() {\n          checksCompleted++;\n          if (checksCompleted === modelsNeedingCheck.length) {\n            \/\/ Wszystkie sprawdzone - teraz poka\u017c obrazki\n            showCompareImages();\n          }\n        });\n      });\n      return;\n    }\n    \n    selectedModelsForCompare.forEach(function(modelKey) {\n      var modelConfig = modelsConfig[modelKey];\n      var wrapper = document.createElement(\"div\");\n      wrapper.style.position = \"relative\";\n      \n      var label = document.createElement(\"div\");\n      label.style.position = \"absolute\";\n      label.style.top = \"10px\";\n      label.style.left = \"10px\";\n      label.style.background = \"rgba(0,0,0,0.7)\";\n      label.style.color = \"white\";\n      label.style.padding = \"8px 12px\";\n      label.style.borderRadius = \"6px\";\n      label.style.fontSize = \"1.1em\";\n      label.style.fontWeight = \"bold\";\n      label.style.zIndex = \"10\";\n      label.textContent = modelConfig.displayName;\n      \n      var img = document.createElement(\"img\");\n      img.id = \"forecastImage_\" + modelKey;\n      img.style.width = \"100%\";\n      img.style.borderRadius = \"8px\";\n      img.style.cursor = \"pointer\";\n      img.addEventListener(\"click\", function() {\n        openLightbox(this.src);\n      });\n      \n      wrapper.appendChild(label);\n      wrapper.appendChild(img);\n      container.appendChild(wrapper);\n      \n      \/\/ Za\u0142aduj obraz\n      var configKey = modelKey + \"_\" + selectedTerm + \"_\" + selectedParam;\n      var baseDate = forecastBaseDates[configKey];\n      var imagePath = generateImagePath(modelKey, selectedTerm, selectedParam, \n                                       selectedParam === \"precipsum\" ? selectedPrecipsumRange : selectedHour,\n                                       baseDate);\n      img.src = imagePath;\n    });\n  }\n  \n  function preloadCompareImages() {\n    \/\/ Sprawd\u017a czy wszystkie modele maj\u0105 ustalon\u0105 dat\u0119 bazow\u0105\n    var allHaveDates = true;\n    selectedModelsForCompare.forEach(function(modelKey) {\n      var configKey = modelKey + \"_\" + selectedTerm + \"_\" + selectedParam;\n      if (!forecastBaseDates[configKey]) {\n        allHaveDates = false;\n      }\n    });\n    \n    if (!allHaveDates) {\n      \/\/ Brak dat - nie preloaduj (showCompareImages ustali daty)\n      return;\n    }\n    \n    selectedModelsForCompare.forEach(function(modelKey) {\n      var configKey = modelKey + \"_\" + selectedTerm + \"_\" + selectedParam;\n      var baseDate = forecastBaseDates[configKey];\n      var hours = getForecastHoursForModel(modelKey, selectedParam);\n      hours.forEach(function(hour) {\n        var imagePath = generateImagePath(modelKey, selectedTerm, selectedParam, \n                                         String(hour).padStart(2, \"0\"),\n                                         baseDate);\n        preloadImage(imagePath, modelKey, selectedTerm, selectedParam, hour);\n      });\n    });\n  }\n  \n  function showImages() {\n    \/\/ Je\u015bli tryb por\u00f3wnania - u\u017cyj showCompareImages\n    if (isCompareMode) {\n      showCompareImages();\n      return;\n    }\n    \n    if (!selectedTerm || !selectedModel || !selectedParam) return;\n    if (selectedParam !== \"precipsum\" && !selectedHour) return;\n    if (selectedParam === \"precipsum\" && !selectedPrecipsumRange) return;\n    \n    const container = document.getElementById(\"images\");\n    let hourToUse;\n    if (selectedParam === \"precipsum\") {\n      hourToUse = null;\n    } else {\n      hourToUse = selectedHour;\n    }\n    const configKey = selectedModel + \"_\" + selectedTerm + \"_\" + selectedParam;\n    \n    \/\/ Najpierw sprawd\u017a czy mamy ustalon\u0105 dat\u0119 bazow\u0105 dla tej konfiguracji\n    const knownBaseDate = forecastBaseDates[configKey];\n    \n    if (knownBaseDate) {\n      \/\/ Mamy ustalon\u0105 dat\u0119 - sprawd\u017a cache tylko dla tej daty\n      const imagePath = generateImagePath(selectedModel, selectedTerm, selectedParam, hourToUse, knownBaseDate);\n      const imageId = imagePath.replace(\/[\\\/\\.]\/g, \"_\");\n      \n      if (imageCache[imageId]) {\n        \/\/ Znaleziono w cache - u\u017cyj od razu\n        container.innerHTML = \"\";\n        const cachedImg = imageCache[imageId].cloneNode(true);\n        cachedImg.style.display = \"block\";\n        cachedImg.onclick = function() { openLightbox(cachedImg.src); };\n        container.appendChild(cachedImg);\n        \n        \/\/ Oznacz godzin\u0119\/zakres jako za\u0142adowany\n        if (selectedParam === 'precipsum') {\n          markHourAsLoaded(selectedModel, selectedTerm, selectedParam, null, selectedPrecipsumRange, selectedPrecipsumType);\n        } else if (hourToUse) {\n          markHourAsLoaded(selectedModel, selectedTerm, selectedParam, hourToUse);\n        }\n        \n        \/\/ Dodaj info o dacie je\u015bli wczorajsza\n        const dateWarning = document.getElementById(\"dateWarning\");\n        if (knownBaseDate !== getDateString(0)) {\n          dateWarning.textContent = \"\u26a0 Prognoza z dnia \" + knownBaseDate + \" (najnowsza dost\u0119pna)\";\n          dateWarning.style.display = \"block\";\n        } else {\n          dateWarning.textContent = \"\";\n          dateWarning.style.display = \"none\";\n        }\n        \n        updateCurrentConfig();\n        return; \/\/ Znaleziono w cache, koniec funkcji\n      }\n    }\n    \n    \/\/ Nie znaleziono w cache - poka\u017c loader i sprawd\u017a dost\u0119pno\u015b\u0107\n    container.innerHTML = '<div class=\"loader-container\"><div class=\"loader\"><\/div><p>\u0141adowanie prognozy...<\/p><\/div>';\n    \n    \/\/ Zapami\u0119taj wersj\u0119 konfiguracji przed wywo\u0142aniem async\n    const requestVersion = configVersion;\n    const requestModel = selectedModel;\n    const requestTerm = selectedTerm;\n    const requestParam = selectedParam;\n    const requestHour = hourToUse;\n    const requestPrecipRange = selectedPrecipsumRange;\n    const requestPrecipType = selectedPrecipsumType;\n    \n    \/\/ Sprawd\u017a dost\u0119pno\u015b\u0107 prognozy dla tej konfiguracji\n    checkForecastAvailability(requestModel, requestTerm, requestParam, function(baseDate) {\n      \/\/ Sprawd\u017a czy konfiguracja si\u0119 nie zmieni\u0142a w mi\u0119dzyczasie\n      if (configVersion !== requestVersion) {\n        \/\/ Konfiguracja si\u0119 zmieni\u0142a - ignoruj ten wynik\n        return;\n      }\n      \n      if (!baseDate) {\n        container.innerHTML = \"\";\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        container.appendChild(errorImg);\n        \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        errorMsg.style.marginTop = \"10px\";\n        container.appendChild(errorMsg);\n        \n        updateCurrentConfig();\n        return;\n      }\n      \n      \/\/ Oblicz imageId dla cache\n      const imagePath = generateImagePath(requestModel, requestTerm, requestParam, requestHour, baseDate);\n      const imageId = imagePath.replace(\/[\\\/\\.]\/g, \"_\");\n      \n      \/\/ Za\u0142aduj obrazek (cache ju\u017c zosta\u0142 sprawdzony wcze\u015bniej)\n      tryLoadImageWithFallback(\n        requestModel,\n        requestTerm,\n        requestParam,\n        requestHour,\n        function(img, imagePath, dateUsed) {\n          \/\/ Sprawd\u017a ponownie czy konfiguracja si\u0119 nie zmieni\u0142a\n          if (configVersion !== requestVersion) {\n            return;\n          }\n          \n          container.innerHTML = \"\";\n          img.style.display = \"block\";\n          img.style.maxWidth = \"100%\";\n          img.style.cursor = \"pointer\";\n          img.onclick = function() { openLightbox(img.src); };\n          container.appendChild(img);\n          \n          imageCache[imageId] = img.cloneNode(true);\n          \n          \/\/ Oznacz godzin\u0119\/zakres jako za\u0142adowany\n          if (requestParam === 'precipsum') {\n            markHourAsLoaded(requestModel, requestTerm, requestParam, null, requestPrecipRange, requestPrecipType);\n          } else if (requestHour) {\n            markHourAsLoaded(requestModel, requestTerm, requestParam, requestHour);\n          }\n          \n          \/\/ Dodaj info o dacie je\u015bli wczorajsza\n          const dateWarning = document.getElementById(\"dateWarning\");\n          if (dateUsed !== getDateString(0)) {\n            dateWarning.textContent = \"\u26a0 Prognoza z dnia \" + dateUsed + \" (najnowsza dost\u0119pna)\";\n            dateWarning.style.display = \"block\";\n          } else {\n            dateWarning.textContent = \"\";\n            dateWarning.style.display = \"none\";\n          }\n          \n          updateCurrentConfig();\n        },\n        function(imagePath) {\n          \/\/ Sprawd\u017a ponownie czy konfiguracja si\u0119 nie zmieni\u0142a\n          if (configVersion !== requestVersion) {\n            return;\n          }\n          \n          container.innerHTML = \"\";\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          container.appendChild(errorImg);\n          \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          errorMsg.style.marginTop = \"10px\";\n          container.appendChild(errorMsg);\n          \n          updateCurrentConfig();\n        }\n      );\n    });\n  }\n  \n  function updateCurrentConfig() {\n    const configEl = document.getElementById(\"imageTitle\");\n    if (!configEl) return;\n    \n    if (!selectedTerm || !selectedModel || !selectedParam) {\n      configEl.style.display = \"none\";\n      return;\n    }\n    \n    const modelName = modelsConfig[selectedModel] ? modelsConfig[selectedModel].displayName : selectedModel;\n    const paramName = parameterDisplayNames[selectedParam] || selectedParam;\n    \n    let timeInfo = \"\";\n    if (selectedParam === \"precipsum\" && selectedPrecipsumRange) {\n      timeInfo = selectedPrecipsumType.replace(\"R\", \"\") + \"h suma, zakres: \" + selectedPrecipsumRange + \"h\";\n    } else if (selectedHour) {\n      if (selectedModel === \"WRF_POLSKA\" && selectedParam === \"precipdaily\") {\n        timeInfo = \"zakres: \" + getAccumulatedRangeLabel(selectedHour, 24);\n      } else {\n        timeInfo = \"+\" + selectedHour + \"h\";\n      }\n    }\n    \n    configEl.textContent = modelName + \" | \" + paramName + \" | bieg \" + selectedTerm + \"z | \" + timeInfo;\n    configEl.style.display = \"block\";\n  }\n  \n  function preloadForecastImages() {\n    \/\/ Je\u015bli tryb por\u00f3wnania - u\u017cyj preloadCompareImages\n    if (isCompareMode) {\n      preloadCompareImages();\n      return;\n    }\n    \n    if (!selectedTerm || !selectedModel || !selectedParam) return;\n    \n    \/\/ Poczekaj a\u017c forecastBaseDate dla tej konfiguracji b\u0119dzie ustalona\n    const configKey = selectedModel + \"_\" + selectedTerm + \"_\" + selectedParam;\n    const baseDate = forecastBaseDates[configKey];\n    \n    if (!baseDate) {\n      \/\/ Je\u015bli jeszcze nie mamy daty bazowej, spr\u00f3buj j\u0105 ustali\u0107\n      checkForecastAvailability(selectedModel, selectedTerm, selectedParam, function(baseDateResult) {\n        if (baseDateResult) {\n          \/\/ Teraz mamy dat\u0119, mo\u017cemy preloadowa\u0107\n          preloadForecastImages();\n        }\n      });\n      return;\n    }\n    \n    try {\n      const imagesToPreload = [];\n      \n      if (selectedParam === \"precipsum\") {\n        \/\/ Dla precipsum preloaduj wszystkie zakresy dla aktualnego typu\n        const ranges = getFilteredPrecipsumRangesForModel(selectedModel, selectedPrecipsumType);\n        if (ranges) {\n          \/\/ Znajd\u017a indeks aktualnego zakresu\n          const currentRangeIndex = ranges.indexOf(selectedPrecipsumRange);\n          \n          \/\/ Preloaduj zakresy w pobli\u017cu aktualnego (priorytetowo)\n          const maxOffset = Math.max(currentRangeIndex, ranges.length - currentRangeIndex - 1);\n          \n          for (let offset = 0; offset <= maxOffset; offset++) {\n            \/\/ Preload aktualny zakres\n            if (offset === 0 && currentRangeIndex >= 0) {\n              const tempRange = selectedPrecipsumRange;\n              const rangeToLoad = ranges[currentRangeIndex];\n              selectedPrecipsumRange = rangeToLoad;\n              imagesToPreload.push({\n                path: generateImagePath(selectedModel, selectedTerm, selectedParam, null, baseDate),\n                hour: null,\n                precipRange: rangeToLoad,\n                precipType: selectedPrecipsumType\n              });\n              selectedPrecipsumRange = tempRange;\n              continue;\n            }\n            \n            \/\/ Preload nast\u0119pny zakres\n            if (currentRangeIndex + offset < ranges.length) {\n              const tempRange = selectedPrecipsumRange;\n              const rangeToLoad = ranges[currentRangeIndex + offset];\n              selectedPrecipsumRange = rangeToLoad;\n              imagesToPreload.push({\n                path: generateImagePath(selectedModel, selectedTerm, selectedParam, null, baseDate),\n                hour: null,\n                precipRange: rangeToLoad,\n                precipType: selectedPrecipsumType\n              });\n              selectedPrecipsumRange = tempRange;\n            }\n            \n            \/\/ Preload poprzedni zakres\n            if (currentRangeIndex - offset >= 0) {\n              const tempRange = selectedPrecipsumRange;\n              const rangeToLoad = ranges[currentRangeIndex - offset];\n              selectedPrecipsumRange = rangeToLoad;\n              imagesToPreload.push({\n                path: generateImagePath(selectedModel, selectedTerm, selectedParam, null, baseDate),\n                hour: null,\n                precipRange: rangeToLoad,\n                precipType: selectedPrecipsumType\n              });\n              selectedPrecipsumRange = tempRange;\n            }\n          }\n        }\n      } else {\n        \/\/ Dla zwyk\u0142ych parametr\u00f3w preloaduj godziny w pobli\u017cu aktualnej\n        const hours = getForecastHoursForModel(selectedModel, selectedParam);\n        const currentIndex = hours.indexOf(selectedHour);\n        \n        if (currentIndex === -1) return;\n        \n        \/\/ Preloaduj priorytetowo - najpierw najbli\u017csze godziny\n        const maxOffset = Math.max(currentIndex, hours.length - currentIndex - 1);\n        \n        for (let offset = 0; offset <= maxOffset; offset++) {\n          \/\/ Preload aktualn\u0105 godzin\u0119\n          if (offset === 0) {\n            imagesToPreload.push({\n              path: generateImagePath(selectedModel, selectedTerm, selectedParam, hours[currentIndex], baseDate),\n              hour: hours[currentIndex]\n            });\n            continue;\n          }\n          \n          \/\/ Preload godzin\u0119 po aktualnej\n          if (currentIndex + offset < hours.length) {\n            imagesToPreload.push({\n              path: generateImagePath(selectedModel, selectedTerm, selectedParam, hours[currentIndex + offset], baseDate),\n              hour: hours[currentIndex + offset]\n            });\n          }\n          \n          \/\/ Preload godzin\u0119 przed aktualn\u0105\n          if (currentIndex - offset >= 0) {\n            imagesToPreload.push({\n              path: generateImagePath(selectedModel, selectedTerm, selectedParam, hours[currentIndex - offset], baseDate),\n              hour: hours[currentIndex - offset]\n            });\n          }\n        }\n      }\n      \n      const imagesToQueue = [];\n      imagesToPreload.forEach(function(imageData) {\n        if (!preloadedImages.has(imageData.path)) {\n          const imageId = imageData.path.replace(\/[\\\/\\.]\/g, \"_\");\n          if (!imageCache[imageId]) {\n            imagesToQueue.push({\n              path: imageData.path,\n              id: imageId,\n              hour: imageData.hour,\n              model: selectedModel,\n              term: selectedTerm,\n              param: selectedParam,\n              precipRange: imageData.precipRange,\n              precipType: imageData.precipType\n            });\n          }\n        }\n      });\n      \n      \/\/ Dodaj do kolejki\n      imageLoadQueue = imageLoadQueue.concat(imagesToQueue);\n      \n      \/\/ Uruchom przetwarzanie kolejki je\u015bli nie jest aktywne\n      if (!isLoadingImages && imageLoadQueue.length > 0) {\n        processImageQueue();\n      }\n      \n    } catch (error) {\n      console.error(\"B\u0142\u0105d podczas preloadowania obrazk\u00f3w:\", error);\n    }\n  }\n  \n  \/\/ Throttle dla updateLoadedHoursVisualization\n  let updateVisualizationTimeout = null;\n  \n  \/\/ Funkcja do oznaczania godzin jako za\u0142adowanych\n  function markHourAsLoaded(model, term, param, hour, precipRange, precipType) {\n    \/\/ Dla precipsum u\u017cywamy rozszerzonego klucza z typem sumy\n    let configKey;\n    if (param === 'precipsum') {\n      const typeToUse = precipType || selectedPrecipsumType;\n      configKey = model + \"_\" + term + \"_\" + param + \"_\" + typeToUse;\n    } else {\n      configKey = model + \"_\" + term + \"_\" + param;\n    }\n    \n    if (!loadedHours[configKey]) {\n      loadedHours[configKey] = new Set();\n    }\n    \n    if (param === 'precipsum') {\n      \/\/ Dla precipsum zapisz ca\u0142y klucz zakresu (np. \"R06_00-06\")\n      const typeToUse = precipType || selectedPrecipsumType;\n      const rangeToUse = precipRange || selectedPrecipsumRange;\n      const rangeKey = typeToUse + \"_\" + rangeToUse;\n      loadedHours[configKey].add(rangeKey);\n    } else {\n      const hourStr = String(hour).padStart(2, \"0\");\n      loadedHours[configKey].add(hourStr);\n    }\n    \n    \/\/ Aktualizuj wizualizacj\u0119 tylko je\u015bli to aktualnie wybrany model\/term\/param\n    if (model === selectedModel && term === selectedTerm && param === selectedParam) {\n      \/\/ Throttle - aktualizuj maksymalnie co 100ms\n      if (updateVisualizationTimeout) {\n        clearTimeout(updateVisualizationTimeout);\n      }\n      updateVisualizationTimeout = setTimeout(function() {\n        updateLoadedHoursVisualization();\n        updateVisualizationTimeout = null;\n      }, 100);\n    }\n  }\n  \n  \/\/ Funkcja do aktualizacji wizualizacji za\u0142adowanych godzin\n  function updateLoadedHoursVisualization() {\n    if (!selectedModel || !selectedTerm || !selectedParam) {\n      return;\n    }\n    \n    \/\/ Dla precipsum pracujemy na zakresach zamiast godzin\n    if (selectedParam === 'precipsum') {\n      const configKey = selectedModel + \"_\" + selectedTerm + \"_\" + selectedParam + \"_\" + selectedPrecipsumType;\n      const rangesLoaded = loadedHours[configKey] || new Set();\n      \n      \/\/ Aktualizacja przycisk\u00f3w zakres\u00f3w\n      const hourButtons = document.querySelectorAll('#hours button');\n      hourButtons.forEach(function(button) {\n        const rangeKey = button.dataset.value; \/\/ np. \"R06_00-06\"\n        if (rangesLoaded.has(rangeKey)) {\n          button.classList.add('loaded');\n        } else {\n          button.classList.remove('loaded');\n        }\n      });\n      \n      \/\/ Aktualizacja slidera - oblicz procent za\u0142adowanych zakres\u00f3w\n      const ranges = getFilteredPrecipsumRangesForModel(selectedModel, selectedPrecipsumType);\n      const slider = document.getElementById('forecastHourSlider');\n      if (!slider) return;\n      \n      \/\/ Resetuj t\u0142o slidera na domy\u015blne\n      slider.style.background = 'linear-gradient(to right, #b0b0b0 0%, #b0b0b0 0%, #e0e0e0 0%, #e0e0e0 100%)';\n      \n      if (ranges.length > 0) {\n        \/\/ Znajd\u017a najd\u0142u\u017cszy ci\u0105g\u0142y ci\u0105g za\u0142adowanych zakres\u00f3w OD POCZ\u0104TKU\n        let continuousLoadedIndex = -1;\n        for (let index = 0; index < ranges.length; index++) {\n          const rangeKey = selectedPrecipsumType + \"_\" + ranges[index];\n          if (rangesLoaded.has(rangeKey)) {\n            continuousLoadedIndex = index;\n          } else {\n            \/\/ Przerwij gdy napotkamy pierwszy nieza\u0142adowany zakres\n            break;\n          }\n        }\n        \n        if (continuousLoadedIndex >= 0) {\n          const percentage = ((continuousLoadedIndex + 1) \/ ranges.length) * 100;\n          slider.style.background = 'linear-gradient(to right, #b0b0b0 0%, #b0b0b0 ' + percentage + '%, #e0e0e0 ' + percentage + '%, #e0e0e0 100%)';\n        }\n      }\n      \n      return;\n    }\n    \n    const configKey = selectedModel + \"_\" + selectedTerm + \"_\" + selectedParam;\n    const hoursLoaded = loadedHours[configKey] || new Set();\n    \n    \/\/ Aktualizacja przycisk\u00f3w godzin\n    const hourButtons = document.querySelectorAll('#hours button');\n    hourButtons.forEach(function(button) {\n      const hour = button.dataset.value;\n      \/\/ Normalizuj do formatu \"XX\" (z zerem wiod\u0105cym)\n      const hourStr = String(hour).padStart(2, \"0\");\n      if (hoursLoaded.has(hourStr)) {\n        button.classList.add('loaded');\n      } else {\n        button.classList.remove('loaded');\n      }\n    });\n    \n    \/\/ Aktualizacja slidera - oblicz procent za\u0142adowanych godzin\n    const hours = selectedModel ? getForecastHoursForModel(selectedModel, selectedParam) : [];\n    const slider = document.getElementById('forecastHourSlider');\n    if (!slider) return;\n    \n    \/\/ Resetuj t\u0142o slidera na domy\u015blne (gradient pocz\u0105tkowy)\n    slider.style.background = 'linear-gradient(to right, #b0b0b0 0%, #b0b0b0 0%, #e0e0e0 0%, #e0e0e0 100%)';\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 resetowania stanu przy zmianie modelu lub terminu\n  function resetStateForModelChange() {\n    \/\/ Inkrementuj wersj\u0119 konfiguracji - to spowoduje ignorowanie starych \u017c\u0105da\u0144\n    configVersion++;\n    \n    \/\/ Przerwij wszystkie aktualnie \u0142adowane obrazy\n    currentLoadingImages.forEach(function(img) {\n      if (img && img.src) {\n        img.src = ''; \/\/ Przerwij \u0142adowanie\n      }\n    });\n    currentLoadingImages = [];\n    \n    \/\/ Wyczy\u015b\u0107 kolejk\u0119 \u0142adowania obraz\u00f3w\n    imageLoadQueue = [];\n    isLoadingImages = false;\n    \n    \/\/ NIE resetujemy forecastBaseDates - ka\u017cdy termin ma w\u0142asny klucz w cache\n    \/\/ Klucz to model + \"_\" + term + \"_\" + param, wi\u0119c r\u00f3\u017cne terminy maj\u0105 r\u00f3\u017cne wpisy\n    \n    \/\/ NIE czy\u015bcimy imageCache i preloadedImages - pozwalamy u\u017cy\u0107 ponownie ju\u017c za\u0142adowanych\n  }\n  \n  \/\/ Funkcja do przetwarzania kolejki obraz\u00f3w z limitowaniem jednoczesnych pobra\u0144\n  function 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(function(imageInfo) {\n      \/\/ Sprawd\u017a czy obraz ju\u017c istnieje w buforze\n      if (imageCache[imageInfo.id]) {\n        loadedCount++;\n        if (loadedCount === currentBatch.length) {\n          \/\/ Wymu\u015b aktualizacj\u0119 wizualizacji po zako\u0144czeniu partii\n          if (updateVisualizationTimeout) {\n            clearTimeout(updateVisualizationTimeout);\n            updateVisualizationTimeout = null;\n          }\n          updateLoadedHoursVisualization();\n          setTimeout(processImageQueue, 10);\n        }\n        return;\n      }\n      \n      \/\/ Wczytaj obrazek w tle - bezpo\u015brednio, bez fallback (fallback jest w checkForecastAvailability)\n      const preloadImg = new Image();\n      \n      preloadImg.onload = function() {\n        \/\/ Zapisz obrazek w buforze\n        imageCache[imageInfo.id] = preloadImg;\n        preloadedImages.add(imageInfo.path);\n        \n        \/\/ Oznacz godzin\u0119\/zakres jako za\u0142adowany\n        if (imageInfo.model && imageInfo.term && imageInfo.param) {\n          if (imageInfo.param === 'precipsum') {\n            markHourAsLoaded(imageInfo.model, imageInfo.term, imageInfo.param, null, imageInfo.precipRange, imageInfo.precipType);\n          } else if (imageInfo.hour !== undefined) {\n            markHourAsLoaded(imageInfo.model, imageInfo.term, imageInfo.param, imageInfo.hour);\n          }\n        }\n        \n        loadedCount++;\n        if (loadedCount === currentBatch.length) {\n          \/\/ Wymu\u015b aktualizacj\u0119 wizualizacji po zako\u0144czeniu partii\n          if (updateVisualizationTimeout) {\n            clearTimeout(updateVisualizationTimeout);\n            updateVisualizationTimeout = null;\n          }\n          updateLoadedHoursVisualization();\n          setTimeout(processImageQueue, 10);\n        }\n      };\n      \n      preloadImg.onerror = function() {\n        loadedCount++;\n        if (loadedCount === currentBatch.length) {\n          \/\/ Wymu\u015b aktualizacj\u0119 wizualizacji po zako\u0144czeniu partii\n          if (updateVisualizationTimeout) {\n            clearTimeout(updateVisualizationTimeout);\n            updateVisualizationTimeout = null;\n          }\n          updateLoadedHoursVisualization();\n          setTimeout(processImageQueue, 10);\n        }\n      };\n      \n      \/\/ Rozpocznij \u0142adowanie obrazka\n      preloadImg.src = imageInfo.path;\n    });\n  }\n  \n  function openLightbox(src) {\n    const lightbox = document.getElementById(\"lightbox\");\n    const lightboxImg = document.getElementById(\"lightboxImg\");\n    lightboxImg.src = src;\n    lightbox.classList.add(\"active\");\n  }\n  \n  function closeLightbox() {\n    const lightbox = document.getElementById(\"lightbox\");\n    lightbox.classList.remove(\"active\");\n  }\n  \n  \/\/ ========================================\n  \/\/ INICJALIZACJA\n  \/\/ ========================================\n  document.addEventListener(\"DOMContentLoaded\", function() {\n    initTerms();\n    initModels();\n    initParameters();\n    \n    \/\/ Renderuj odpowiednie godziny\/zakresy w zale\u017cno\u015bci od parametru\n    if (selectedParam === \"precipsum\") {\n      renderPrecipsumRanges();\n    } else {\n      renderForecastHours();\n    }\n    \n    \/\/ Uruchom preloading i aktualizacj\u0119 konfiguracji po inicjalizacji\n    updateCurrentConfig();\n    updateLoadedHoursVisualization(); \/\/ Pocz\u0105tkowy stan\n    preloadForecastImages();\n    \n    document.getElementById(\"lightbox\").addEventListener(\"click\", closeLightbox);\n    document.querySelector(\".lightbox-close\").addEventListener(\"click\", closeLightbox);\n    \n    \/\/ Zmienna do animacji\n    var animationInterval = null;\n    var isAnimating = false;\n    \n    \/\/ Obs\u0142uga przycisku animacji\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        toggleAnimation();\n        animateBtn.classList.add(\"active\");\n        animateBtn.textContent = \"\u23f8\";\n        animateBtn.title = \"Zatrzymaj animacj\u0119\";\n      }\n    });\n    \n    \/\/ Funkcja animacji\n    function toggleAnimation() {\n      if (isAnimating) {\n        \/\/ Zatrzymaj animacj\u0119\n        clearInterval(animationInterval);\n        animationInterval = null;\n        isAnimating = false;\n        const animateBtn = document.getElementById(\"animateBtn\");\n        if (animateBtn) {\n          animateBtn.classList.remove(\"active\");\n          animateBtn.textContent = \"\u25b6\";\n          animateBtn.title = \"Animuj godziny prognozy\";\n        }\n        return;\n      }\n      \n      isAnimating = true;\n      const animateBtn = document.getElementById(\"animateBtn\");\n      if (animateBtn) {\n        animateBtn.classList.add(\"active\");\n        animateBtn.textContent = \"\u23f8\";\n        animateBtn.title = \"Zatrzymaj animacj\u0119\";\n      }\n      \n      function animateStep() {\n        if (selectedParam === \"precipsum\") {\n          \/\/ Dla precipsum nawiguj po zakresach\n          const rangeButtons = [];\n          const availableTypes = getAvailablePrecipsumTypesForModel(selectedModel);\n          availableTypes.forEach(function(type) {\n            const typeRanges = getFilteredPrecipsumRangesForModel(selectedModel, type);\n            if (typeRanges) {\n              typeRanges.forEach(function(range) {\n                rangeButtons.push({ type: type, range: range, key: type + \"_\" + range });\n              });\n            }\n          });\n          \n          if (rangeButtons.length === 0) return;\n          \n          const currentKey = selectedPrecipsumType + \"_\" + selectedPrecipsumRange;\n          const currentIndex = rangeButtons.findIndex(function(item) { return item.key === currentKey; });\n          const newIndex = (currentIndex + 1) % rangeButtons.length;\n          \n          selectedPrecipsumType = rangeButtons[newIndex].type;\n          selectedPrecipsumRange = rangeButtons[newIndex].range;\n          setActiveButton(\"hours\", rangeButtons[newIndex].key);\n          \n          const slider = document.getElementById(\"forecastHourSlider\");\n          if (slider) {\n            slider.value = newIndex;\n            document.getElementById(\"hourValue\").textContent = getPrecipsumHourValueText(selectedModel, selectedPrecipsumRange);\n          }\n          showImages();\n        } else {\n          \/\/ Dla zwyk\u0142ych parametr\u00f3w nawiguj po godzinach\n          const hours = getForecastHoursForModel(selectedModel, selectedParam);\n          if (hours.length === 0) return;\n          \n          const currentIndex = hours.findIndex(function(h) { \n            return String(h).padStart(2, \"0\") === String(selectedHour).padStart(2, \"0\"); \n          });\n          const newIndex = (currentIndex + 1) % hours.length;\n          const newHour = String(hours[newIndex]).padStart(2, \"0\");\n          selectedHour = newHour;\n          setActiveButton(\"hours\", newHour);\n          \n          const slider = document.getElementById(\"forecastHourSlider\");\n          if (slider) {\n            slider.value = newIndex;\n            document.getElementById(\"hourValue\").textContent = getForecastHourDisplayText(selectedModel, selectedParam, newHour);\n          }\n          showImages();\n        }\n      }\n      \n      animationInterval = setInterval(animateStep, 500);\n    }\n    \n    \/\/ Obs\u0142uga klawiszy dla nawigacji\n    document.addEventListener(\"keydown\", function(event) {\n      \/\/ Ignoruj 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      \/\/ Escape - zamknij lightbox lub zatrzymaj animacj\u0119\n      if (event.key === \"Escape\") {\n        if (isAnimating) {\n          toggleAnimation();\n        }\n        closeLightbox();\n        return;\n      }\n      \n      \/\/ Klawisze 1-9 i 0 - wyb\u00f3r modelu\n      if ((event.key >= \"1\" && event.key <= \"9\") || event.key === \"0\") {\n        event.preventDefault();\n        let modelIndex;\n        if (event.key === \"0\") {\n          modelIndex = 9; \/\/ 0 = 10. model (indeks 9)\n        } else {\n          modelIndex = parseInt(event.key) - 1;\n        }\n        const modelKeys = Object.keys(modelsConfig);\n        if (modelIndex < modelKeys.length) {\n          const modelKey = modelKeys[modelIndex];\n  \n          if (selectedModel !== modelKey) {\n            resetStateForModelChange();\n          }\n  \n          selectedModel = modelKey;\n          setActiveButton(\"models\", modelKey);\n  \n          \/\/ Prze\u0142aduj parametry dla nowego modelu (z mapowaniem nazw\/odpowiednik\u00f3w)\n          initParameters();\n  \n          updateCurrentConfig();\n          if (selectedParam) {\n            if (selectedParam === \"precipsum\" && selectedPrecipsumRange) {\n              showImages();\n            } else if (selectedParam !== \"precipsum\" && selectedHour) {\n              showImages();\n            }\n          }\n          preloadForecastImages();\n        }\n        return;\n      }\n      \n      \/\/ < > - zmiana terminu startu (bez przechodzenia przez skrajne)\n      if (event.key === \",\" || event.key === \"<\" || event.key === \".\" || event.key === \">\") {\n        event.preventDefault();\n        const currentIndex = terms.indexOf(selectedTerm);\n        let newIndex;\n        if (event.key === \",\" || event.key === \"<\") {\n          \/\/ Nie przechod\u017a poni\u017cej pierwszego terminu\n          if (currentIndex <= 0) return;\n          newIndex = currentIndex - 1;\n        } else {\n          \/\/ Nie przechod\u017a powy\u017cej ostatniego terminu\n          if (currentIndex >= terms.length - 1) return;\n          newIndex = currentIndex + 1;\n        }\n        \n        \/\/ Resetuj stan przy zmianie terminu\n        resetStateForModelChange();\n        \n        selectedTerm = terms[newIndex];\n        setActiveButton(\"terms\", selectedTerm);\n        if (selectedParam === \"precipsum\") {\n          renderPrecipsumRanges();\n        } else {\n          renderForecastHours();\n        }\n        updateCurrentConfig();\n        if (selectedModel && selectedParam) {\n          if (selectedParam === \"precipsum\" && selectedPrecipsumRange) {\n            showImages();\n          } else if (selectedParam !== \"precipsum\" && selectedHour) {\n            showImages();\n          }\n        }\n        preloadForecastImages();\n        return;\n      }\n      \n      \/\/ Tab - rozwini\u0119cie listy parametr\u00f3w\n      if (event.key === \"Tab\") {\n        event.preventDefault();\n        const paramSelect = document.getElementById(\"paramSelect\");\n        if (paramSelect) {\n          paramSelect.focus();\n          \/\/ Spr\u00f3buj otworzy\u0107 dropdown\n          const showEvent = new MouseEvent('mousedown', {\n            view: window,\n            bubbles: true,\n            cancelable: true\n          });\n          paramSelect.dispatchEvent(showEvent);\n        }\n        return;\n      }\n      \n      \/\/ A - animacja godzin prognozy\n      if (event.key === \"a\" || event.key === \"A\") {\n        event.preventDefault();\n        toggleAnimation();\n        return;\n      }\n      \n      \/\/ Strza\u0142ki lewo\/prawo - zmiana godzin prognozy\n      if (event.key === \"ArrowLeft\" || event.key === \"ArrowRight\") {\n        \/\/ Zatrzymaj animacj\u0119 przy r\u0119cznej nawigacji\n        if (isAnimating) {\n          toggleAnimation();\n        }\n        event.preventDefault();\n        \n        if (selectedParam === \"precipsum\") {\n          \/\/ Dla precipsum nawiguj tylko w obr\u0119bie wybranego typu (R03, R06, R12, R24)\n          const typeRanges = getFilteredPrecipsumRangesForModel(selectedModel, selectedPrecipsumType);\n          \n          if (typeRanges.length === 0) return;\n          \n          \/\/ Znajd\u017a aktualny indeks w obr\u0119bie typu\n          const currentIndex = typeRanges.indexOf(selectedPrecipsumRange);\n          \n          let newIndex;\n          if (event.key === \"ArrowLeft\") {\n            newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex;\n          } else {\n            newIndex = currentIndex < typeRanges.length - 1 ? currentIndex + 1 : currentIndex;\n          }\n          \n          if (newIndex === currentIndex) return;\n          \n          \/\/ Ustaw nowy zakres (typ pozostaje ten sam)\n          selectedPrecipsumRange = typeRanges[newIndex];\n          \n          \/\/ Zaktualizuj interfejs\n          const newKey = selectedPrecipsumType + \"_\" + selectedPrecipsumRange;\n          setActiveButton(\"hours\", newKey);\n          \n          \/\/ Zaktualizuj slider\n          const slider = document.getElementById(\"forecastHourSlider\");\n          if (slider) {\n            \/\/ Oblicz indeks w obr\u0119bie aktualnie wybranego typu\n            const typeIndex = typeRanges.indexOf(selectedPrecipsumRange);\n            slider.value = typeIndex;\n            document.getElementById(\"hourValue\").textContent = getPrecipsumHourValueText(selectedModel, selectedPrecipsumRange);\n          }\n          \n          showImages();\n          preloadForecastImages();\n          \n        } else {\n          \/\/ Dla zwyk\u0142ych parametr\u00f3w nawiguj po godzinach\n          \/\/ W trybie por\u00f3wnania u\u017cyj maksymalnego zakresu wybranych modeli\n          const hours = getMaxForecastHoursForCompareModels(selectedParam);\n          \n          if (hours.length === 0) return;\n          \n          \/\/ Znajd\u017a aktualny indeks\n          const currentIndex = hours.findIndex(function(h) { \n            return String(h).padStart(2, \"0\") === String(selectedHour).padStart(2, \"0\"); \n          });\n          \n          let newIndex;\n          if (event.key === \"ArrowLeft\") {\n            newIndex = currentIndex > 0 ? currentIndex - 1 : currentIndex;\n          } else {\n            newIndex = currentIndex < hours.length - 1 ? currentIndex + 1 : currentIndex;\n          }\n          \n          if (newIndex === currentIndex) return;\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            document.getElementById(\"hourValue\").textContent = getForecastHourDisplayText(selectedModel, selectedParam, newHour);\n          }\n          \n          if (isCompareMode) {\n            showCompareImages();\n            preloadCompareImages();\n          } else {\n            showImages();\n            preloadForecastImages();\n          }\n        }\n      }\n      \n      \/\/ Klawisze C - por\u00f3wnaj modele\n      if (event.key === \"c\" || event.key === \"C\") {\n        event.preventDefault();\n        toggleCompareMode();\n        return;\n      }\n    });\n    \n    \/\/ ===== FUNKCJE DLA TRYBU POR\u00d3WNANIA =====\n    \n    function toggleCompareMode() {\n      if (!isCompareMode) {\n        \/\/ W\u0142\u0105cz tryb por\u00f3wnania - poka\u017c modal wyboru modeli\n        showCompareModal();\n      } else {\n        \/\/ Wy\u0142\u0105cz tryb por\u00f3wnania\n        isCompareMode = false;\n        selectedModelsForCompare = []; \/\/ Wyczy\u015b\u0107 wybrane modele\n        document.getElementById(\"compareBtn\").classList.remove(\"active\");\n        \n        \/\/ Przywr\u00f3\u0107 normalny uk\u0142ad\n        var container = document.getElementById(\"images\");\n        container.style.display = \"block\";\n        container.style.gridTemplateColumns = \"\";\n        container.style.gap = \"\";\n        \n        \/\/ Przywr\u00f3\u0107 normalne pod\u015bwietlanie przycisk\u00f3w modeli\n        const modelDisplayNames = {};\n        models.forEach(function(m) {\n          modelDisplayNames[m] = modelsConfig[m].displayName;\n        });\n        renderButtons(\"models\", models, function(model) {\n          \/\/ Resetuj stan przy zmianie modelu\n          if (selectedModel !== model) {\n            resetStateForModelChange();\n          }\n          \n          selectedModel = model;\n          \n          \/\/ Prze\u0142aduj parametry dla nowego modelu (mog\u0105 by\u0107 inne)\n          initParameters();\n          \n          \/\/ Prze\u0142aduj godziny prognozy dla nowego modelu\n          if (selectedParam === \"precipsum\") {\n            renderPrecipsumRanges();\n          } else {\n            renderForecastHours();\n          }\n          \n          updateCurrentConfig();\n          if (selectedParam && selectedHour) {\n            showImages();\n          }\n          preloadForecastImages();\n        }, selectedModel, modelDisplayNames);\n        \n        \/\/ Prze\u0142aduj godziny dla aktualnie wybranego modelu\n        if (selectedParam === \"precipsum\") {\n          renderPrecipsumRanges();\n        } else {\n          renderForecastHours();\n        }\n        \n        showImages();\n        preloadForecastImages();\n      }\n    }\n    \n    function showCompareModal() {\n      var modal = document.getElementById(\"compareModal\");\n      var modelsList = document.getElementById(\"compareModelsList\");\n      \n      \/\/ Wyczy\u015b\u0107 list\u0119\n      modelsList.innerHTML = \"\";\n      \n      \/\/ Filtruj modele w zale\u017cno\u015bci od wybranego parametru\n      var modelKeys = Object.keys(modelsConfig);\n      var availableModels = modelKeys.filter(function(modelKey) {\n        \/\/ Sprawd\u017a czy model ma wybrany parametr\n        var modelParams = getAvailableParametersForModel(modelKey);\n        return modelParams.indexOf(selectedParam) !== -1;\n      });\n      \n      \/\/ Je\u015bli jest mniej ni\u017c 2 modele, poka\u017c komunikat\n      if (availableModels.length < 2) {\n        var noModelsMsg = document.createElement(\"div\");\n        noModelsMsg.style.padding = \"20px\";\n        noModelsMsg.style.textAlign = \"center\";\n        noModelsMsg.style.color = \"#666\";\n        noModelsMsg.textContent = \"Por\u00f3wnanie modeli nie jest mo\u017cliwe dla tego parametru - tylko jeden model ma ten parametr.\";\n        modelsList.appendChild(noModelsMsg);\n        modal.style.display = \"flex\";\n        return;\n      }\n      \n      \/\/ Dodaj dost\u0119pne modele jako opcje\n      availableModels.forEach(function(modelKey) {\n        var modelConfig = modelsConfig[modelKey];\n        var item = document.createElement(\"div\");\n        item.className = \"compare-model-item\";\n        item.textContent = modelConfig.displayName;\n        item.dataset.model = modelKey;\n        \n        \/\/ Zaznacz je\u015bli ju\u017c wybrany\n        if (selectedModelsForCompare.indexOf(modelKey) !== -1) {\n          item.classList.add(\"selected\");\n        }\n        \n        item.addEventListener(\"click\", function() {\n          var model = this.dataset.model;\n          var index = selectedModelsForCompare.indexOf(model);\n          \n          if (index !== -1) {\n            \/\/ Odznacz\n            selectedModelsForCompare.splice(index, 1);\n            this.classList.remove(\"selected\");\n          } else {\n            \/\/ Zaznacz tylko je\u015bli nie ma ju\u017c 4 modeli\n            if (selectedModelsForCompare.length < 4) {\n              selectedModelsForCompare.push(model);\n              this.classList.add(\"selected\");\n            }\n          }\n          \n          \/\/ Aktualizuj przycisk potwierdzenia\n          updateConfirmButton();\n        });\n        \n        modelsList.appendChild(item);\n      });\n      \n      updateConfirmButton();\n      modal.style.display = \"flex\";\n    }\n    \n    function updateConfirmButton() {\n      var confirmBtn = document.getElementById(\"confirmCompareBtn\");\n      var count = selectedModelsForCompare.length;\n      \n      if (count === 2 || count === 4) {\n        confirmBtn.disabled = false;\n        confirmBtn.textContent = \"Por\u00f3wnaj \" + count + \" modele\";\n      } else if (count === 3) {\n        confirmBtn.disabled = true;\n        confirmBtn.textContent = \"Wybierz 2 lub 4 modele (\" + count + \" wybrane)\";\n      } else {\n        confirmBtn.disabled = true;\n        confirmBtn.textContent = \"Wybierz modele (\" + count + \"\/4)\";\n      }\n    }\n    \n    function closeCompareModal() {\n      document.getElementById(\"compareModal\").style.display = \"none\";\n    }\n    \n    function confirmCompare() {\n      var count = selectedModelsForCompare.length;\n      if (count !== 2 && count !== 4) return;\n      \n      closeCompareModal();\n      isCompareMode = true;\n      document.getElementById(\"compareBtn\").classList.add(\"active\");\n      \n      \/\/ Prze\u0142aduj przyciski modeli \u017ceby pod\u015bwietli\u0107 wybrane\n      const modelDisplayNames = {};\n      models.forEach(function(m) {\n        modelDisplayNames[m] = modelsConfig[m].displayName;\n      });\n      renderButtons(\"models\", models, function(model) {\n        \/\/ W trybie por\u00f3wnania nie zmieniaj selectedModel\n      }, null, modelDisplayNames);\n      \n      \/\/ Prze\u0142aduj godziny dla maksymalnego zakresu wybranych modeli\n      if (selectedParam === \"precipsum\") {\n        renderPrecipsumRanges();\n      } else {\n        renderForecastHours();\n      }\n      \n      \/\/ Poka\u017c modele\n      showCompareImages();\n      preloadCompareImages();\n    }\n    \n    \/\/ Event listenery dla przycisk\u00f3w\n    document.getElementById(\"compareBtn\").addEventListener(\"click\", function() {\n      toggleCompareMode();\n    });\n    \n    document.getElementById(\"compareModalClose\").addEventListener(\"click\", function() {\n      closeCompareModal();\n    });\n    \n    document.getElementById(\"confirmCompareBtn\").addEventListener(\"click\", function() {\n      confirmCompare();\n    });\n    \n    \/\/ Zamknij modal klikaj\u0105c poza nim\n    document.getElementById(\"compareModal\").addEventListener(\"click\", function(e) {\n      if (e.target === this) {\n        closeCompareModal();\n      }\n    });\n    \n    updateCurrentConfig();\n  });\n  console.log(\"Autor Bart\u0142omiej Sobczyk 2026\");\n  <\/script>\n  \n  <\/body>\n  <\/html>\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: Typ sumy: 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-9 Wyb\u00f3r modelu &lt; &gt; Zmiana terminu startu \u2190 \u2192 Zmiana godzin prognozy Tab Rozwini\u0119cie parametr\u00f3w A Animacja godzin prognozy C Por\u00f3wnanie modeli Godzina [&hellip;]<\/p>\n","protected":false},"author":11,"featured_media":46961,"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>Kr\u00f3tkoterminowe - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB<\/title>\n<meta name=\"description\" content=\"Aplikacja przegl\u0105dania prognoz kr\u00f3tkoterminowych 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=48626\" \/>\n<meta property=\"og:locale\" content=\"pl_PL\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Prognozy Kr\u00f3tkoterminowe\" \/>\n<meta property=\"og:description\" content=\"Prognozy Kr\u00f3tkoterminowe IMGW-PIB\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cmm.imgw.pl\/?page_id=48626\" \/>\n<meta property=\"og:site_name\" content=\"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/Meteoimgw\/\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-27T15:32:17+00:00\" \/>\n<meta property=\"og:image\" content=\"http:\/\/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 Kr\u00f3tkoterminowe\" \/>\n<meta name=\"twitter:description\" content=\"Prognozy Kr\u00f3tkoterminowe IMGW-PIB\" \/>\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=48626\",\"url\":\"https:\/\/cmm.imgw.pl\/?page_id=48626\",\"name\":\"Kr\u00f3tkoterminowe - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB\",\"isPartOf\":{\"@id\":\"https:\/\/cmm.imgw.pl\/#website\"},\"datePublished\":\"2026-01-08T12:27:19+00:00\",\"dateModified\":\"2026-04-27T15:32:17+00:00\",\"description\":\"Aplikacja przegl\u0105dania prognoz kr\u00f3tkoterminowych IMGW-PIB\",\"breadcrumb\":{\"@id\":\"https:\/\/cmm.imgw.pl\/?page_id=48626#breadcrumb\"},\"inLanguage\":\"pl-PL\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cmm.imgw.pl\/?page_id=48626\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cmm.imgw.pl\/?page_id=48626#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cmm.imgw.pl\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Kr\u00f3tkoterminowe\"}]},{\"@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":"Kr\u00f3tkoterminowe - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","description":"Aplikacja przegl\u0105dania prognoz kr\u00f3tkoterminowych 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=48626","og_locale":"pl_PL","og_type":"article","og_title":"Prognozy Kr\u00f3tkoterminowe","og_description":"Prognozy Kr\u00f3tkoterminowe IMGW-PIB","og_url":"https:\/\/cmm.imgw.pl\/?page_id=48626","og_site_name":"Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","article_publisher":"https:\/\/www.facebook.com\/Meteoimgw\/","article_modified_time":"2026-04-27T15:32:17+00:00","og_image":[{"width":1356,"height":365,"url":"http:\/\/cmm.imgw.pl\/wp-content\/uploads\/2025\/10\/MODELE_LOGO_UNIFIKACJA_v2.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_title":"Prognozy Kr\u00f3tkoterminowe","twitter_description":"Prognozy Kr\u00f3tkoterminowe IMGW-PIB","twitter_site":"@IMGW_CMM","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/cmm.imgw.pl\/?page_id=48626","url":"https:\/\/cmm.imgw.pl\/?page_id=48626","name":"Kr\u00f3tkoterminowe - Laboratorium Modelowania Meteorologicznego CMOK IMGW-PIB","isPartOf":{"@id":"https:\/\/cmm.imgw.pl\/#website"},"datePublished":"2026-01-08T12:27:19+00:00","dateModified":"2026-04-27T15:32:17+00:00","description":"Aplikacja przegl\u0105dania prognoz kr\u00f3tkoterminowych IMGW-PIB","breadcrumb":{"@id":"https:\/\/cmm.imgw.pl\/?page_id=48626#breadcrumb"},"inLanguage":"pl-PL","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cmm.imgw.pl\/?page_id=48626"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/cmm.imgw.pl\/?page_id=48626#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cmm.imgw.pl\/"},{"@type":"ListItem","position":2,"name":"Kr\u00f3tkoterminowe"}]},{"@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\/48626"}],"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=48626"}],"version-history":[{"count":372,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/pages\/48626\/revisions"}],"predecessor-version":[{"id":50105,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/pages\/48626\/revisions\/50105"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=\/wp\/v2\/media\/46961"}],"wp:attachment":[{"href":"https:\/\/cmm.imgw.pl\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=48626"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}