Hosting
What does it look like to volunteer for YFU Flanders

Mechelen, nestled in the heart of Flanders, is a quaint city where you will both live and work. However, your work will often entail traveling around the country. Fortunately, the public transportation system is well-organized, providing easy access to the capital city Brussels, the seaside, and the picturesque Ardennes mountains with just a short train ride.

Characterized by its rich multiculturalism and vibrant student atmosphere, Mechelen offers something for everyone. Surrounded by nature parks in nearly every direction, the city provides ample opportunities for outdoor enthusiasts. Whether you enjoy leisurely hikes in Het Mechels Broek, bouldering at Het KlimKaffee, or savoring a local beer at De Vismarkt or our city brewery Het Anker, Mechelen truly caters to diverse interests and tastes.

your work

what, when and where?

During your volunteering project, you will be expected to work five days a week, typically from Monday to Friday. However, there may be occasions when you'll need to organize events during weekends or evenings. It's essential to collaborate closely with your project coordinator to manage your workload effectively. While you won't be required to track your working hours, completing your assigned tasks in a timely manner is paramount.

We value granting you a significant degree of autonomy within the project framework, but with this freedom comes responsibility. It's crucial for you to be accountable and considerate of your commitments. Bi-weekly follow-up sessions with your project coordinator will serve as a guiding platform. These meetings will involve discussing recent developments, planning future events, addressing challenges, and setting goals to ensure alignment between your expectations, project reality, and schedule.

During our weekly team meetings, you have the opportunity to ask questions to the team and explore potential collaborations with colleagues on various projects. These sessions aim to keep everyone informed, as we operate as a small team, but also serve as occasions to inspire creativity and foster collaboration.

Monday to Wednesday, all colleagues convene at our office, while Thursdays and Fridays offer the flexibility to work from home, with the office remaining open. This structure allows you to plan tasks accordingly. Typically, workdays commence at 9 AM and conclude at 5 PM.

Additionally, our office observes a closure during the week between Christmas and New Year, as well as on all official public holidays. During these periods, participants in our ESC projects are not expected to work.

Accomodation

Where you will live

Although YFU collaborates with host families for its exchange students, our ESC volunteers are housed in two rooms within a student dormitory situated in the center of Mechelen. This location is conveniently close to our office, a train station, and numerous shops and bars, all nestled in the historic heart of the city.In these accommodations, you will have your own room but will share a kitchen and bathroom with other residents. It will be necessary to coordinate and agree upon arrangements with your fellow residents, many of whom are international students enrolled at Thomas More University of Applied Sciences. Additionally, the dormitory is overseen by a superintendent, who will introduce themselves and serve as your point of contact in case any issues arise.Your room will be provided with clean sheets, towels, and bedding. The kitchen is equipped with a variety of utensils. If you require anything else, we offer a small budget for enhancing your quality of life.

Transportation

Getting places

Your accommodation is centrally situated in Mechelen, offering easy access on foot to the YFU office, as well as the train and bus stations, along with a myriad of shops, restaurants, and bars. Upon your arrival in Flanders, you'll receive an annual bus pass. Additionally, for each quarter of your stay, you'll be provided with a 10-ride train ticket. Furthermore, a bicycle will be at your disposal, reflecting the widespread use of cycling among Belgians. All work-related travel expenses will be reimbursed, and in exceptional circumstances, you may request a lift from a colleague.

Finances

What you are entitled to under your ESC project

The European Solidarity Corps has established guidelines for organizing our activities. As such, ESC volunteers are entitled to the following benefits:

Travel: Your travel to and from Flanders is covered. While you will book your own flight in consultation with us, we will refund your ticket. Upon arrival at Brussels airport, you will take the train to Mechelen Nekkerspoel, where someone will meet you to guide you to our office and your accommodation. Additionally, upon arrival in Flanders, you will receive a pass for using buses throughout Flanders and a quarterly 10-ride ticket for trains. Furthermore, a bike will be provided for your use, as biking is a common mode of transportation in Belgium.

Pocket Money and Food Allowance: As per ESC guidelines, volunteers participating in a program in Flanders are entitled to €6 per day as pocket money, which will be paid at the beginning of each month. Similarly, a set amount will be transferred to your account monthly for food, totaling €280. This allowance is expected to cover the costs of maintaining a healthy and nutritious diet.

Accommodation: We rent two similar rooms in a student dormitory located in the center of Mechelen, within walking distance of the office and the train station. This means you will share a well-equipped kitchen and bathroom with other young people, primarily students from Thomas More University of Applied Sciences. We provide sheets, pillows, and towels. It's important to note that your volunteer project is not a typical 9-to-5 arrangement. You will live together with others, fostering a shared social life, engaging in leisure activities, and learning the Dutch language together. Our ESC volunteers are more than just colleagues; they form a close-knit community.

Language: Learning the Dutch language is a fundamental aspect of your project. Not only will it assist you in communicating with partners, students, and colleagues, but it will also enhance your sense of belonging and unlock a myriad of opportunities in Flanders. To support you in this endeavor, you will be enrolled in a language course upon your arrival. These courses are led by experienced facilitators, and you will be placed in a group based on your proficiency level in Dutch.

Insurance: Participants will enroll in Henner, the insurance provided by the European Commission for the ESC program.

If you want to learn more about the ESC and the options the Corps provides for both young people and organizations such as YFU, take a look at their yearly guidelines. Here you can find everything ranging from ensurances to travel costs. If long reads are not your cup of tea, you can always reach out the the national agency in your country. They can probably provide you with answers to all your questions.

Mentoring

Follow-up during your stay

Throughout your project, you'll collaborate closely with two key individuals. Firstly, your project coordinator, based in our office, will be your main point of contact. They possess in-depth knowledge of your project and can assist you with paperwork, goal-setting, and aligning your expectations with your work. Responsible for managing your workload, they'll organize bi-weekly follow-up meetings for progress updates and feedback sessions. Additionally, as the ultimate decision-maker for your project, they'll make final calls when necessary.Your mentor, the second contact person, operates outside the office but boasts a solid understanding of YFU as an organization. Serving as your initial friend or buddy, they reside in Mechelen, offering easy accessibility. Your mentor serves as a valuable resource for inquiries about life in Flanders and provides a supportive environment for discussing any work-related challenges that may arise. While no set meetings are scheduled with your mentor, they'll likely initiate contact more frequently at the outset of your project.

In case of any issues, your mentor and project coordinator will convene to address and resolve concerns, ensuring your needs are met and your voice is heard.

Kies een continent op de kaart of onder "Alle Bestemmingen" om onze bestemmingen te ontdekken.
Lees de verhalen van onze uitwisselingsstudenten en bekijk de informatie die je nodig hebt om het perfecte land voor jou te vinden!

Country

Leeftijd

Duur

Prijs

Taal

Inschrijven tot

Extra's

              `; return div; }, "introduction": createWidgetHandler( { header: "string", subheader: "string", text: "string", icon: "icon", image1: "image", image2: "image", button: "button", layout: "string", side: "string" }, (text,attrs) => { const div = document.createElement("div"); div.classList.add("introduction","widget",`layout-${attrs.layout}`) div.innerHTML = `
              ${attrs.header ? `

              ${attrs.header}

              ` : ""} ${attrs.subheader ? `

              ${attrs.subheader}

              `: ""} ${attrs.text ? `

              ${attrs.text}

              `: ""} ${attrs.button ? `${attrs.button.label}` : ""}
              ${attrs.image1 ? ``: ""} ${attrs.image2 ? ``: ""} ${attrs.icon ? ``: ""}
              ` return div } ), "aside": createWidgetHandler( { header: "string", text: "string", icon: "icon", button: "button", unordered_list: "listPairs", ordered_list: "listPairs", offset: "string", nudge: "string" }, (text, attrs) => { const aside = document.createElement("aside"); if (attrs.offset) aside.style.gridRow = attrs.offset; if (attrs.nudge) aside.style.marginTop = `${attrs.nudge}rem`; aside.innerHTML = ` ${attrs.icon ? `` : ""} ${attrs.header ? `
              ${attrs.header}
              ` : ""} ${attrs.text ? `

              ${attrs.text}

              ` : ""} ${attrs.unordered_list ? `` : ""} ${attrs.ordered_list ? `
                ` : ""} ${attrs.button ? `
                ${attrs.button.label}
                ` : ""} `; if (attrs.unordered_list) { const ul = aside.querySelector("ul"); for (const { title, href } of attrs.unordered_list) { const li = document.createElement("li"); if (href) { const a = document.createElement("a"); a.href = href; a.textContent = title; li.appendChild(a); } else { li.textContent = title; } ul.appendChild(li); } } if (attrs.ordered_list) { const ol = aside.querySelector("ol"); for (const { title, href } of attrs.ordered_list) { const li = document.createElement("li"); if (href) { const a = document.createElement("a"); a.href = href; a.textContent = title; li.appendChild(a); } else { li.textContent = title; } ol.appendChild(li); } } return aside; } ), "your_widget": createWidgetHandler( { }, (text, attrs) => { } ), "sticky": createWidgetHandler( { icon: "icon", text: "string" }, (text, attrs) => { } ), "ordered_list": createWidgetHandler( { elements: "listPairs" }, (text, attrs) => { const div = document.createElement("div"); div.classList = "widget list" if (attrs.elements) { const ol = document.createElement("ol"); for (const { title, href } of attrs.elements) { const li = document.createElement("li"); if (href) { const a = document.createElement("a"); a.href = href; a.textContent = title; li.appendChild(a); } else { li.textContent = title; } ol.appendChild(li); } div.appendChild(ol); return div; } return null; } ), "unordered_list": createWidgetHandler( { elements: "listPairs" }, (text, attrs) => { const div = document.createElement("div"); div.classList = "widget list" if (attrs.elements) { const ul = document.createElement("ul"); for (const { title, href } of attrs.elements) { const li = document.createElement("li"); if (href) { const a = document.createElement("a"); a.href = href; a.textContent = title; li.appendChild(a); } else { li.textContent = title; } ul.appendChild(li); } div.appendChild(ul); return div; } return null; } ), "featured_boxes": createWidgetHandler( { header_1: "string", header_2: "string", icon_1: "icon", icon_2: "icon", text_1: "string", text_2: "string", unordered_list_1: "listPairs", unordered_list_2: "listPairs", ordered_list_1: "listPairs", ordered_list_2: "listPairs", button_1: "button", button_2: "button", color_1: "color", color_2: "color" }, (text, attrs) => { const wrapper = document.createElement("div") wrapper.classList = "widget featured-boxes" if (attrs.header_1 || attrs.text_1 || attrs.icon_1 || attrs.unordered_list_1 || attrs.ordered_list_1 || attrs.button_1) { const div1 = document.createElement("div") div1.classList = `option-1 ${attrs.color_1 ? `${attrs.color_1}` : ""}` div1.innerHTML = ` ${attrs.icon_1 ? `` : ""}
                ${attrs.header_1 ? `

                ${attrs.header_1}

                ` : ""} ${attrs.text_1 ? `

                ${attrs.text_1}

                ` : ""} ${attrs.unordered_list_1 ? `` : ""} ${attrs.ordered_list_1 ? `
                  ` : ""} ${attrs.button_1 ? `` : ""}
                  ` if (attrs.unordered_list_1) { const ul = div1.querySelector("ul"); for (const { title, href } of attrs.unordered_list_1) { const li = document.createElement("li"); if (href) { const a = document.createElement("a"); a.href = href; a.textContent = title; li.appendChild(a); } else { li.textContent = title; } ul.appendChild(li); } } if (attrs.ordered_list_1) { const ol = div1.querySelector("ol"); for (const { title, href } of attrs.ordered_list_1) { const li = document.createElement("li"); if (href) { const a = document.createElement("a"); a.href = href; a.textContent = title; li.appendChild(a); } else { li.textContent = title; } ol.appendChild(li); } } wrapper.appendChild(div1) } if (attrs.title_2 || attrs.text_2 || attrs.icon_2 || attrs.unordered_list_2 || attrs.ordered_list_2 || attrs.button_2) { const div2 = document.createElement("div") div2.classList = `option-2 ${attrs.color_1 ? `${attrs.color_2}` : ""}` div2.innerHTML = ` ${attrs.icon_2 ? `` : ""}
                  ${attrs.header_2 ? `

                  ${attrs.header_2}

                  ` : ""} ${attrs.text_2 ? `

                  ${attrs.text_2}

                  ` : ""} ${attrs.unordered_list_2 ? `` : ""} ${attrs.ordered_list_2 ? `
                    ` : ""} ${attrs.button_2 ? `` : ""}
                    ` if (attrs.unordered_list_2) { const ul = div2.querySelector("ul"); for (const { title, href } of attrs.unordered_list_2) { const li = document.createElement("li"); if (href) { const a = document.createElement("a"); a.href = href; a.textContent = title; li.appendChild(a); } else { li.textContent = title; } ul.appendChild(li); } } if (attrs.ordered_list_2) { const ol = div2.querySelector("ol"); for (const { title, href } of attrs.ordered_list_2) { const li = document.createElement("li"); if (href) { const a = document.createElement("a"); a.href = href; a.textContent = title; li.appendChild(a); } else { li.textContent = title; } ol.appendChild(li); } } wrapper.appendChild(div2) } return wrapper } ), "postcard": createWidgetHandler( { header: "string", quote: "string", image: "image", button: "button", duration: "string", program: "string", flag_alpha2_code: "alpha2", side: "string", layout: "string", link_to_country: "string" }, (text, attrs) => { const div = document.createElement("div") div.classList.add("widget","postcard",`${attrs.layout ? `layout-${attrs.layout}` : ""}`) div.innerHTML = `
                    ${attrs.header ? `

                    ${attrs.header}

                    ` : ""} ${attrs.flag_alpha2_code ? `
                    ` : '
                    '} ${attrs.quote ? `

                    ${attrs.quote}

                    ` : ""}
                    • YFU Vlaanderen
                    • Frans Halsvest 92
                    • Mechelen, 2800
                    • België
                    ${attrs.image ? `` : ""}
                    ` return div } ), "arrow_menu": createWidgetHandler( { steps: "listPairs" }, (text, attrs) => { const nav = document.createElement("nav"); nav.classList.add("widget","arrow-menu"); const ol = document.createElement("ol"); nav.appendChild(ol); if (attrs.steps) { attrs.steps.forEach(({ title, href }, index) => { const li = document.createElement("li"); // Create a wrapper (anchor or span) to hold the entire arrow const wrapper = href ? document.createElement("a") : document.createElement("span"); if (href) wrapper.href = href; wrapper.classList.add("arrow-step"); // Tail (skip first) if (index > 0) { const tail = document.createElementNS("http://www.w3.org/2000/svg", "svg"); tail.setAttribute("viewBox", "0 0 1 1"); ("viewBox", "0 0 1 1"); tail.classList.add("arrow-tail") const useTail = document.createElementNS("http://www.w3.org/2000/svg", "use"); useTail.setAttribute("href", "#svg_arrow_tail"); tail.appendChild(useTail); wrapper.appendChild(tail); } // Label const label = document.createElement("span"); label.classList.add("arrow-label"); label.textContent = title; wrapper.appendChild(label); // Head (always present) const head = document.createElementNS("http://www.w3.org/2000/svg", "svg"); head.setAttribute("viewBox", "0 0 1 1"); head.classList.add("arrow-head") const useHead = document.createElementNS("http://www.w3.org/2000/svg", "use"); useHead.setAttribute("href", "#svg_arrow_head"); head.appendChild(useHead); wrapper.appendChild(head); li.appendChild(wrapper); ol.appendChild(li); }); } return nav; } ), "homepage": createWidgetHandler( { image: "image", overlay_strength: "string" }, (text, attrs) => { homepageOverwrite = true if (attrs.image) { homepageImage = attrs.image } if (attrs.overlay_strength) { homepageOverlayStrength = attrs.overlay_strength } } ), "pages": createWidgetHandler( { previous: "button", // → [label, href] current: "button", next: "button" }, (text, attrs) => { const safeLabel = (label) => label?.trim() || "​"; const safeHref = (href) => href?.trim() || "#"; const isVisible = (label) => label && label.trim() !== ""; const prevLabel = safeLabel(attrs.previous?.label); const currLabel = safeLabel(attrs.current?.label); const nextLabel = safeLabel(attrs.next?.label); const prevHref = safeHref(attrs.previous?.href); const currHref = safeHref(attrs.current?.href); const nextHref = safeHref(attrs.next?.href); pagesNavElement = document.createElement("nav"); pagesNavElement.classList.add("pages-nav"); pagesNavElement.setAttribute("role", "navigation"); pagesNavElement.setAttribute("aria-label", "Page navigation"); pagesNavElement.innerHTML = ` ${isVisible(prevLabel) ? `${prevLabel}` : ""} ${isVisible(prevLabel) ? `<` : ""} ${isVisible(currLabel) ? `${currLabel}` : ""} ${isVisible(nextLabel) ? `>` : ""} ${isVisible(nextLabel) ? `${nextLabel}` : ""} `; } ) } // Restructure Widgets function restructureWidget(widget) { if (widget === h1) { const container = h1.querySelector("div.container"); const titleText = container?.childNodes[0]?.textContent?.trim() || ""; const subText = container?.querySelector("div.sub")?.textContent?.trim(); const fragment = document.createDocumentFragment(); // title (always) const title = document.createElement("h1"); title.innerText = titleText; fragment.appendChild(title); // subtitle (only if non-empty) if (subText) { const subtitle = document.createElement("h2"); subtitle.innerText = subText; fragment.appendChild(subtitle); } return fragment; } if (widget.classList.contains("picture-hero")) { const figure = document.createElement("figure"); const img = widget.querySelector("img"); const newImg = document.createElement("img"); newImg.src = img.src; newImg.alt = img.alt || ""; figure.appendChild(newImg); figure.id = "hero_image"; return figure; } if (widget.classList.contains("static_citation")) { const quoteSpan = widget.querySelector("h3 span"); if (quoteSpan) { const rawText = quoteSpan.innerText.trim(); const { keyword, attrs } = parseWidgetKeyword(rawText); if (keyword && widgetHandlers[keyword]) { return widgetHandlers[keyword](rawText, attrs); } else { // Fallback: treat it as a regular quote block const blockquote = document.createElement("blockquote"); blockquote.classList.add("widget", "static_citation"); blockquote.innerHTML = `

                    ${rawText}

                    `; return blockquote; } } } if (widget.classList.contains("program_related_programs_table")) { /* -------------------------------------------------- 1. BASIC SET-UP -------------------------------------------------- */ const originalDesktop = widget.querySelector(".desktop"); if (!originalDesktop) return; const desktopTable = originalDesktop.querySelector("table"); if (!desktopTable) return; /* Header → column index map ------------------------ */ const columnMap = {}; Array.from(desktopTable.querySelectorAll("thead th")).forEach((th, i) => { const txt = th.textContent.toLowerCase().trim(); if (txt.includes("land")) columnMap.country = i; else if (txt.includes("program")) columnMap.program = i; else if (txt.includes("duur") && !("duration" in columnMap)) columnMap.duration = i; else if (txt.includes("thema")) columnMap.theme = i; else if (txt.includes("leeftijd")) columnMap.age = i; else if (txt.includes("prijs")) columnMap.price = i; else if (txt.includes("begin")) columnMap.begin = i; else if (txt === "") columnMap.status = i; }); const rows = Array.from(desktopTable.querySelectorAll("tbody tr")); const SHOW_LIMIT = 7; let showAll = false; /* DOM scaffolding ---------------------------------- */ const div = document.createElement("div"); div.classList.add("widget", "program_related_programs_table"); const filtersContainer = document.createElement("div"); filtersContainer.classList.add("filters-container"); const newDesktop = document.createElement("div"); newDesktop.classList.add("desktop"); const newMobile = document.createElement("div"); newMobile.classList.add("mobile", "container"); /* “See all results” button ------------------------- */ const seeAllBtn = document.createElement("button"); seeAllBtn.innerHTML = "

                    Bekijk alle resultaten

                    "; seeAllBtn.className = "see-all-button"; seeAllBtn.style.display = "none"; seeAllBtn.addEventListener("click", () => { showAll = true; updateTables(); }); /* -------------------------------------------------- 2. HELPERS -------------------------------------------------- */ const filterElements = {}; function addFilterListeners(el) { ["input", "change"].forEach(evt => el.addEventListener(evt, () => { showAll = false; updateTables(); }) ); } function getUniqueOptions(idx) { return [...new Set( rows .filter(r => !r.classList.contains("group")) .map(r => r.children[idx]?.textContent.trim()) .filter(Boolean) )].sort(); } function createCheckboxGroup(name, options, labelText) { const wrap = document.createElement("details"); wrap.className = `checkbox-group ${name}`; const summary = document.createElement("summary"); summary.innerHTML = ` ${labelText || name.charAt(0).toUpperCase() + name.slice(1)}`; wrap.appendChild(summary); options.forEach(opt => { const id = `${name}-${opt}`; const row = document.createElement("div"); const chk = document.createElement("input"); chk.type = "checkbox"; chk.id = id; chk.name = name; chk.value = opt; const lbl = document.createElement("label"); lbl.htmlFor = id; lbl.textContent = opt; row.appendChild(chk); row.appendChild(lbl); wrap.appendChild(row); }); addFilterListeners(wrap); return wrap; } function extractAgesFromText(t) { const m = t.match(/(\d{1,2})\D+(\d{1,2})/); if (!m) return []; const a = +m[1], b = +m[2]; if (isNaN(a) || isNaN(b) || a > b) return []; return Array.from({ length: b - a + 1 }, (_, i) => a + i); } function getAllAges(idx) { const set = new Set(); rows.forEach(r => { if (r.classList.contains("group")) return; extractAgesFromText(r.children[idx]?.textContent.trim() || "") .forEach(a => set.add(a)); }); return [...set].sort((a, b) => a - b); } /* -------------------------------------------------- 3. STANDARD FILTERS --------------------------------------------------*/ const labelMap = { country: "Land", program: "Programma", duration: "Duur", theme: "Thema", begin: "Begin", age: "Leeftijd", price: "Prijs", status: "Status", language: "Taal" }; Object.entries(columnMap).forEach(([name, idx]) => { if (name === "price") return; if (name === "age") { const ages = getAllAges(idx); if (ages.length) { const grp = createCheckboxGroup("age", ages, labelMap.age); filtersContainer.appendChild(grp); filterElements.age = grp; } return; } // Get the unique options once you've skipped “age” const opts = getUniqueOptions(idx); if (!opts.length) return; // Use the Dutch label from labelMap const grp = createCheckboxGroup(name, opts, labelMap[name]); filtersContainer.appendChild(grp); filterElements[name] = grp; }); /* Price range -------------------------------------- */ ["price"].forEach(key => { const from = document.createElement("input"); const to = document.createElement("input"); from.type = to.type = "number"; from.placeholder = "Prijs van"; to.placeholder = "Prijs tot"; addFilterListeners(from); addFilterListeners(to); filtersContainer.appendChild(from); filtersContainer.appendChild(to); filterElements[`${key}From`] = from; filterElements[`${key}To`] = to; }); /* -------------------------------------------------- 4. LANGUAGE MAPPING (Dutch names, multi-language) – extend or adjust as needed -------------------------------------------------- */ const languageToCountries = { Nederlands: ["Nederland", "Suriname", "Aruba", "Curaçao"], Engels: ["Australië", "Canada", "Ierland", "Verenigde Staten", "Verenigd Koninkrijk", "Zuid-Afrika", "Nieuw-Zeeland", "India", "Ghana", "Botswana", "Kenya", "Liberia"], Frans: ["Frankrijk", "België", "Canada", "Zwitserland", "Luxemburg", "Marokko", "Tunesië", "Algerije", "Senegal", "Ivoorkust"], Duits: ["Duitsland", "Oostenrijk", "Zwitserland", "Luxemburg"], Spaans: ["Argentinië", "Spanje", "Mexico", "Colombia", "Chili", "Peru", "Uruguay", "Paraguay", "Ecuador", "Bolivia", "Costa Rica", "Dominicaanse Republiek", "El Salvador", "Guatemala", "Honduras", "Nicaragua", "Panama", "Venezuela"], Portugees: ["Brazilië", "Portugal", "Angola", "Mozambique"], Italiaans: ["Italië", "Zwitserland"], Deens: ["Denemarken"], Noors: ["Noorwegen"], Zweeds: ["Zweden"], Fins: ["Finland"], Ests: ["Estland"], Lets: ["Letland"], Litouws: ["Litouwen"], Pools: ["Polen"], Hongaars: ["Hongarije"], Bulgaars: ["Bulgarije"], Roemeens: ["Roemenië", "Moldavië"], Tsjechisch: ["Tsjechië"], Slowaaks: ["Slowakije"], Servisch: ["Servië", "Kosovo"], Kroatisch: ["Kroatië", "Bosnië en Herzegovina"], Sloveens: ["Slovenië"], Grieks: ["Griekenland"], Russisch: ["Rusland", "Wit-Rusland", "Kazachstan", "Kirgizië", "Tadzjikistan"], Oekraïens: ["Oekraïne"], Kazachs: ["Kazachstan"], Azerbeidzjaans: ["Azerbeidzjan"], Armeens: ["Armenië"], Georgisch: ["Georgië"], Oezbeeks: ["Oezbekistan"], Tadzjieks: ["Tadzjikistan"], Mongools: ["Mongolië"], Arabisch: ["Egypte", "Marokko", "Tunesië", "Algerije", "Saoedi-Arabië", "Jordanië", "Libanon", "Libië", "Palestijnse gebieden", "Qatar", "Verenigde Arabische Emiraten", "Bahrein", "Koeweit", "Oman", "Jemen"], Chinees: ["China", "Taiwan", "Singapore"], Japans: ["Japan"], Koreaans: ["Zuid-Korea"], Thais: ["Thailand"], Vietnamees: ["Vietnam"], Indonesisch: ["Indonesië"], Bengaals: ["Bangladesh"], Hindi: ["India"], Urdu: ["Pakistan"], Swahili: ["Tanzania"], Turks: ["Turkije"], }; /* Build language filter with only *represented* languages */ (function buildLanguageFilter() { const countriesInTable = getUniqueOptions(columnMap.country); const representedLangs = Object.keys(languageToCountries).filter(lang => languageToCountries[lang].some(c => countriesInTable.includes(c)) ); if (!representedLangs.length) return; // nothing to show const langGroup = createCheckboxGroup("language", representedLangs.sort(), "Taal"); if (filterElements.country) { /* ⬅️ put Language directly after the Country group */ filterElements.country.after(langGroup); } else { /* fallback, should never happen */ filtersContainer.appendChild(langGroup); } filterElements.language = langGroup; })(); /* -------------------------------------------------- 5. FILTER LOGIC -------------------------------------------------- */ function matchesFilters(row) { if (row.classList.contains("group")) return true; const country = row.children[columnMap.country]?.textContent.trim(); /* Language */ if (filterElements.language) { const selLangs = [...filterElements.language.querySelectorAll("input:checked")].map(i => i.value); if (selLangs.length) { const validCountries = new Set(); selLangs.forEach(l => (languageToCountries[l] || []).forEach(c => validCountries.add(c))); if (!validCountries.has(country)) return false; } } /* Other filters */ return Object.entries(columnMap).every(([name, idx]) => { const val = row.children[idx]?.textContent.trim() || ""; if (name === "price") { const num = +(val.match(/\d+/) || [null])[0]; const min = +filterElements.priceFrom?.value || -Infinity; const max = +filterElements.priceTo?.value || Infinity; return isNaN(num) || (num >= min && num <= max); } if (name === "age") { const selected = [...filterElements.age?.querySelectorAll("input:checked") || []].map(i => +i.value); if (!selected.length) return true; return extractAgesFromText(val).some(a => selected.includes(a)); } const checked = [...filterElements[name]?.querySelectorAll("input:checked") || []].map(i => i.value); return !checked.length || checked.includes(val); }); } /* -------------------------------------------------- 6. TABLE BUILDERS (limit aware) -------------------------------------------------- */ function buildDesktopTable(filtered, limit = Infinity) { const table = desktopTable.cloneNode(true); const tbody = table.querySelector("tbody"); tbody.innerHTML = ""; let count = 0; filtered.forEach(r => { if (count >= limit) return; if (r.classList.contains("group")) return; tbody.appendChild(r.cloneNode(true)); count++; const next = rows[rows.indexOf(r) + 1]; if (next?.classList.contains("group")) tbody.appendChild(next.cloneNode(true)); }); return table; } function buildMobileTable(filtered, limit = Infinity) { const cont = document.createElement("div"); let count = 0; filtered.forEach(r => { if (count >= limit) return; if (r.classList.contains("group")) return; const cells = [...r.children]; const details = document.createElement("details"); const summary = document.createElement("summary"); summary.innerHTML = ` ${[cells[columnMap.country]?.textContent, cells[columnMap.duration]?.textContent, cells[columnMap.begin]?.textContent, cells[columnMap.theme]?.textContent].filter(Boolean).join(", ")}` // [ // cells[columnMap.country]?.textContent, // cells[columnMap.duration]?.textContent, // cells[columnMap.begin]?.textContent, // cells[columnMap.theme]?.textContent // ].filter(Boolean).join(", "); details.appendChild(summary); const tbl = document.createElement("table"); const tb = document.createElement("tbody"); Object.entries(columnMap).forEach(([n, idx]) => { const tr = document.createElement("tr"); tr.innerHTML = `${n.charAt(0).toUpperCase() + n.slice(1)}${cells[idx]?.textContent || ""}`; tb.appendChild(tr); }); tbl.appendChild(tb); details.appendChild(tbl); cont.appendChild(details); count++; }); return cont; } /* -------------------------------------------------- 7. UPDATE TABLES -------------------------------------------------- */ function updateTables() { const filtered = rows.filter(matchesFilters); const limit = showAll ? Infinity : SHOW_LIMIT; newDesktop.innerHTML = ""; newDesktop.appendChild(buildDesktopTable(filtered, limit)); newMobile.innerHTML = ""; newMobile.appendChild(buildMobileTable(filtered, limit)); seeAllBtn.style.display = !showAll && filtered.length > SHOW_LIMIT ? "grid" : "none"; } /* Clear filters */ const clearBtn = document.createElement("button"); clearBtn.textContent = "Filters wissen"; clearBtn.className = "clear-filters"; clearBtn.addEventListener("click", () => { Object.entries(filterElements).forEach(([k, el]) => { if (k.endsWith("From") || k.endsWith("To")) { el.value = ""; return; } [...el.querySelectorAll("input[type='checkbox']")].forEach(c => (c.checked = false)); }); showAll = false; updateTables(); }); filtersContainer.appendChild(clearBtn); /* -------------------------------------------------- 8. INIT -------------------------------------------------- */ updateTables(); div.appendChild(filtersContainer); div.appendChild(newDesktop); div.appendChild(newMobile); div.appendChild(seeAllBtn); return div; } if (widget.classList.contains("program_related_featured_countries_and_programs")) { const rawCountries = widget.querySelectorAll(".country-page") const div = document.createElement("div") div.classList.add("widget", "program_related_featured_countries_and_programs") rawCountries.forEach (country => { if (country.querySelector(".title") && country.querySelector("img") && country.querySelector("table")) { const container = document.createElement("a") container.classList = "program" container.href = country.href const header = document.createElement("h3") header.textContent = country.querySelector(".title").textContent const image = country.querySelector("img") const table = country.querySelector("table").cloneNode(true) const newReadMore = document.createElement("a") newReadMore.classList = "button" newReadMore.textContent = country.querySelector(".read-more").textContent newReadMore.href = country.href container.appendChild(header) container.appendChild(image) container.appendChild(table) container.appendChild(newReadMore) div.appendChild(container) } }); return div } if (widget.classList.contains("static_rich_text")) { const output = document.createElement("div"); output.classList.add("widget", "static_rich_text", ...widget.classList); // Remove the original `.container` wrapper const container = widget.querySelector(".container") || widget; // Helper to downgrade headings function downgradeHeadings(el) { el.querySelectorAll("h2, h3").forEach(header => { const newTag = document.createElement(header.tagName === "H2" ? "h3" : "h4"); newTag.innerHTML = header.innerHTML; output.appendChild(newTag); }); } // Helper to process and clean paragraphs function processParagraphContent(html) { const splitContent = html.split(/\s*/i); return splitContent.map(fragment => { const p = document.createElement("p"); p.innerHTML = fragment.trim(); return p; }); } // Downgrade headers and add them to output downgradeHeadings(container); // Gather all text content (excluding nested widgets like columns/buttons) container.querySelectorAll("p").forEach(p => { const cleaned = p.cloneNode(true); // Remove inline styles and empty tags cleaned.removeAttribute("style"); if (!cleaned.innerHTML.trim()) return; const processedParas = processParagraphContent(cleaned.innerHTML); processedParas.forEach(p => output.appendChild(p)); }); // Include any standalone buttons or links that should remain const buttons = container.querySelectorAll("a.button"); buttons.forEach(btn => { const clone = btn.cloneNode(true); output.appendChild(clone); }); return output; } if (widget.classList.contains("static_picture")) { const figure = document.createElement("figure"); figure.classList.add("widget", "static_picture"); const img = widget.querySelector("img"); const caption = widget.querySelector(".caption"); if (img) { const newImg = document.createElement("img"); newImg.src = img.src; newImg.alt = caption || ""; if (img.className) newImg.className = img.className; figure.appendChild(newImg); } if (caption) { const figcaption = document.createElement("figcaption"); figcaption.innerText = caption.innerText.trim(); figure.appendChild(figcaption); } return figure; } if (widget.classList.contains("static_picture_gallery")) { const slider = widget.querySelector(".image-slider"); const images = slider.querySelectorAll("ul.images li"); const captionText = slider.querySelector(".caption")?.innerText || ""; // Create a scrollable container const scrollContainer = document.createElement("div"); scrollContainer.classList.add("carousel-scroll-container"); scrollContainer.setAttribute("role", "region"); scrollContainer.setAttribute("aria-label", "Image gallery"); images.forEach((li) => { const figure = document.createElement("figure"); figure.classList.add("carousel-slide"); const img = document.createElement("img"); const bgUrl = li.style.backgroundImage.match(/url\(["']?(.*?)["']?\)/); img.src = bgUrl ? bgUrl[1] : ""; img.alt = li.dataset.caption || captionText || ""; figure.appendChild(img); if (li.dataset.caption) { const figcaption = document.createElement("figcaption"); figcaption.textContent = li.dataset.caption.trim(); figure.appendChild(figcaption); } scrollContainer.appendChild(figure); }); // Replace old content widget.innerHTML = ""; widget.appendChild(scrollContainer); } if (widget.classList.contains("static_image_and_text")) { const container = widget.querySelector(".container"); if (!container) return; const heading = container.querySelector("h2"); const textContent = container.querySelector(".content"); const img = container.querySelector("img"); const layout = ["top", "right", "bottom", "left"].find(dir => container.classList.contains(dir)) || "left"; const shape = container.classList.contains("circle") ? "circle" : "rectangle"; const wrapper = document.createElement("div"); wrapper.classList.add("widget", "static_image_and_text", `text-${layout}`, `shape-${shape}`); // Create image element const newImg = document.createElement("img"); newImg.src = img?.src || ""; newImg.alt = img?.alt || ""; // ensure accessibility newImg.classList.add("image"); wrapper.appendChild(newImg); // Create text content const textDiv = document.createElement("div"); textDiv.classList.add("text-content"); if (heading) { const newHeading = document.createElement("h3"); newHeading.textContent = heading.textContent; textDiv.appendChild(newHeading); } if (textContent) { [...textContent.childNodes].forEach(node => { if (node !== heading) { textDiv.appendChild(node.cloneNode(true)); } }); } wrapper.appendChild(textDiv); return wrapper; } if (widget.classList.contains("static_image_and_table")) { const container = widget.querySelector(".container"); if (!container) return; const heading = container.querySelector("h2"); const textContent = container.querySelector(".content"); const img = container.querySelector("img"); const layout = ["top", "right", "bottom", "left"].find(dir => container.classList.contains(dir)) || "left"; const shape = container.classList.contains("circle") ? "circle" : "rectangle"; const wrapper = document.createElement("div"); wrapper.classList.add("widget", "static_image_and_table", `text-${layout}`, `shape-${shape}`); // Create image element const newImg = document.createElement("img"); newImg.src = img?.src || ""; newImg.alt = img?.alt || ""; // ensure accessibility newImg.classList.add("image"); wrapper.appendChild(newImg); // Create text content const textDiv = document.createElement("div"); textDiv.classList.add("text-content"); if (heading) { const newHeading = document.createElement("h3"); newHeading.textContent = heading.textContent; textDiv.appendChild(newHeading); } if (textContent) { [...textContent.childNodes].forEach(node => { if (node !== heading) { textDiv.appendChild(node.cloneNode(true)); } }); } wrapper.appendChild(textDiv); return wrapper; } if (widget.classList.contains("static_youtube_video")) { const iframe = widget.querySelector("iframe"); if (iframe) { iframe.removeAttribute("width"); iframe.removeAttribute("height"); iframe.removeAttribute("frameborder"); iframe.setAttribute("allow", "encrypted-media; picture-in-picture"); iframe.setAttribute("allowfullscreen", ""); iframe.setAttribute("loading", "lazy"); } } if (widget.classList.contains("static_collapsable_text")) { // Extract dynamic title and content const title = widget.querySelector(".headline .text")?.textContent?.trim() || "Untitled"; const contentHTML = widget.querySelector(".content")?.innerHTML || ""; // Create new widget wrapper const wrapper = document.createElement("div"); wrapper.classList.add("widget", "static_collapsible_text"); // Create
                    structure const details = document.createElement("details"); // Create with text and icon const summary = document.createElement("summary"); summary.innerHTML = ` ${title} `; // Create content container const content = document.createElement("div"); content.classList.add("collapsible-content"); content.innerHTML = contentHTML; // Assemble and return details.appendChild(summary); details.appendChild(content); wrapper.appendChild(details); return wrapper; } return widget.cloneNode(true); } function createArcSvg(section) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('focusable', 'false'); const use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#svg_header_arc'); svg.appendChild(use); const colorClass = [...section.classList].find(cls => cls.startsWith('section-color_')); svg.classList.add(colorClass || 'section-color_none', 'section-arc'); return svg; } // 2) BUILD SECTIONS + TOC const newSections = []; const tocItems = []; let currentSection = null; let sectionCounter = 1; let lastColorIsPrimary = true; // Hero + h1/subtitle const firstSection = document.createElement("section"); firstSection.id = "hero_section"; firstSection.classList.add("section-color_base"); if (hero) firstSection.appendChild(restructureWidget(hero)); if (h1) firstSection.appendChild(restructureWidget(h1)); if (firstSection.children.length) newSections.push(firstSection); for (const el of rawWidgets) { if (isSectionMarker(el)) { const txt = el.querySelector("span")?.textContent || ""; const nameMatch = txt.match(/name\s*=\s*"([^"]*)"/i); const colorMatch = txt.match(/color\s*=\s*"([^"]*)"/i); const nameVal = nameMatch?.[1]?.trim(); const colorVal = colorMatch?.[1]?.trim(); let idString = sectionCounter++ if (nameVal) { idString = nameVal.replace(/ /g, "_"); } const section = document.createElement("section"); section.id = `section_${idString}`; const mapped = mapColorToClass(colorVal); if (mapped) { section.classList.add(mapped); lastColorIsPrimary = false; } else { const alt = lastColorIsPrimary ? "section-color_primary" : "section-color_base"; section.classList.add(alt); lastColorIsPrimary = !lastColorIsPrimary; } if (nameVal) { const title = document.createElement("h2"); title.innerText = nameVal; section.appendChild(title); tocItems.push({ id: section.id, name: nameVal }); } currentSection = section; newSections.push(section); continue; } // // Default: append processed widget to section const target = currentSection || firstSection; const processed = restructureWidget(el); if (processed) target.appendChild(processed); } // INSERT TOC under h1/subtitle if (tocItems.length >= 2) { const h1 = firstSection.querySelector("h1"); const h2 = firstSection.querySelector("h2"); if (h1) { const nav = document.createElement("nav"); nav.classList.add("table-of-contents"); const header = document.createElement("h2"); header.innerText = "Inhoud:"; nav.appendChild(header); const ol = document.createElement("ol"); tocItems.forEach(({ id, name }) => { const li = document.createElement("li"); const a = document.createElement("a"); a.href = `#${id}`; a.innerText = name; li.appendChild(a); ol.appendChild(li); }); nav.appendChild(ol); if (h2) { const description = document.createElement("div") description.classList.add("table-of-contents-description") const descriptionText = document.createElement("div") descriptionText.innerHTML = `

                    ${h2.innerText}

                    ` h2.remove() description.appendChild(nav) h1.insertAdjacentElement("afterend", description); description.appendChild(descriptionText) } else { h1.insertAdjacentElement("afterend", nav); } } } // replace
                    content main.innerHTML = ""; newSections.forEach(section => { if (section.children.length) { section.appendChild(createArcSvg(section)); main.appendChild(section); } }); if (firstSection && homepageOverwrite) { firstSection.innerHTML = `
                    YOUTH FOR UNDERSTANDING Interculturele Uitwissilingen
                    ` } if (pagesNavElement && document.querySelector(".main-nav")) { document.querySelector(".main-nav").insertAdjacentElement('afterend', pagesNavElement); }; // Rail dots // 3) INJECT PROGRESS RAIL PLACEHOLDER if (tocItems.length >= 2) { const rail = document.createElement("nav"); rail.id = "progress-rail"; // styling via CSS; here only structural const fill = document.createElement("div"); fill.id = "progress-fill"; rail.appendChild(fill); // empty dots for each named section tocItems.forEach(({ id, name }) => { const dot = document.createElement("a"); dot.className = "rail-dot"; dot.href = `#${id}`; dot.dataset.section = id; dot.dataset.requireHover = ""; rail.appendChild(dot); const label = document.createElement("span"); label.innerText = `${name}`; label.ariaHidden = true; dot.appendChild(label) }); document.body.appendChild(rail); } }), // 4) ONCE EVERYTHING IS LOADED → POSITION DOTS & HOOK SCROLL window.addEventListener("load", () => { const dots = document.querySelectorAll(".rail-dot"); if (!dots.length) return; const docHeight = document.documentElement.scrollHeight - window.innerHeight; const fill = document.getElementById("progress-fill"); dots.forEach(dot => { const id = dot.dataset.section; const sec = document.getElementById(id); if (!sec) return; const topPx = sec.getBoundingClientRect().top + window.scrollY; dot.style.top = `${(topPx / docHeight) * 100}%`; dot.addEventListener("click", e => { e.preventDefault(); sec.scrollIntoView({ behavior: "smooth" }); }); }); const updateFill = () => { const pct = Math.min((window.scrollY / docHeight) * 100, 100); fill.style.height = pct + "%"; }; window.addEventListener("scroll", updateFill); updateFill(); }); function matchSvgTextSize() { const remRef = document.querySelector("#nav_pages_size_reference"); const svg = document.querySelector(".nav-pages-text"); const svgText = svg.querySelector("text"); if (!remRef || !svg || !svgText) return; const remPx = parseFloat(getComputedStyle(remRef).fontSize); // e.g. 40px const svgHeightPx = svg.getBoundingClientRect().height; const viewBoxHeight = parseFloat(svg.getAttribute("viewBox").split(" ")[3]); if (svgHeightPx && viewBoxHeight) { const correctedFontSize = (remPx * viewBoxHeight) / svgHeightPx; svgText.setAttribute("font-size", correctedFontSize); } } // Run on load and resize document.addEventListener("DOMContentLoaded", matchSvgTextSize); window.addEventListener("resize", matchSvgTextSize); // Ensure page is visible after processing const observer = new MutationObserver((mutations, obs) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if ( node.tagName === 'LINK' && node.getAttribute('rel') === 'stylesheet' ) { const href = node.getAttribute('href'); if ( href && ( href.includes('/assets/') || href.includes('/Bouwplaats 3 - Testing_files/') || href.includes('about') ) && href.endsWith('.css') ) { node.remove(); obs.disconnect(); // Stop observing after removal return; } } } } }); // Start observing the whole document for new elements observer.observe(document.documentElement, { childList: true, subtree: true }); function clamp(value, min, max) { return Math.min(Math.max(value, min), max); } function updateArcHeights() { const svgs = document.querySelectorAll('.section-arc'); svgs.forEach(svg => { const rect = svg.getBoundingClientRect(); const top = rect.top; const start = window.innerHeight * 0.4; // 80% viewport height const end = window.innerHeight * 0.2; // 20% viewport height if (top <= start && top >= end) { // Normalize between 0 and 1 const progress = (start - top) / (start - end); const clampedProgress = clamp(progress, 0, 1); // Interpolate between 10rem and 0rem const maxHeightRem = 10; const newHeightRem = maxHeightRem * (1 - clampedProgress); svg.style.height = `${newHeightRem}rem`; } else if (top > start) { svg.style.height = `10rem`; } else if (top < end) { svg.style.height = `0rem`; } }); } window.addEventListener('scroll', updateArcHeights); window.addEventListener('resize', updateArcHeights); // Initial call to set heights correctly updateArcHeights(); // -------------------- document.addEventListener("DOMContentLoaded", () => { const nav = document.querySelector('nav'); const pagesNav = document.querySelector(".pages-nav") const brand = document.querySelector('.nav-brand'); const icons = document.querySelector('.nav-icons'); let isShrunk = false; function forceReflow(el) { void el.offsetWidth; // Forces layout reflow } function animateTransition(shrinking) { brand.classList.remove('animate-fly'); icons.classList.remove('animate-fly'); forceReflow(brand); // Ensure animations restart cleanly forceReflow(icons); brand.classList.add('animate-fly'); icons.classList.add('animate-fly'); if (pagesNav) { if (shrinking) { pagesNav.classList.add('shrink'); } else { pagesNav.classList.remove('shrink'); } } // Delay the class change (layout change) to AFTER 0.5s setTimeout(() => { if (shrinking) { nav.classList.add('shrink'); } else { nav.classList.remove('shrink'); } }, 500); } window.addEventListener('scroll', () => { const shouldShrink = window.scrollY > 1; if (shouldShrink && !isShrunk) { animateTransition(true); isShrunk = true; } else if (!shouldShrink && isShrunk) { animateTransition(false); isShrunk = false; } }); }); document.addEventListener("DOMContentLoaded", () => { const arcPath = document.querySelector('#svg_nav_pages_arc'); const arcDraw = document.querySelector(".nav-pages-arc"); window.addEventListener('scroll', () => { const isScrolled = window.scrollY > 1; // Animate the arc path itself gsap.to(arcPath, { delay: 0.55, duration: 0.5, attr: { d: isScrolled ? "M 0,15 C 32,15 66,15 100,15 134,15 168,15 200,15" // Flattened : "M 0,15 C 32,25 66,30 100,30 134,30 168,25 200,15" // Original curve }, ease: "ease" }); }); }); // 2 Tap Tooltips for Mobile (function () { const isTouchDevice = matchMedia('(hover: none) and (pointer: coarse)').matches; if (!isTouchDevice) return; let activeEl = null; document.addEventListener('click', function (e) { const el = e.target.closest('[data-require-hover]'); // If clicking somewhere else, remove active state if (activeEl && el !== activeEl) { activeEl.classList.remove('hover-active'); activeEl = null; } // If the clicked element does not require hover, do nothing else if (!el) return; // If it’s the first tap, activate hover and block action if (el !== activeEl) { e.preventDefault(); e.stopImmediatePropagation(); el.classList.add('hover-active'); activeEl = el; } else { // Second tap on the same element — allow action, remove hover el.classList.remove('hover-active'); activeEl = null; } }, true); // Use capture to block native handlers })(); -->