PageSourceSearch

https://teatrbaniek.pl/script.js

js teatrbaniek.pl collected 2026-10-05 19:09:10 UTC 58,126 bytes, 992 lines download raw bytes

1(function () {
2  var header = document.querySelector("[data-header]");
3  var nav = document.querySelector("[data-nav]");
4  var menuToggle = document.querySelector("[data-menu-toggle]");
5  var lightbox = document.querySelector("[data-lightbox-modal]");
6  var lightboxImage = lightbox ? lightbox.querySelector("img") : null;
7  var lightboxClose = document.querySelector("[data-lightbox-close]");
8  var languageSwitch = document.querySelector(".language-switch");
9  var siteConfig = window.SITE_CONTENT || {};
10  var languages = siteConfig.languages || { pl: siteConfig };
11  var urlParams = new URLSearchParams(window.location.search);
12  var requestedLanguage = urlParams.get("lang");
13  function getPathLanguage() {
14    var segments = window.location.pathname.split("/").filter(Boolean);
15    for (var i = 0; i < segments.length; i += 1) {
16      if (languages[segments[i]]) return segments[i];
17    }
18    return "";
19  }
20  function getHostDefaultLanguage() {
21    var host = window.location.hostname.replace(/^www\./, "");
22    if (host === "bubble-theatre.com") return "en";
23    if (host === "teatrbaniek.pl") return "pl";
24    return siteConfig.defaultLanguage || "pl";
25  }
26  var pathLanguage = getPathLanguage();
27  var currentLanguage = requestedLanguage || pathLanguage || getHostDefaultLanguage();
28  if (!languages[currentLanguage]) currentLanguage = Object.keys(languages)[0] || "pl";
29  localStorage.setItem("siteLanguage", currentLanguage);
30  document.documentElement.lang = currentLanguage;
31  var siteContent = languages[currentLanguage] || {};
32  var subpageContent = {
33    "format-teatry.html": {
34      pl: {
35        seo: {
36          title: "Teatry, domy kultury i instytucje | Teatr Baniek Mydlanych",
37          description: "Profesjonalny spektakl baniek mydlanych dla teatrów, scen teatralnych, domów kultury, bibliotek i instytucji publicznych.",
38          ogTitle: "Spektakl dla teatrów i domów kultury",
39          ogDescription: "Gotowy program sceniczny z oprawą teatralną, techniką i interakcją z publicznością."
40        },
41        heroEyebrow: "Format sceniczny",
42        heroTitle: "Teatry i <span>instytucje</span>",
43        heroText: "Pełnowymiarowe widowisko rodzinne przygotowane dla scen teatralnych, domów kultury, bibliotek, centrów kultury i wydarzeń miejskich.",
44        heroCta: "ZAPROŚ NAS NA SCENĘ",
45        panelTitle: "Profesjonalna produkcja <span>gotowa na scenę</span>",
46        panelText: "To nie jest pojedynczy pokaz, ale kompletne widowisko z własną oprawą sceniczną, dekoracjami, kostiumami i doświadczonym zespołem technicznym. Przyjeżdżamy przygotowani do pracy na różnych typach scen i przestrzeni, z zapleczem organizacyjnym wypracowanym podczas ponad 1000 występów w Polsce i za granicą.",
47        bullets: ["profesjonalne dekoracje, kostiumy i teatralna oprawa wizualna,", "własna ekipa techniczna oraz przygotowane wymagania sceniczne,", "możliwość przyjazdu własnym transportem ciężarowym,", "doświadczenie ponad 1000 występów na scenach różnej skali i w wielu krajach,", "warianty językowe dla publiczności międzynarodowej."],
48        mediaEyebrow: "Photo-Video",
49        mediaTitle: "ZOBACZ FORMAT <span>NA SCENIE</span>",
50        videoText: "Materiał video formatu scenicznego",
51        ctaEyebrow: "Dla repertuaru i wydarzeń specjalnych",
52        ctaTitle: "Stwórzmy bańkową scenę w Twoim mieście",
53        leadTitle: "Napisz swój e-mail i wkrótce się skontaktujemy",
54        emailPlaceholder: "Twój e-mail",
55        leadButton: "SKONTAKTUJEMY SIĘ"
56      },
57      en: {
58        seo: {
59          title: "Theatres, Cultural Centers and Institutions | Soap Bubble Theatre",
60          description: "A professional soap bubble stage show for theatres, theatre venues, cultural centers, libraries and public institutions.",
61          ogTitle: "Stage show for theatres and cultural venues",
62          ogDescription: "A ready-made theatrical program with staging, technical crew and audience interaction."
63        },
64        heroEyebrow: "Stage format",
65        heroTitle: "Theatres and <span>institutions</span>",
66        heroText: "A full-scale family show prepared for theatre stages, cultural centers, libraries, civic venues and city events.",
67        heroCta: "BRING US ON STAGE",
68        panelTitle: "A professional production <span>ready for the stage</span>",
69        panelText: "This is not a single act, but a complete show with its own stage design, set
69s, costumes and experienced technical team. We arrive prepared to work on different types of stages and venues, with production know-how built through more than 1000 performances in Poland and abroad.",
70        bullets: ["professional sets, costumes and theatrical visual staging,", "our own technical crew and prepared stage requirements,", "possibility to arrive with our own truck transport,", "experience from 1000+ performances on stages of many scales and in many countries,", "language versions for international audiences."],
71        mediaEyebrow: "Photo-Video",
72        mediaTitle: "SEE THE FORMAT <span>ON STAGE</span>",
73        videoText: "Video material for the stage format",
74        ctaEyebrow: "For repertoire and special events",
75        ctaTitle: "Let us create a bubble stage in your city",
76        leadTitle: "Leave your email and we will contact you soon",
77        emailPlaceholder: "Your email",
78        leadButton: "WE WILL CONTACT YOU"
79      },
80      es: {
81        seo: {
82          title: "Teatros, centros culturales e instituciones | Teatro de Pompas de Jabón",
83          description: "Un espectáculo profesional de pompas de jabón para teatros, espacios escénicos, centros culturales, bibliotecas e instituciones públicas.",
84          ogTitle: "Espectáculo para teatros y centros culturales",
85          ogDescription: "Un programa escénico listo con puesta teatral, equipo técnico e interacción con el público."
86        },
87        heroEyebrow: "Formato escénico",
88        heroTitle: "Teatros e <span>instituciones</span>",
89        heroText: "Un espectáculo familiar de gran formato preparado para teatros, centros culturales, bibliotecas, espacios municipales y eventos urbanos.",
90        heroCta: "LLEVARNOS AL ESCENARIO",
91        panelTitle: "Una producción profesional <span>lista para escena</span>",
92        panelText: "No es un número aislado, sino un espectáculo completo con escenografía propia, decorados, vestuario y un equipo técnico experimentado. Llegamos preparados para trabajar en distintos tipos de escenarios y espacios, con experiencia acumulada en más de 1000 funciones en Polonia y en el extranjero.",
93        bullets: ["decorados, vestuario y puesta visual teatral profesional,", "equipo técnico propio y requisitos escénicos preparados,", "posibilidad de llegada con transporte de carga propio,", "experiencia de más de 1000 funciones en escenarios de diferentes escalas y países,", "versiones lingüísticas para público internacional."],
94        mediaEyebrow: "Foto-Video",
95        mediaTitle: "VER EL FORMATO <span>EN ESCENA</span>",
96        videoText: "Material de video del formato escénico",
97        ctaEyebrow: "Para programación y eventos especiales",
98        ctaTitle: "Creemos un escenario de pompas en tu ciudad",
99        leadTitle: "Deja tu email y nos pondremos en contacto pronto",
100        emailPlaceholder: "Tu email",
101        leadButton: "NOS PONDREMOS EN CONTACTO"
102      }
103    },
104    "format-galerie.html": {
105      pl: {
106        seo: { title: "Galerie i centra rozrywkowe | Teatr Baniek Mydlanych", description: "Efektowne rodzinne widowisko do galerii handlowych, centrów rozrywkowych i akcji promocyjnych.", ogTitle: "Show do galerii i centrów rozrywkowych", ogDescription: "Kolorowa atrakcja, która przyciąga rodziny i buduje ruch w obiekcie." },
107        heroEyebrow: "Event rodzinny",
108        heroTitle: "Galerie i <span>centra rozrywki</span>",
109        heroText: "Widowisko zaprojektowane tak, aby zatrzymać publiczność, stworzyć naturalny punkt programu i dać rodzinom powód do wspólnego zdjęcia, uśmiechu i powrotu.",
110        heroCta: "ZRÓBMY SHOW",
111        panelTitle: "Atrakcja, która <span>pracuje dla miejsca</span>",
112        panelText: "Format galerii może działać jako główny punkt dnia, część kampanii sezonowej albo cykl krótszych wejść w przestrzeni eventowej. Oprócz widowiska scenicznego możemy przygotować animacje wśród publiczności, w tym efektowne zamknięcie dziecka w dużej bańce.",
113        bullets: ["show do pasaży, scen mobilnych i przestrzeni centralnych,", "animacje rodzinne w przestrzeni galer
113ii, np. zamknięcie dziecka w dużej bańce,", "programy na ferie, Dzień Dziecka, Mikołajki i akcje tematyczne,", "efektowne momenty do zdjęć i materiałów promocyjnych,", "elastyczny czas trwania i liczba wejść w ciągu dnia."],
114        mediaEyebrow: "Photo-Video",
115        mediaTitle: "ZOBACZ EVENT <span>W PRZESTRZENI</span>",
116        videoText: "Materiał video z eventu",
117        ctaEyebrow: "Promocje, święta, rodzinne weekendy",
118        ctaTitle: "Dodaj do programu efekt wow",
119        leadTitle: "Napisz swój e-mail i wkrótce się skontaktujemy",
120        emailPlaceholder: "Twój e-mail",
121        leadButton: "SKONTAKTUJEMY SIĘ"
122      },
123      en: {
124        seo: { title: "Malls and Entertainment Centers | Soap Bubble Theatre", description: "A spectacular family show for shopping malls, entertainment centers and promotional campaigns.", ogTitle: "Show for malls and entertainment centers", ogDescription: "A colorful attraction that draws families in and builds foot traffic." },
125        heroEyebrow: "Family event",
126        heroTitle: "Malls and <span>entertainment centers</span>",
127        heroText: "A show designed to stop the audience, create a natural program highlight and give families a reason for a shared photo, smile and return visit.",
128        heroCta: "MAKE IT A SHOW",
129        panelTitle: "An attraction that <span>works for the venue</span>",
130        panelText: "The mall format can work as the main event of the day, part of a seasonal campaign or a series of shorter appearances in an event space. In addition to the stage show, we can prepare audience animations, including the spectacular moment of placing a child inside a giant bubble.",
131        bullets: ["show for passages, mobile stages and central spaces,", "family animations in the mall space, for example a child inside a giant bubble,", "programs for holidays, Children's Day, Christmas events and themed campaigns,", "strong photo moments for promotional materials,", "flexible duration and number of entries during the day."],
132        mediaEyebrow: "Photo-Video",
133        mediaTitle: "SEE THE EVENT <span>IN THE SPACE</span>",
134        videoText: "Video material from the event",
135        ctaEyebrow: "Promotions, holidays, family weekends",
136        ctaTitle: "Add a wow moment to the program",
137        leadTitle: "Leave your email and we will contact you soon",
138        emailPlaceholder: "Your email",
139        leadButton: "WE WILL CONTACT YOU"
140      },
141      es: {
142        seo: { title: "Centros comerciales y de entretenimiento | Teatro de Pompas de Jabón", description: "Un espectáculo familiar impactante para centros comerciales, espacios de entretenimiento y campañas promocionales.", ogTitle: "Show para centros comerciales y de entretenimiento", ogDescription: "Una atracción colorida que atrae familias y aumenta el flujo de visitantes." },
143        heroEyebrow: "Evento familiar",
144        heroTitle: "Centros comerciales y <span>de entretenimiento</span>",
145        heroText: "Un espectáculo diseñado para detener al público, crear un punto natural del programa y dar a las familias un motivo para una foto, una sonrisa y una nueva visita.",
146        heroCta: "HAGAMOS SHOW",
147        panelTitle: "Una atracción que <span>trabaja para el lugar</span>",
148        panelText: "El formato para centros comerciales puede funcionar como punto principal del día, parte de una campaña de temporada o una serie de apariciones breves en el espacio de eventos. Además del show escénico, podemos preparar animaciones con el público, incluido el efecto de encerrar a un niño dentro de una pompa gigante.",
149        bullets: ["show para pasillos, escenarios móviles y espacios centrales,", "animaciones familiares en el centro comercial, por ejemplo un niño dentro de una pompa gigante,", "programas para vacaciones, Día del Niño, Navidad y acciones temáticas,", "momentos visuales fuertes para fotos y materiales promocionales,", "duración flexible y número de pases durante el día."],
150        mediaEyebrow: "Foto-Video",
151        mediaTitle: "VER EL EVENTO <span>EN EL ESPACIO</span>",
152        videoText: "Material de video del evento",
153        ctaEyebrow: "Promociones, fiestas, fines de semana familiares",
154        ctaTitle: "Añade un efecto wow al programa",
155        leadTitle: "Deja tu email y nos pondremos en contacto pronto",
156        emailPlaceholder: "Tu email",
157        leadButton: "NOS PONDREMOS EN CONTACTO"
158      }
159    },
160    "format-tury.html": {
161      pl: {
162        seo: { title: "Tury i wyjazdy | Teatr Baniek Mydlanych", description: "Międzynarodowe widowisko objazdowe gotowe do pracy na trasach, festiwalach i scenach różnej skali.", ogTitle: "Międzynarodowe show objazdowe", ogDescription: "Format gotowy do tras koncertowych, występów zagranicznych i dużych przestrzeni eventowych." },
163        heroEyebrow: "Touring show",
164        heroTitle: "Międzynarodowe <span>trasy</span>",
165        heroText: "Spektakl zaprojektowany jako mobilne, skalowalne widowisko rodzinne: od sal teatralnych po duże hale, festiwale, eventy i programy wyjazdowe.",
166        heroCta: "RUSZAJMY W TRASĘ",
167        panelTitle: "Show gotowe <span>do drogi</span>",
168        panelText: "To międzynarodowa produkcja objazdowa z doświadczeniem ponad 1000 zagranych spektakli w więcej niż 8 krajach świata, m.in. w Niemczech, Czechach, Polsce, Zjednoczonych Emiratach Arabskich, Grecji, Hiszpanii, Izraelu i Ukrainie. Pracujemy w modelu, który łączy profesjonalny management, własną ekipę techniczną i kompaktowy zespół gotowy do sprawnej logistyki między miastami oraz krajami.",
169        bullets: ["ponad 1000 zagranych spektakli na scenach różnej skali,", "doświadczenie w ponad 8 krajach: Niemcy, Czechy, Polska, ZEA, Grecja, Hiszpania, Izrael, Ukraina,", "profesjonalny management produkcji i własna ekipa techniczna,", "adaptacja formatu pod przeloty lotnicze oraz przejazdy naziemne,", "kompaktowy skład zespołu przy zachowaniu jakości pełnego widowiska."],
170        mediaEyebrow: "Video",
171        mediaTitle: "ZOBACZ SHOW <span>W TRASIE</span>",
172        videoText: "Materiał video z trasy",
173        leadTitle: "Napisz swój e-mail i wkrótce się skontaktujemy",
174        emailPlaceholder: "Twój e-mail",
175        leadButton: "SKONTAKTUJEMY SIĘ"
176      },
177      en: {
178        seo: { title: "Tours and Travel Programs | Soap Bubble Theatre", description: "An international touring show ready for routes, festivals and venues of different scales.", ogTitle: "International touring show", ogDescription: "A format ready for concert tours, international performances and large event spaces." },
179        heroEyebrow: "Touring show",
180        heroTitle: "International <span>tours</span>",
181        heroText: "A performance designed as a mobile, scalable family show: from theatre halls to large arenas, festivals, events and travel programs.",
182        heroCta: "LET'S TOUR",
183        panelTitle: "A show <span>ready to travel</span>",
184        panelText: "This is an international touring production with experience from more than 1000 performed shows in over 8 countries, including Germany, Czech Republic, Poland, the United Arab Emirates, Greece, Spain, Israel and Ukraine. We work in a model that combines professional management, our own technical crew and a compact team ready for efficient logistics between cities and countries.",
185        bullets: ["more than 1000 performances on stages of different scales,", "experience in 8+ countries: Germany, Czech Republic, Poland, UAE, Greece, Spain, Israel, Ukraine,", "professional production management and our own technical crew,", "format adapted for air travel and ground transport,", "compact team while keeping the quality of a full show."],
186        mediaEyebrow: "Video",
187        mediaTitle: "SEE THE SHOW <span>ON TOUR</span>",
188        videoText: "Video material from the tour",
189        leadTitle: "Leave your email and we will contact you soon",
190        emailPlaceholder: "Your email",
191        leadButton: "WE WILL CONTACT YOU"
192      },
193      es: {
194        seo: { title: "Giras y programas de viaje | Teatro de Pompas de Jabón", description: "Un espectáculo internacional itinerante listo para giras, festivales y escenarios de diferentes escalas.", ogTitle: "Show internacional de gira", ogDescription: "Un formato listo para giras, actuaciones internacionales y grandes espacios de eventos." },
195        heroEyebrow: "Show de gira",
196        heroTitle: "Giras <span>internacionales</span>",
197        heroText: "Un espectáculo diseñado como show familiar móvil y escalable: desde salas teatrales hasta grandes pabellones, festivales, eventos y programas de viaje.",
198        heroCta: "VAMOS DE GIRA",
199        panelTitle: "Un show <span>listo para viajar</span>",
200        panelText: "Es una producción internacional itinerante con experiencia de más de 1000 funciones realizadas en más de 8 países, entre ellos Alemania, Chequia, Polonia, Emiratos Árabes Unidos, Grecia, España, Israel y Ucrania. Trabajamos con un modelo que combina management profesional, equipo técnico propio y un equipo compacto preparado para una logística eficiente entre ciudades y países.",
201        bullets: ["más de 1000 funciones en escenarios de distintas escalas,", "experiencia en más de 8 países: Alemania, Chequia, Polonia, EAU, Grecia, España, Israel, Ucrania,", "management profesional de producción y equipo técnico propio,", "adaptación del formato para vuelos y traslados terrestres,", "equipo compacto manteniendo la calidad de un espectáculo completo."],
202        mediaEyebrow: "Video",
203        mediaTitle: "VER EL SHOW <span>EN GIRA</span>",
204        videoText: "Material de video de la gira",
205        leadTitle: "Deja tu email y nos pondremos en contacto pronto",
206        emailPlaceholder: "Tu email",
207        leadButton: "NOS PONDREMOS EN CONTACTO"
208      }
209    },
210    "format-urodziny.html": {
211      pl: {
212        seo: { title: "Urodziny i przyjęcia prywatne | Teatr Baniek Mydlanych", description: "Kameralne widowisko baniek mydlanych na urodziny, komunie, chrzciny i rodzinne okazje.", ogTitle: "Magiczne urodziny i przyjęcia", ogDescription: "Rodzinny format show pełen baniek, humoru i wspólnej zabawy." },
213        heroEyebrow: "Kameralna magia",
214        heroTitle: "Urodziny i <span>przyjęcia</span>",
215        heroText: "Mini widowisko dla rodzinnych okazji: efektowne, ciepłe, interaktywne i dopasowane do wieku dzieci oraz charakteru spotkania.",
216        heroCta: "LET'S PARTY!",
217        panelTitle: "Mały format, <span>duży zachwyt</span>",
218        panelText: "Przygotowujemy pokaz tak, aby działał w domu, sali przyjęć, ogrodzie lub kameralnej przestrzeni eventowej. Liczy się bliskość, śmiech i moment, w którym solenizant staje się częścią bańkowej historii.",
219        bullets: ["urodziny, komunie, chrzciny i rodzinne spotkania,", "program dopasowany do wieku i liczby gości,", "bezpieczna interakcja i wspólna zabawa,", "możliwość krótszego lub rozszerzonego formatu."],
220        mediaEyebrow: "Photo-Video",
221        mediaTitle: "ZOBACZ MAGIĘ <span>Z BLISKA</span>",
222        videoText: "Materiał video z przyjęcia",
223        ctaEyebrow: "Urodziny, komunie, rodzinne okazje",
224        ctaTitle: "Niech przyjęcie zapamiętają wszyscy",
225        leadTitle: "Napisz swój e-mail i wkrótce się skontaktujemy",
226        emailPlaceholder: "Twój e-mail",
227        leadButton: "SKONTAKTUJEMY SIĘ"
228      },
229      en: {
230        seo: { title: "Birthdays and Private Parties | Soap Bubble Theatre", description: "A chamber soap bubble show for birthdays, communions, christenings and family occasions.", ogTitle: "Magical birthdays and parties", ogDescription: "A family show format full of bubbles, humor and shared fun." },
231        heroEyebrow: "Intimate magic",
232        heroTitle: "Birthdays and <span>parties</span>",
233        heroText: "A mini show for family occasions: impressive, warm, interactive and adapted to the children's age and the character of the gathering.",
234        heroCta: "LET'S PARTY!",
235        panelTitle: "Small format, <span>big delight</span>",
236        panelText: "We prepare the show so it works at home, in a party room, garden or intimate event space. What matters is closeness, laughter and the moment when the birthday child becomes part of the bubble story.",
237        bullets: ["birthdays, communions, christenings and family gatherings,", "program adapted to age and number of guests,", "safe interaction and shared fun,", "possibility of a shorter or extended format."],
238        mediaEyebrow: "Photo-Video",
239        mediaTitle: "SEE THE MAGIC <span>UP CLOSE</span>",
240        videoText: "Video material from the party",
241        ctaEyebrow: "Birthdays, communions, family occasions",
242        ctaTitle: "Make the party unforgettable for everyone",
243        leadTitle: "Leave your email and we will contact you soon",
244        emailPlaceholder: "Your email",
245        leadButton: "WE WILL CONTACT YOU"
246      },
247      es: {
248        seo: { title: "Cumpleaños y fiestas privadas | Teatro de Pompas de Jabón", description: "Un show íntimo de pompas de jabón para cumpleaños, comuniones, bautizos y ocasiones familiares.", ogTitle: "Cumpleaños y fiestas mágicas", ogDescription: "Un formato familiar lleno de pompas, humor y diversión compartida." },
249        heroEyebrow: "Magia íntima",
250        heroTitle: "Cumpleaños y <span>fiestas</span>",
251        heroText: "Un mini espectáculo para ocasiones familiares: impactante, cálido, interactivo y adaptado a la edad de los niños y al carácter del encuentro.",
252        heroCta: "LET'S PARTY!",
253        panelTitle: "Formato pequeño, <span>gran asombro</span>",
254        panelText: "Preparamos el show para que funcione en casa, en un salón de fiestas, jardín o espacio íntimo de eventos. Lo importante es la cercanía, la risa y el momento en que el homenajeado se convierte en parte de la historia de pompas.",
255        bullets: ["cumpleaños, comuniones, bautizos y encuentros familiares,", "programa adaptado a la edad y al número de invitados,", "interacción segura y diversión compartida,", "posibilidad de formato más corto o ampliado."],
256        mediaEyebrow: "Foto-Video",
257        mediaTitle: "VER LA MAGIA <span>DE CERCA</span>",
258        videoText: "Material de video de la fiesta",
259        ctaEyebrow: "Cumpleaños, comuniones, ocasiones familiares",
260        ctaTitle: "Haz que todos recuerden la fiesta",
261        leadTitle: "Deja tu email y nos pondremos en contacto pronto",
262        emailPlaceholder: "Tu email",
263        leadButton: "NOS PONDREMOS EN CONTACTO"
264      }
265    },
266    "bohaterowie.html": {
267      pl: {
268        seo: { title: "Bohaterowie spektaklu | Teatr Baniek Mydlanych", description: "Poznaj bohaterów Krainy Mydlanych Baniek: Crodziejska Wróżka, Mistrz Bulbulas, Bąbelek, Mydlaneczka i Profesor Cudak.", ogTitle: "Bohaterowie spektaklu", ogDescription: "Pięć barwnych postaci, które prowadzą publiczność przez bańkową przygodę." },
269        heroEyebrow: "Świat spektaklu",
270        heroTitle: "Bohaterowie <span>bańkowej przygody</span>",
271        heroText: "Pięć barwnych charakterów z Krainy Mydlanych Baniek: przyjaźń, fantazja, humor i magia, która prowadzi publiczność przez cały spektakl.",
272        heroCta: "ZAPROŚ BOHATERÓW",
273        mediaEyebrow: "Postacie",
274        mediaTitle: "MAGICZNA <span>DRUŻYNA</span>",
275        cards: [
276          { subtitle: "główna wróżka krainy", title: "Crodziejska Wróżka", text: "Mądra opiekunka i nauczycielka młodych czarodziejów, która przypomina, że prawdziwa magia zaczyna się od dobrego serca.", traits: "Gracyjna, wielkoduszna, inspirująca" },
277          { subtitle: "mistrz szalonych mikstur", title: "Mistrz Bulbulas", text: "Ekscentryczny twórca najbardziej nieprzewidywalnych baniek. Groźny, zabawny i w głębi serca dużo bardziej czuły, niż przyznaje.", traits: "Teatralny, charyzmatyczny, przewrotny" },
278          { subtitle: "wesoły wynalazca", title: "Bąbelek", text: "Nieustanny eksperymentator, który wymyśla nowe bańkowe mechanizmy i potrafi zmienić każdą pomyłkę w przygodę.", traits: "Pomysłowy, odważny, optymistyczny" },
279          { subtitle: "uczennica szkoły Bul-Bul", title: "Mydlaneczka", text: "Najlepsza uczennica magicznej szkoły, która wierzy w dobro, fantazję i małe cuda ukryte w każdej bańce.", traits: "Wesoła, romantyczna, energiczna" },
280          { subtitle: "dobry profesor", title: "Profesor Cudak", text: "Roztrzepany, genialny twórca niezwykłych maszyn do baniek, który w każdym małym eksperymencie widzi wielkie odkrycie.", traits: "Dobry, zabawny, pełen wyobraźni" }
281        ],
282        ctaEyebrow: "Postacie, kostiumy, materiały promocyjne",
283        ctaTitle: "Zaprosimy publiczność do świata baniek",
284        heroCtaBottom: "ZAMÓW SPEKTAKL"
285      },
286      en: {
287        seo: { title: "Show Characters | Soap Bubble Theatre", description: "Meet the characters from the Land of Soap Bubbles: Miss Wizardson, Master Blowcheater, Saponice, Bubblittle and Academician Wackadoodle.", ogTitle: "Show characters", ogDescription: "Five colorful characters guiding the audience through the bubble adventure." },
288        heroEyebrow: "World of the show",
289        heroTitle: "Characters of the <span>bubble adventure</span>",
290        heroText: "Five vivid personalities from the Land of Soap Bubbles: friendship, fantasy, humor and magic leading the audience through the whole performance.",
291        heroCta: "INVITE THE CHARACTERS",
292        mediaEyebrow: "Characters",
293        mediaTitle: "THE MAGICAL <span>TEAM</span>",
294        cards: [
295          { subtitle: "main fairy of the land", title: "Miss Wizardson", text: "A wise guardian and teacher of young magicians who reminds everyone that true magic begins with a kind heart.", traits: "Graceful, generous, inspiring" },
296          { subtitle: "master of wild mixtures", title: "Master Blowcheater", text: "An ecce
296ntric creator of the most unpredictable bubbles. Fierce, funny and much softer at heart than he wants to admit.", traits: "Theatrical, charismatic, mischievous" },
297          { subtitle: "joyful inventor", title: "Saponice", text: "A tireless experimenter who invents new bubble mechanisms and can turn every mistake into an adventure.", traits: "Inventive, brave, optimistic", image: "assets/character-mylochka.jpg" },
298          { subtitle: "student of Bul-Bul school", title: "Bubblittle", text: "The top student of the magical school, believing in kindness, imagination and small wonders hidden inside every bubble.", traits: "Cheerful, romantic, energetic", image: "assets/character-puzyrik.jpg" },
299          { subtitle: "kind professor", title: "Academician Wackadoodle", text: "A distracted, brilliant maker of bubble machines who sees a great discovery in every tiny experiment.", traits: "Kind, funny, imaginative" }
300        ],
301        ctaEyebrow: "Characters, costumes, promotional materials",
302        ctaTitle: "We will invite the audience into the world of bubbles",
303        heroCtaBottom: "BOOK THE SHOW"
304      },
305      es: {
306        seo: { title: "Personajes del espectáculo | Teatro de Pompas de Jabón", description: "Conoce a los personajes del País de las Pompas de Jabón: Hada Encantadora, Maestro Enflautador, Burbujín, Jaboncita y Científico Loco.", ogTitle: "Personajes del espectáculo", ogDescription: "Cinco personajes coloridos que guían al público por la aventura de pompas." },
307        heroEyebrow: "Mundo del espectáculo",
308        heroTitle: "Personajes de la <span>aventura de pompas</span>",
309        heroText: "Cinco personalidades brillantes del País de las Pompas de Jabón: amistad, fantasía, humor y magia que guían al público durante todo el espectáculo.",
310        heroCta: "INVITAR A LOS PERSONAJES",
311        mediaEyebrow: "Personajes",
312        mediaTitle: "EL EQUIPO <span>MÁGICO</span>",
313        cards: [
314          { subtitle: "hada principal del país", title: "Hada Encantadora", text: "Una sabia guardiana y maestra de jóvenes magos que recuerda que la verdadera magia empieza en un corazón bondadoso.", traits: "Elegante, generosa, inspiradora" },
315          { subtitle: "maestro de mezclas locas", title: "Maestro Enflautador", text: "Un creador excéntrico de las pompas más imprevisibles. Amenazante, divertido y mucho más tierno de lo que admite.", traits: "Teatral, carismático, travieso" },
316          { subtitle: "inventor alegre", title: "Burbujín", text: "Un experimentador incansable que inventa nuevos mecanismos de pompas y convierte cada error en una aventura.", traits: "Ingenioso, valiente, optimista" },
317          { subtitle: "alumna de la escuela Bul-Bul", title: "Jaboncita", text: "La mejor alumna de la escuela mágica, que cree en la bondad, la fantasía y los pequeños milagros escondidos en cada pompa.", traits: "Alegre, romántica, energética" },
318          { subtitle: "profesor bondadoso", title: "Científico Loco", text: "Un inventor despistado y genial de máquinas de pompas que ve un gran descubrimiento en cada pequeño experimento.", traits: "Bondadoso, divertido, imaginativo" }
319        ],
320        ctaEyebrow: "Personajes, vestuario, materiales promocionales",
321        ctaTitle: "Invitaremos al público al mundo de las pompas",
322        heroCtaBottom: "RESERVAR SHOW"
323      }
324    }
325  };
326
327  function setText(selector, value) {
328    var element = document.querySelector(selector);
329    if (element && typeof value === "string") element.textContent = value;
330  }
331
332  function setHtml(selector, value) {
333    var element = document.querySelector(selector);
334    if (element && typeof value === "string") element.innerHTML = value;
335  }
336
337  function setMeta(selector, value) {
338    var element = document.querySelector(selector);
339    if (element && typeof value === "string") element.setAttribute("content", value);
340  }
341
342  function renderSeo() {
343    var seo = siteContent.seo || {};
344    if (seo.title) document.title = seo.title;
345    setMeta('meta[name="description"]', seo.description);
346    setMeta('meta[name="keywords"]', seo.keywords);
347    setMeta('meta[property="og:title"]', seo.ogTitle || seo.title);
348    setMeta('meta[property="og:description"]', seo.ogDescription || seo.description);
349    setMeta('meta[property="og:locale"]', seo.ogLocale);
350  }
351
352  function setLabelText(selector, value) {
353    var label = document.querySelector(selector);
354    if (!label || typeof value !== "string") return;
355    var field = label.querySelector("input, textarea, select");
356    label.textContent = value + " ";
357    if (field) label.appendChild(field);
358  }
359
360  function escapeHtml(value) {
361    return String(value || "")
362      .replace(/&/g, "&amp;")
363      .replace(/</g, "&lt;")
364      .replace(/>/g, "&gt;")
365      .replace(/"/g, "&quot;")
366      .replace(/'/g, "&#039;");
367  }
368
369  function formatEventDate(value) {
370    if (!value) return "";
371    var date = new Date(value + "T12:00:00");
372    if (Number.isNaN(date.getTime())) return value;
373    var locale = currentLanguage === "en" ? "en-GB" : currentLanguage === "es" ? "es-ES" : "pl-PL";
374    return new Intl.DateTimeFormat(locale, {
375      day: "2-digit",
376      month: "short",
377      year: "numeric"
378    }).format(date);
379  }
380
381  function fallbackImage(title, colors) {
382    var svg =
383      '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800">' +
384      '<defs><linearGradient id="g" x1="0" x2="1" y1="0" y2="1"><stop stop-color="' +
385      colors[0] +
386      '"/><stop offset="1" stop-color="' +
387      colors[1] +
388      '"/></linearGradient><filter id="blur"><feGaussianBlur stdDeviation="22"/></filter></defs>' +
389      '<rect width="1200" height="800" fill="url(#g)"/>' +
390      '<circle cx="240" cy="640" r="210" fill="#ff3f9a" opacity=".28" filter="url(#blur)"/>' +
391      '<circle cx="920" cy="210" r="250" fill="#59d8ff" opacity=".24" filter="url(#blur)"/>' +
392      '<circle cx="740" cy="470" r="180" fill="none" stroke="#fff" stroke-width="10" opacity=".55"/>' +
393      '<circle cx="812" cy="390" r="44" fill="#fff" opacity=".72"/>' +
394      '<text x="70" y="690" fill="#fff" font-family="Arial, Helvetica, sans-serif" font-size="72" font-weight="900">' +
395      title +
396      "</text></svg>";
397    return "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
398  }
399
400  function renderEditableContent() {
401    var ui = siteContent.ui || {};
402    var logoImage = document.querySelector(".logo img");
403
404    renderSeo();
405
406    if (logoImage && siteContent.logo) {
407      logoImage.src = resolveAssetPath(siteContent.logo.src || "assets/logo.png");
408      logoImage.alt = siteContent.logo.alt || "Teatr Baniek Mydlanych";
409    }
410
411    setText('.main-nav a[href="#about"]', ui.navAbout);
412    setText('.main-nav a[href="#offer"]', ui.navOffer);
413    setText('.main-nav a[href="#events"]', ui.navEvents);
414    setText('.main-nav a[href="#gallery"]', ui.navGallery);
415    document.querySelectorAll('.main-nav a[href*="bohaterowie.html"]').forEach(function (link) {
416      link.textContent = ui.navCharacters || link.textContent;
417    });
418    setText('.main-nav a[href="#faq"]', ui.navFaq);
419    setText('.main-nav a[href="#contact"]', ui.navContact);
420
421    setText(".hero-copy .eyebrow", ui.heroEyebrow);
422    setText(".hero h1 span:nth-child(1)", ui.heroLine1);
423    setText(".hero h1 span:nth-child(2)", ui.heroLine2);
424    setText(".hero h1 span:nth-child(3)", ui.heroLine3);
425    setText(".hero-text", ui.heroText);
426    setText(".hero-buttons .btn-primary", ui.ctaOrder);
427    var videoButtonText = document.querySelector("[data-video-trigger]");
428    if (videoButtonText && ui.ctaVideo) {
429      videoButtonText.childNodes[videoButtonText.childNodes.length - 1].textContent = " " + ui.ctaVideo;
430    }
431
432    setText("#offer .section-heading p", ui.offerEyebrow);
433    setHtml("#offer-title", ui.offerTitle);
434
435    if (Array.isArray(siteContent.offers)) {
436      document.querySelectorAll(".offer-card").forEach(function (card, index) {
437        var offer = siteContent.offers[index];
438        if (!offer) return;
439        var title = card.querySelector("h3");
440        var text = card.querySelector(".card-body p");
441        var list = card.querySelector("ul");
442        var link = card.querySelector(".btn-card");
443        if (title) title.textContent = offer.title;
444        if (text) text.textContent = offer.text;
445        if (list) {
446          list.innerHTML = offer.bullets.map(function (item) {
447            return "<li>" + escapeHtml(item) + "</li>";
448          }).join("");
449        }
450        if (link) {
451          link.textContent = offer.cta || ui.learnMore || link.textContent;
452          if (offer.url) link.href = withLanguage(offer.url);
453        }
454      });
455    }
456
457    setHtml("#showcase-title", ui.showcaseTitle);
458    setText(".showcase-copy p", ui.showcaseText);
459    setText(".showcase-copy .btn-primary", ui.ctaOrder);
460
461    setText("#about .eyebrow", ui.aboutEyebrow);
462    setText("#about-title", ui.aboutTitle);
463    setText(".split-copy > p:not(.eyebrow)", ui.aboutText);
464    setText(".stats div:nth-child(1) strong", ui.statOneValue);
465    setText(".stats div:nth-child(1) span", ui.statOneLabel);
466    setText(".stats div:nth-child(2) strong", ui.statTwoValue);
467    setText(".stats div:nth-child(2) span", ui.statTwoLabel);
468    setText(".stats div:nth-child(3) strong", ui.statThreeValue);
469    setText(".stats div:nth-child(3) span", ui.statThreeLabel);
470
471    setText("#places .eyebrow", ui.placesEyebrow);
472    setText("#places-title", ui.placesTitle);
473    setText("#places .narrow p:last-child", ui.placesText);
474
475    setText(".scale .section-heading p", ui.scaleEyebrow);
476    setHtml("#scale-title", ui.scaleTitle);
477    setText(".scale-item:nth-child(1) strong", ui.scaleMiniTitle);
478    setText(".scale-item:nth-child(1) span", ui.scaleMiniText);
479    setText(".scale-item:nth-child(2) strong", ui.scaleStageTitle);
480    setText(".scale-item:nth-child(2) span", ui.scaleStageText);
481    setText(".scale-item:nth-child(3) strong", ui.scaleEventTitle);
482    setText(".scale-item:nth-child(3) span", ui.scaleEventText);
483
484    setText("#gallery .section-heading p", ui.galleryEyebrow);
485    setHtml("#gallery-title", ui.galleryTitle);
486    setText(".video-placeholder strong", ui.videoPlaceholderTitle);
487    setText(".video-placeholder span", ui.videoPlaceholderText);
488
489    setText("#faq .section-heading p", ui.faqEyebrow);
490    setHtml("#faq-title", ui.faqTitle);
491
492    setText("#order .eyebrow", ui.orderEyebrow);
493    setText("#order-title", ui.orderTitle);
494    setText(".step:nth-child(1) strong", ui.stepOneTitle);
495    setText(".step:nth-child(1) p", ui.stepOneText);
496    setText(".step:nth-child(2) strong", ui.stepTwoTitle);
497    setText(".step:nth-child(2) p", ui.stepTwoText);
498    setText(".step:nth-child(3) strong", ui.stepThreeTitle);
499    setText(".step:nth-child(3) p", ui.stepThreeText);
500
501    setLabelText('[data-contact-form] label:nth-of-type(1)', ui.formName);
502    setLabelText('[data-contact-form] label:nth-of-type(2)', ui.formEmail);
503    setLabelText('[data-contact-form] label:nth-of-type(3)', ui.formPhone);
504    setLabelText('[data-contact-form] label:nth-of-type(4)', ui.formMessage);
505    setText('[data-contact-form] button[type="submit"]', ui.formSubmit);
506    setText(".mobile-cta", ui.ctaOrder);
507
508    if (siteContent.contact) {
509      setText("[data-contact-eyebrow]", siteContent.contact.eyebrow);
510      setText("[data-contact-title]", siteContent.contact.headline);
511      setText("[data-contact-text]", siteContent.contact.text);
512      setHtml("[data-contact-details]", siteContent.contact.detailsHtml);
513    }
514
515    var faqList = document.querySelector("[data-faq-list]");
516    if (faqList && Array.isArray(siteContent.faq)) {
517      faqList.innerHTML = siteContent.faq
518        .map(function (item) {
519          return (
520            '<div class="faq-item">' +
521            '<button type="button" aria-expanded="false">' +
522            escapeHtml(item.question) +
523            "</button>" +
524            '<div class="faq-panel">' +
525            escapeHtml(item.answer) +
526            "</div>" +
527            "</div>"
528          );
529        })
530        .join("");
531    }
532
533    if (siteContent.events) {
534      setText("[data-events-eyebrow]", siteContent.events.eyebrow);
535      setHtml("[data-events-title]", siteContent.events.headline);
536      setText("[data-events-text]", siteContent.events.text);
537
538      var eventsList = document.querySelector("[data-events-list]");
539      if (eventsList) {
540        var items = Array.isArray(siteContent.events.items) ? siteContent.events.items : [];
541        if (items.length) {
542          var visibleCount = Number(siteContent.events.visibleCount) || items.length;
543          eventsList.innerHTML = items
544            .slice(0, visibleCount)
545            .map(function (eventItem) {
546              var ticketUrl = eventItem.ticketUrl || eventItem.url || "";
547              var labels = siteContent.events.labels || {};
548              var ticketLabel = eventItem.ticketLabel || eventItem.label || "KUP BILET";
549              var dateTime = [formatEventDate(eventItem.date), eventItem.time].filter(
549Boolean).join(" · ");
550              var action = ticketUrl
551                ? '<a class="event-ticket" href="' +
552                  escapeHtml(ticketUrl) +
553                  '" target="_blank" rel="noopener">' +
554                  escapeHtml(ticketLabel) +
555                  "</a>"
556                : '<span class="event-note">' + escapeHtml(eventItem.note || labels.ticketsSoon || "BILETY WKRÓTCE") + "</span>";
557
558              return (
559                '<article class="event-card reveal">' +
560                '<div class="event-row event-city"><span>' + escapeHtml(labels.city || "Miasto") + "</span><strong>" +
561                escapeHtml(eventItem.city) +
562                "</strong></div>" +
563                '<div class="event-row"><span>' + escapeHtml(labels.venue || "Miejsce") + "</span><strong>" +
564                escapeHtml(eventItem.venue) +
565                "</strong></div>" +
566                '<div class="event-row"><span>' + escapeHtml(labels.dateTime || "Data i godzina") + '</span><time datetime="' +
567                escapeHtml(eventItem.date) +
568                '">' +
569                escapeHtml(dateTime) +
570                "</time></div>" +
571                action +
572                "</article>"
573              );
574            })
575            .join("");
576        } else {
577          eventsList.innerHTML =
578            '<div class="events-empty reveal">' + escapeHtml(siteContent.events.emptyText) + "</div>";
579        }
580      }
581
582      var eventsActions = document.querySelector("[data-events-actions]");
583      if (eventsActions && siteContent.events.allEvents) {
584        var allEvents = siteContent.events.allEvents;
585        var allEventsUrl = allEvents.url || "#contact";
586        var isExternal = /^https?:\/\//.test(allEventsUrl);
587        eventsActions.innerHTML =
588          '<a class="btn btn-primary" href="' +
589          escapeHtml(allEventsUrl) +
590          '"' +
591          (isExternal ? ' target="_blank" rel="noopener"' : "") +
592          ">" +
593          escapeHtml(allEvents.label || "WSZYSTKIE WYDARZENIA") +
594          "</a>";
595      }
596    }
597
598    var youtubeFrames = document.querySelectorAll("[data-youtube-frame]");
599    if (youtubeFrames.length && siteContent.youtube && siteContent.youtube.videoId) {
600      var videoId = encodeURIComponent(siteContent.youtube.videoId);
601      var videoTitle = escapeHtml(siteContent.youtube.title || "Video");
602      var youtubeUrl = "https://www.youtube.com/watch?v=" + videoId;
603      youtubeFrames.forEach(function (youtubeFrame) {
604        youtubeFrame.innerHTML =
605          '<iframe src="https://www.youtube.com/embed/' +
606          videoId +
607          '?rel=0" title="' +
608          videoTitle +
609          '" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen loading="lazy" referrerpolicy="strict-origin-when-cross-origin"></iframe>' +
610          '<a class="youtube-fallback-link" href="' +
611          youtubeUrl +
612          '" target="_blank" rel="noopener">' +
613          escapeHtml(ui.videoFallbackLink || "Watch on YouTube") +
614          "</a>";
615      });
616    }
617
618    renderSubpageContent();
619  }
620
621  renderEditableContent();
622
623  function resolveAssetPath(path) {
624    if (!path || path.indexOf("assets/") !== 0) return path;
625    return pathLanguage ? "../" + path : path;
626  }
627
628  function withLanguage(url) {
629    if (!url || url.charAt(0) === "#" || /^https?:\/\//.test(url)) return url;
630    var nextUrl = new URL(url, window.location.href);
631    if (pathLanguage) {
632      nextUrl.searchParams.delete("lang");
633    } else {
634      nextUrl.searchParams.set("lang", currentLanguage);
635    }
636    return nextUrl.pathname.split("/").pop() + nextUrl.search + nextUrl.hash;
637  }
638
639  function getLanguageUrl(code) {
640    var nextUrl = new URL(window.location.href);
641    var parts = nextUrl.pathname.split("/");
642    var file = parts.pop() || "index.html";
643    var languageIndex = -1;
644    for (var i = 0; i < parts.length; i += 1) {
645      if (languages[parts[i]]) {
646        languageIndex = i;
647        break;
648      }
649    }
650    if (languageIndex >= 0) {
651      parts[languageIndex] = code;
652    } else {
653      parts.push(code);
654    }
655    nextUrl.pathname = parts.concat(file).join("/");
656    nextUrl.searchParams.delete("lang");
657    return nextUrl.toString();
658  }
659
660  function setListItems(list, items) {
661    if (!list || !Array.isArray(items)) return;
662    list.innerHTML = items.map(function (item) {
663      return "<li>" + escapeHtml(item) + "</li>";
664    }).join("");
665  }
666
667  function renderSubpageContent() {
668    var page = window.location.pathname.split("/").pop() || "index.html";
669    var pageLanguages = subpageContent[page];
670    if (!pageLanguages) return;
671    var pageContent = pageLanguages[currentLanguage] || pageLanguages.pl;
672    if (!pageContent) return;
673    var seo = pageContent.seo || {};
674
675    if (seo.title) document.title = seo.title;
676    setMeta('meta[name="description"]', seo.description);
677    setMeta('meta[property="og:title"]', seo.ogTitle || seo.title);
678    setMeta('meta[property="og:description"]', seo.ogDescription || seo.description);
679
680    document.querySelectorAll(".logo").forEach(function (link) {
681      link.href = withLanguage("index.html");
682    });
683
684    document.querySelectorAll(".main-nav a").forEach(function (link) {
685      var href = link.getAttribute("href") || "";
686      if (href.indexOf("#about") !== -1) link.textContent = (siteContent.ui && siteContent.ui.navAbout) || link.textContent;
687      if (href.indexOf("#offer") !== -1) link.textContent = (siteContent.ui && siteContent.ui.navOffer) || link.textContent;
688      if (href.indexOf("#events") !== -1) link.textContent = (siteContent.ui && siteContent.ui.navEvents) || link.textContent;
689      if (href.indexOf("#gallery") !== -1) link.textContent = (siteContent.ui && siteContent.ui.navGallery) || link.textContent;
690      if (href.indexOf("bohaterowie.html") !== -1) link.textContent = (siteContent.ui && siteContent.ui.navCharacters) || link.textContent;
691      if (href.indexOf("#faq") !== -1) link.textContent = (siteContent.ui && siteContent.ui.navFaq) || link.textContent;
692      if (href.indexOf("#contact") !== -1) link.textContent = (siteContent.ui && siteContent.ui.navContact) || link.textContent;
693      link.href = withLanguage(href);
694    });
695
696    setText(".subpage-hero-inner .eyebrow", pageContent.heroEyebrow);
697    setHtml(".subpage-hero-inner h1", pageContent.heroTitle);
698    setText(".subpage-hero-inner > p:not(.eyebrow)", pageContent.heroText);
699    setText(".subpage-hero-inner .btn", pageContent.heroCta);
700    var heroButton = document.querySelector(".subpage-hero-inner .btn");
701    if (heroButton) heroButton.href = withLanguage(heroButton.getAttribute("href") || "index.html#contact");
702
703    setHtml(".subpage-panel h2", pageContent.panelTitle);
704    setText(".subpage-panel > p", pageContent.panelText);
705    setListItems(document.querySelector(".subpage-panel ul"), pageContent.bullets);
706
707    setText(".section-heading .eyebrow, .section-heading p", pageContent.mediaEyebrow);
708    setHtml(".section-heading h2", pageContent.mediaTitle);
709    setText(".video-placeholder span", pageContent.videoText);
710
711    setText(".subpage-cta .eyebrow", pageContent.ctaEyebrow);
712    setText(".subpage-cta h2", pageContent.ctaTitle);
713    setText(".quick-lead-title", pageContent.leadTitle);
714    document.querySelectorAll(".quick-lead input[type='email']").forEach(function (input) {
715      if (pageContent.emailPlaceholder) input.placeholder = pageContent.emailPlaceholder;
716    });
717    document.querySelectorAll(".quick-lead button[type='submit']").forEach(function (button) {
718      if (pageContent.leadButton) button.textContent = pageContent.leadButton;
719    });
720
721    if (Array.isArray(pageContent.cards)) {
722      document.querySelectorAll(".hero-card").forEach(function (card, index) {
723        var cardContent = pageContent.cards[index];
724        if (!cardContent) return;
725        var title = card.querySelector("h3");
726        var text = card.querySelector("p");
727        var subtitle = card.querySelector("span");
728        var traits = card.querySelector("strong");
729        var image = card.querySelector("img");
730        if (subtitle) subtitle.textContent = cardContent.subtitle || "";
731        if (title) title.textContent = cardContent.title;
732        if (text) text.textContent = cardContent.text;
733        if (traits) traits.textContent = cardContent.traits || "";
734        if (image && cardContent.image) image.src = resolveAssetPath(cardContent.image);
735      });
736    }
737
738    var bottomButton = document.querySelector(".subpage-cta .btn");
739    if (bottomButton && pageContent.heroCtaBottom) bottomButton.textContent = pageContent.heroCtaBottom;
740  }
741
742  function renderSocialLinks() {
743    if (!siteConfig.socialEnabled) {
744      document.querySelectorAll("[data-social-links]").forEach(function (container) {
745        container.innerHTML = "";
746        container.setAttribute("hidden", "");
747      });
748      return;
749    }
750    var social = Array.isArray(siteConfig.social) ? siteConfig.social : [];
751    document.querySelectorAll("[data-social-links]").forEach(function (container) {
752      if (container.hasAttribute("hidden")) return;
753      container.innerHTML = social
754        .map(function (item) {
755          var label = escapeHtml(item.label || "Social");
756          return (
757            '<a href="' +
758            escapeHtml(item.url || "#") +
759            '" target="_blank" rel="noopener" aria-label="' +
760            label +
761            '">' +
762            '<img src="' +
763            escapeHtml(item.icon || "") +
764            '" alt="" aria-hidden="true">' +
765            "<span>" +
766            label +
767            "</span></a>"
768          );
769        })
770        .join("");
771    });
772  }
773
774  renderSocialLinks();
775
776  function renderLanguageMenu() {
777    if (!languageSwitch) return;
778    languageSwitch.innerHTML = (siteContent.label || currentLanguage.toUpperCase()) + ' <span aria-hidden="true">⌄</span>';
779    var menu = document.createElement("div");
780    menu.className = "language-menu";
781    menu.setAttribute("hidden", "");
782    Object.keys(languages).forEach(function (code) {
783      var option = document.createElement("button");
784      option.type = "button";
785      option.textContent = languages[code].label || code.toUpperCase();
786      option.className = code === currentLanguage ? "is-active" : "";
787      option.addEventListener("click", function () {
788        localStorage.setItem("siteLanguage", code);
789        window.location.href = getLanguageUrl(code);
790      });
791      menu.appendChild(option);
792    });
793    languageSwitch.insertAdjacentElement("afterend", menu);
794    languageSwitch.addEventListener("click", function () {
795      menu.toggleAttribute("hidden");
796    });
797    document.addEventListener("click", function (event) {
798      if (!languageSwitch.contains(event.target) && !menu.contains(event.target)) {
799        menu.setAttribute("hidden", "");
800      }
801    });
802  }
803
804  renderLanguageMenu();
805
806  document.querySelectorAll("img").forEach(function (image) {
807    image.addEventListener(
808      "error",
809      function () {
810        var src = image.getAttribute("src") || "";
811        if (src.indexOf("assets/icon-") === 0 && src.indexOf(".png") !== -1) {
812          image.src = src.replace(".png", ".svg");
813          return;
814        }
815        var isLogo = src.indexOf("logo") !== -1;
816        if (isLogo && src !== resolveAssetPath("assets/logo.png")) {
817          image.src = resolveAssetPath("assets/logo.png");
818          return;
819        }
820        image.src = isLogo
821          ? fallbackImage("TEATR BANIEK", ["#06104d", "#ff3f9a"])
822          : fallbackImage("TEATR BANIEK MYDLANYCH", ["#040318", "#102a9b"]);
823      },
824      { once: true }
825    );
826  });
827
828  function updateHeader() {
829    if (!header) return;
830    header.classList.toggle("is-scrolled", window.scrollY > 24);
831  }
832
833  updateHeader();
834  window.addEventListener("scroll", updateHeader, { passive: true });
835
836  if (menuToggle && nav) {
837    menuToggle.addEventListener("click", function () {
838      var isOpen = nav.classList.toggle("is-open");
839      header.classList.toggle("is-open", isOpen);
840      menuToggle.setAttribute("aria-expanded", String(isOpen));
841    });
842
843    nav.querySelectorAll("a").forEach(function (link) {
844      link.addEventListener("click", function () {
845        nav.classList.remove("is-open");
846        header.classList.remove("is-open");
847        menuToggle.setAttribute("aria-expanded", "false");
848      });
849    });
850  }
851
852  document.querySelectorAll('a[href^="#"]').forEach(function (anchor) {
853    anchor.addEventListener("click", function (event) {
854      var target = document.querySelector(anchor.getAttribute("href"));
855      if (!target) return;
856      event.preventDefault();
857      target.scrollIntoView({ behavior: "smooth", block: "start" });
858    });
859  });
860
861  document.querySelectorAll(".faq-item button").forEach(function (button) {
862    button.addEventListener("click", function () {
863      var item = button.closest(".faq-item");
864      var isOpen = item.classList.toggle("is-open");
865      button.setAttribute("aria-expanded", String(isOpen));
866    });
867  });
868
869  document.querySelectorAll("[data-lightbox]").forEach(function (button) {
870    button.addEventListener("click", function () {
871      if (!lightbox || !lightboxImage) return;
872      var thumbnail = button.querySelector("img");
873      lightboxImage.src = thumbnail.currentSrc || thumbnail.src || button.getAttribute("data-lightbox");
874      lightboxImage.alt = thumbnail.alt;
875      lightbox.classList.add("is-open");
876      lightbox.setAttribute("aria-hidden", "false");
877      if (lightboxClose) lightboxClose.focus();
878    });
879  });
880
881  function closeLightbox() {
882    if (!lightbox || !lightboxImage) return;
883    lightbox.classList.remove("is-open");
884    lightbox.setAttribute("aria-hidden", "true");
885    lightboxImage.src = "";
886  }
887
888  if (lightboxClose) {
889    lightboxClose.addEventListener("click", closeLightbox);
890  }
891
892  if (lightbox) {
893    lightbox.addEventListener("click", function (event) {
894      if (event.target === lightbox) closeLightbox();
895    });
896  }
897
898  document.addEventListener("keydown", function (event) {
899    if (event.key === "Escape") closeLightbox();
900  });
901
902  var videoTrigger = document.querySelector("[data-video-trigger]");
903  if (videoTrigger) {
904    videoTrigger.addEventListener("click", function () {
905      var gallery = document.querySelector("#gallery");
906      if (gallery) gallery.scrollIntoView({ behavior: "smooth", block: "start" });
907    });
908  }
909
910  var reveals = document.querySelectorAll(".reveal");
911  if ("IntersectionObserver" in window) {
912    var observer = new IntersectionObserver(
913      function (entries) {
914        entries.forEach(function (entry) {
915          if (entry.isIntersecting) {
916            entry.target.classList.add("is-visible");
917            observer.unobserve(entry.target);
918          }
919        });
920      },
921      { threshold: 0.12 }
922    );
923
924    reveals.forEach(function (element) {
925      observer.observe(element);
926    });
927  } else {
928    reveals.forEach(function (element) {
929      element.classList.add("is-visible");
930    });
931  }
932
933  document.querySelectorAll("[data-contact-form]").forEach(function (form) {
934    form.addEventListener("submit", async function (event) {
935      event.preventDefault();
936
937      var button = form.querySelector("button");
938      var status = form.querySelector("[data-form-status]");
939      var originalText = button.textContent;
940      var formConfig = Object.assign({}, siteConfig.form || {}, siteContent.form || {});
941      var data = {};
942
943      new FormData(form).forEach(function (value, key) {
944        data[key] = value;
945      });
946      data.page = window.location.href;
947      data.sentAt = new Date().toISOString();
948
949      button.disabled = true;
950      button.textContent = (siteContent.ui && siteContent.ui.formSending) || "WYSYŁAM...";
951      if (status) status.textContent = "";
952
953      try {
954        if (formConfig.endpoint) {
955          var isGoogleScript = formConfig.endpoint.indexOf("script.google.com") !== -1;
956          var response = await fetch(formConfig.endpoint, {
957            method: "POST",
958            mode: isGoogleScript ? "no-cors" : "cors",
959            headers: { "Content-Type": "text/plain;charset=utf-8" },
960            body: JSON.stringify(data)
961          });
962          if (!isGoogleScript && !response.ok) throw new Error("Form endpoint error");
963        } else if (formConfig.fallbackEmail) {
964          var subject = encodeURIComponent("Zapytanie ze strony - Teatr Baniek Mydlanych");
965          var body = encodeURIComponent(
966            "Imię i nazwisko: " +
967              (data.name || "") +
968              "\nE-mail: " +
969              (data.email || "") +
970              "\nTelefon: " +
971              (data.phone || "") +
972              "\n\nWiadomość:\n" +
973              (data.message || "")
974          );
975          window.location.href = "mailto:" + formConfig.fallbackEmail + "?subject=" + subject + "&body=" + body;
976        }
977
978        form.reset();
979        button.textContent = formConfig.successMessage || "DZIĘKUJEMY!";
980        if (status) status.textContent = formConfig.endpoint ? ((siteContent.ui && siteContent.ui.formSent) || "Wiadomość została wysłana.") : ((siteContent.ui && siteContent.ui.formMail) || "Otwieramy program pocztowy.");
981      } catch (error) {
982        button.textContent = formConfig.errorMessage || "BŁĄD WYSYŁKI";
983        if (status) status.textContent = (siteContent.ui && siteContent.ui.formRetry) || "Spróbuj ponownie lub napisz do nas bezpośrednio.";
984      } finally {
985        setTimeout(function () {
986          button.disabled = false;
987          button.textContent = originalText;
988        }, 2600);
989      }
990    });
991  });
992})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.