From 5e330d5910cadc15db0db85c7eec83f0bd014ae1 Mon Sep 17 00:00:00 2001
From: bernardhanna
Date: Wed, 8 Jul 2026 09:29:31 +0100
Subject: [PATCH] edit to online course page
---
app/OnlineCoursesPage.php | 4 +-
...0_clear_online_courses_placeholder_cta.php | 27 ++
.../seeders/EditableStaticPagesSeeder.php | 4 +-
docs/getting-help-public.md | 67 ++++
resources/views/online-courses.blade.php | 6 +-
.../fix-blog-chevron-position-remove-arrow.sh | 91 +++++
scripts/fix-blog-footer-js-stray-brace.sh | 19 +
scripts/fix-blog-footer-script-clean.sh | 190 ++++++++++
scripts/fix-blog-mobile-menu-clickability.sh | 174 +++++++++
scripts/fix-blog-mobile-menu-js-syntax.sh | 92 +++++
scripts/fix-blog-mobile-menu-laravel-match.sh | 330 ++++++++++++++++++
scripts/fix-blog-mobile-menu-snappy.sh | 178 ++++++++++
scripts/fix-blog-mobile-menu-v2.sh | 131 +++++++
scripts/patch-blog-menu-share-stories-icon.sh | 22 +-
.../patch-blog-mobile-menu-parent-links.sh | 106 ++++++
15 files changed, 1416 insertions(+), 25 deletions(-)
create mode 100644 database/migrations/2026_07_08_090000_clear_online_courses_placeholder_cta.php
create mode 100644 docs/getting-help-public.md
create mode 100755 scripts/fix-blog-chevron-position-remove-arrow.sh
create mode 100755 scripts/fix-blog-footer-js-stray-brace.sh
create mode 100755 scripts/fix-blog-footer-script-clean.sh
create mode 100755 scripts/fix-blog-mobile-menu-clickability.sh
create mode 100755 scripts/fix-blog-mobile-menu-js-syntax.sh
create mode 100755 scripts/fix-blog-mobile-menu-laravel-match.sh
create mode 100755 scripts/fix-blog-mobile-menu-snappy.sh
create mode 100755 scripts/fix-blog-mobile-menu-v2.sh
create mode 100755 scripts/patch-blog-mobile-menu-parent-links.sh
diff --git a/app/OnlineCoursesPage.php b/app/OnlineCoursesPage.php
index e21f87f2a..aaffb2b27 100644
--- a/app/OnlineCoursesPage.php
+++ b/app/OnlineCoursesPage.php
@@ -30,8 +30,8 @@ public static function config(): self
'use_dynamic_content' => false,
'hero_title' => 'Online Courses',
'hero_text' => __('online-courses.online-courses-text'),
- 'hero_cta_text' => 'Optional secondary CTA introduction to online courses',
- 'hero_cta_link' => '/',
+ 'hero_cta_text' => null,
+ 'hero_cta_link' => null,
'intro_title' => 'EU Code Week Online Courses',
'intro_text_1' => __('online-courses.online-courses-sub-text1'),
'intro_text_2' => __('online-courses.online-courses-sub-text2'),
diff --git a/database/migrations/2026_07_08_090000_clear_online_courses_placeholder_cta.php b/database/migrations/2026_07_08_090000_clear_online_courses_placeholder_cta.php
new file mode 100644
index 000000000..97da1854d
--- /dev/null
+++ b/database/migrations/2026_07_08_090000_clear_online_courses_placeholder_cta.php
@@ -0,0 +1,27 @@
+where('hero_cta_text', 'Optional secondary CTA introduction to online courses')
+ ->update([
+ 'hero_cta_text' => null,
+ 'hero_cta_link' => null,
+ ]);
+ }
+
+ public function down(): void
+ {
+ // Placeholder CTA intentionally removed; no restore.
+ }
+};
diff --git a/database/seeders/EditableStaticPagesSeeder.php b/database/seeders/EditableStaticPagesSeeder.php
index 3234d0fd3..2d898b1dc 100644
--- a/database/seeders/EditableStaticPagesSeeder.php
+++ b/database/seeders/EditableStaticPagesSeeder.php
@@ -32,8 +32,8 @@ private function seedOnlineCoursesPage(): void
'use_dynamic_content' => true,
'hero_title' => 'Online Courses',
'hero_text' => __('online-courses.online-courses-text'),
- 'hero_cta_text' => 'Optional secondary CTA introduction to online courses',
- 'hero_cta_link' => '/',
+ 'hero_cta_text' => null,
+ 'hero_cta_link' => null,
'intro_title' => 'EU Code Week Online Courses',
'intro_text_1' => __('online-courses.online-courses-sub-text1'),
'intro_text_2' => __('online-courses.online-courses-sub-text2'),
diff --git a/docs/getting-help-public.md b/docs/getting-help-public.md
new file mode 100644
index 000000000..04b6021a8
--- /dev/null
+++ b/docs/getting-help-public.md
@@ -0,0 +1,67 @@
+# Getting help with CodeWeek
+
+This page explains how to get help with your **CodeWeek** account, activities, or
+certificates. It's for teachers, organisers, and participants.
+
+---
+
+## How to ask for help
+
+The fastest way to reach the CodeWeek team is the official contact channel:
+
+- **Contact form / email:** use the contact option on the CodeWeek website at
+ [codeweek.eu](https://codeweek.eu)
+
+When you get in touch, please include:
+
+1. **The email address** you registered with on CodeWeek
+2. **Your CodeWeek 4all code** (if your question is about an activity or certificate)
+3. **A clear description** of the problem and what you'd like help with
+
+Including these details up front helps the team look into your request without
+needing to email back and forth.
+
+---
+
+## Common topics
+
+### Signing in / account access
+If you can't sign in, double-check you're using the email address you originally
+registered with. If you still have trouble, contact the team with that email address
+and a short description of what happens when you try to log in.
+
+### Certificates
+Participation and Certificate of Excellence availability depends on the activities
+registered under your 4all code. You can find and download available certificates
+after signing in:
+
+- [codeweek.eu/certificates](https://codeweek.eu/certificates)
+
+If you believe a certificate is missing, include your **registered email** and your
+**4all code** when you contact the team so they can check your specific case.
+
+### Activities and events
+For questions about adding, editing, or finding activities, contact the team with your
+registered email and 4all code and describe what you're trying to do.
+
+---
+
+## What to expect
+
+- The team reviews requests and replies by email.
+- For account or data changes, the team verifies the details before making any change —
+ so please use the **same email address** associated with your CodeWeek account where
+ possible.
+- Response times vary with demand, especially around the annual CodeWeek period.
+
+---
+
+## Privacy
+
+Only share the information needed to resolve your request (typically your registered
+email and 4all code). Please don't include passwords or other sensitive personal data
+in your message.
+
+---
+
+*For more about CodeWeek, visit [codeweek.eu](https://codeweek.eu).*
diff --git a/resources/views/online-courses.blade.php b/resources/views/online-courses.blade.php
index da3bf6b27..080d1d92c 100644
--- a/resources/views/online-courses.blade.php
+++ b/resources/views/online-courses.blade.php
@@ -38,12 +38,14 @@
@lang('online-courses.online-courses-text')
@endif
+ @if($dynamic && $page->hero_cta_text && $page->hero_cta_link)
-
- {{ $dynamic && $page->hero_cta_text ? $page->hero_cta_text : 'Optional secondary CTA introduction to online courses' }}
+
+ {{ $page->hero_cta_text }}
›
+ @endif
diff --git a/scripts/fix-blog-chevron-position-remove-arrow.sh b/scripts/fix-blog-chevron-position-remove-arrow.sh
new file mode 100755
index 000000000..57b514a2e
--- /dev/null
+++ b/scripts/fix-blog-chevron-position-remove-arrow.sh
@@ -0,0 +1,91 @@
+#!/usr/bin/env bash
+# Chevron position (right: -.8rem) + remove arrow.png from Share your stories submenu.
+set -euo pipefail
+
+THEME="${THEME:-/var/www/html/blog/wp-content/themes/eucodewe-1389}"
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+SHARE_TEXT="Share your stories"
+
+[[ -d "$THEME" ]] || { echo "Theme not found: $THEME"; exit 1; }
+[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found: $WP_ROOT"; exit 1; }
+
+cd "$THEME"
+chmod u+w new.css 2>/dev/null || true
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+css = Path("new.css")
+text = css.read_text()
+
+# Don't hide chevron on last top-level item when it has children (Blog)
+text = text.replace(
+ ".header__menu nav > ul > li:last-child a:after {\n display: none;\n}",
+ ".header__menu nav > ul > li:last-child:not(.menu-item-has-children) a:after {\n display: none;\n}",
+)
+
+chevron_sel = (
+ "li.menu-item.menu-item-type-custom.menu-item-object-custom.menu-item-has-children a:after"
+)
+chevron_block = """li.menu-item.menu-item-type-custom.menu-item-object-custom.menu-item-has-children a:after {
+ content: "";
+ width: 10px;
+ height: 10px;
+ background: url(https://codeweek.eu/blog/wp-content/uploads/2025/01/Vector-6.svg);
+ display: block;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ transition: 0.3s;
+ right: -.8rem;
+}"""
+
+pattern = re.compile(
+ r"li\.menu-item\.menu-item-type-custom\.menu-item-object-custom\.menu-item-has-children a:after\s*\{[^}]+\}",
+ re.S,
+)
+if pattern.search(text):
+ text = pattern.sub(chevron_block, text, count=1)
+ print("Updated chevron rule with right: -.8rem")
+else:
+ print("WARNING: chevron rule not found — append manually if needed")
+
+# Remove submenu arrow layout overrides (no longer needed)
+for pat in [
+ r"\n/\* Blog dropdown: Share your stories.*?(?=\n/\* |\Z)",
+ r"\n/\* Blog nav: restore dropdown chevron.*?(?=\n/\* |\Z)",
+ r"\n/\* Blog nav: chevron on Blog.*?(?=\n/\* |\Z)",
+]:
+ text = re.sub(pat, "\n", text, flags=re.S)
+
+css.write_text(text)
+print("Patched new.css")
+PY
+
+echo "--- chevron rule ---"
+grep -n -A12 "menu-item-has-children a:after" new.css | head -15
+
+cd "$WP_ROOT"
+command -v wp >/dev/null 2>&1 || { echo "wp-cli required for menu arrow removal"; exit 1; }
+
+SHARE_ID=""
+for menu in primary header main "Menu 1" 2; do
+ SHARE_ID=$(wp menu item list "$menu" --fields=db_id,title --format=csv --allow-root 2>/dev/null \
+ | awk -F, -v t="$SHARE_TEXT" '$2 ~ t {print $1; exit}') || true
+ [[ -n "${SHARE_ID:-}" ]] && break
+done
+SHARE_ID="${SHARE_ID:-8142}"
+
+wp eval --allow-root "
+\$id = (int) ${SHARE_ID};
+delete_post_meta(\$id, 'rt-wp-menu-custom-fields');
+delete_transient('rt-wp-menu-custom-fields-' . \$id);
+echo \"Removed arrow custom HTML from menu item \$id\n\";
+"
+
+wp cache flush --allow-root 2>/dev/null | tail -1 || true
+for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm; do
+ systemctl is-active --quiet "$svc" 2>/dev/null && systemctl reload "$svc" && echo "Reloaded $svc" && break
+done
+echo "Done. Hard-refresh https://codeweek.eu/blog/"
diff --git a/scripts/fix-blog-footer-js-stray-brace.sh b/scripts/fix-blog-footer-js-stray-brace.sh
new file mode 100755
index 000000000..77749e391
--- /dev/null
+++ b/scripts/fix-blog-footer-js-stray-brace.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# Remove stray }); breaking blog footer JS.
+set -euo pipefail
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+cd "$WP_ROOT"
+wp eval --allow-root '
+$settings = get_option("auhfc_settings_sitewide");
+$footer = $settings["footer"] ?? "";
+$footer = str_replace(
+ "$(\".mobile-nav .menu-menu-1-container\").append(actionButtons);\n});\n\t});",
+ "$(\".mobile-nav .menu-menu-1-container\").append(actionButtons);\n});",
+ $footer
+);
+$settings["footer"] = $footer;
+update_option("auhfc_settings_sitewide", $settings);
+echo "Removed stray }); from footer script\n";
+' 2>/dev/null | grep -v '^Deprecated:\|^Notice:\|^Warning:\|^PHP Warning:' || true
+wp cache flush --allow-root 2>/dev/null | tail -1 || true
+echo Done.
diff --git a/scripts/fix-blog-footer-script-clean.sh b/scripts/fix-blog-footer-script-clean.sh
new file mode 100755
index 000000000..e01e37106
--- /dev/null
+++ b/scripts/fix-blog-footer-script-clean.sh
@@ -0,0 +1,190 @@
+#!/usr/bin/env bash
+# Repair corrupted blog footer script and apply Laravel-style mobile menu JS.
+set -euo pipefail
+
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+CACHE_VER="${CACHE_VER:-$(date +%Y%m%d%H%M)}"
+
+cd "$WP_ROOT"
+command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; }
+
+PATCH_PHP="$(mktemp)"
+cat > "$PATCH_PHP" <<'PHP'
+
+ $(window).scroll(function(){
+ if ($(window).scrollTop() >= 330) {
+ $('.sticky-btn').addClass('fixed');
+ }
+ else {
+ $('.sticky-btn').removeClass('fixed');
+ }
+ });
+ $(window).scroll(function(){
+ if ($(window).scrollTop() >= 330) {
+ $('.header').addClass('header-fixed');
+ }
+ else {
+ $('.header').removeClass('header-fixed');
+ }
+ });
+ $(document).ready(function () {
+ var actionButtons = `
+
+ `;
+ $(".mobile-nav .menu-menu-1-container").append(actionButtons);
+ });
+
+(function () {
+ var ARROW_SRC = "https://codeweek.eu/images/chevron-down-icon.svg";
+
+ function getMenuLabel(menuItem) {
+ var label = "";
+ menuItem.childNodes.forEach(function (node) {
+ if (node.nodeType === Node.TEXT_NODE) {
+ label += node.textContent;
+ }
+ });
+ return label.replace(/\s+/g, " ").trim();
+ }
+
+ function injectMobileParentLinks(menuItem) {
+ var href = menuItem.getAttribute("href") || "";
+ var isVoidHref = !href || href === "#" || href.indexOf("javascript:") === 0;
+ if (isVoidHref) {
+ return;
+ }
+
+ var parentItem = menuItem.parentElement;
+ var submenu = parentItem.querySelector(":scope > .sub-menu");
+ if (!submenu || submenu.querySelector(".mobile-parent-link")) {
+ return;
+ }
+
+ var li = document.createElement("li");
+ li.className = "menu-item mobile-parent-link menu-item-type-custom menu-item-object-custom";
+ var link = document.createElement("a");
+ link.href = href;
+ link.textContent = getMenuLabel(menuItem) || menuItem.textContent.replace(/\s+/g, " ").trim();
+ li.appendChild(link);
+ submenu.insertBefore(li, submenu.firstChild);
+ }
+
+ function syncMenuSectionOpen() {
+ var menuToggle = document.getElementById("menuToggle");
+ if (!menuToggle) {
+ return;
+ }
+ menuToggle.classList.toggle(
+ "menu-section-open",
+ !!menuToggle.querySelector(".menu-wrapper > .menu-item-has-children.active")
+ );
+ }
+
+ function toggleItem(parentItem) {
+ var willOpen = !parentItem.classList.contains("active");
+ var siblings = parentItem.parentElement ? parentItem.parentElement.children : [];
+
+ Array.prototype.forEach.call(siblings, function (sibling) {
+ if (sibling !== parentItem && sibling.classList && sibling.classList.contains("menu-item-has-children")) {
+ sibling.classList.remove("active");
+ }
+ });
+
+ parentItem.classList.toggle("active", willOpen);
+ syncMenuSectionOpen();
+ }
+
+ function initBlogMobileMenu() {
+ var toggle = document.querySelector("#menuToggle input, #menuToggleCheckbox");
+ if (toggle) {
+ toggle.addEventListener("change", function () {
+ document.body.style.overflow = this.checked ? "hidden" : "";
+ if (!this.checked) {
+ document.querySelectorAll("#menuToggle .menu-item-has-children.active").forEach(function (el) {
+ el.classList.remove("active");
+ });
+ syncMenuSectionOpen();
+ }
+ });
+ }
+
+ document.querySelectorAll("#menuToggle .menu-item-has-children > a").forEach(function (menuItem) {
+ if (!menuItem.querySelector(".arrow-icon")) {
+ var arrow = document.createElement("img");
+ arrow.className = "arrow-icon";
+ arrow.src = ARROW_SRC;
+ arrow.alt = "";
+ arrow.setAttribute("aria-hidden", "true");
+ menuItem.appendChild(arrow);
+ }
+
+ injectMobileParentLinks(menuItem);
+
+ var arrow = menuItem.querySelector(".arrow-icon");
+ if (arrow && !arrow.dataset.bound) {
+ arrow.dataset.bound = "1";
+ arrow.addEventListener("click", function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+ toggleItem(menuItem.parentElement);
+ });
+ }
+
+ if (menuItem.dataset.bound) {
+ return;
+ }
+ menuItem.dataset.bound = "1";
+
+ menuItem.addEventListener("click", function (event) {
+ if (event.target.closest(".arrow-icon")) {
+ return;
+ }
+
+ var href = menuItem.getAttribute("href") || "";
+ var isVoidHref = !href || href === "#" || href.indexOf("javascript:") === 0;
+
+ if (isVoidHref) {
+ event.preventDefault();
+ toggleItem(menuItem.parentElement);
+ }
+ });
+ });
+
+ syncMenuSectionOpen();
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", initBlogMobileMenu);
+ } else {
+ initBlogMobileMenu();
+ }
+})();
+
+FOOTER;
+
+$settings['footer'] = $footer;
+update_option('auhfc_settings_sitewide', $settings);
+echo "Rebuilt clean footer script\n";
+PHP
+
+wp eval-file "$PATCH_PHP" --allow-root 2>/dev/null | grep -E '^(Rebuilt|Success)' || wp eval-file "$PATCH_PHP" --allow-root | tail -3
+rm -f "$PATCH_PHP"
+
+THEME="$WP_ROOT/wp-content/themes/eucodewe-1389"
+for header in header.php header-v2.php; do
+ [[ -f "$THEME/$header" ]] || continue
+ sed -i "s|/new.css?v=[0-9]*|/new.css?v=$CACHE_VER|g; s|/new.css' ?>|/new.css?v=$CACHE_VER' ?>|g" "$THEME/$header"
+done
+
+wp cache flush --allow-root 2>/dev/null | tail -1 || true
+echo "Done. CSS v=$CACHE_VER"
diff --git a/scripts/fix-blog-mobile-menu-clickability.sh b/scripts/fix-blog-mobile-menu-clickability.sh
new file mode 100755
index 000000000..090766146
--- /dev/null
+++ b/scripts/fix-blog-mobile-menu-clickability.sh
@@ -0,0 +1,174 @@
+#!/usr/bin/env bash
+# Fix blog mobile menu: hide overlapping promo, scope JS to .mobile-nav, repair footer script.
+set -euo pipefail
+
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}"
+
+[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found"; exit 1; }
+[[ -f "$THEME/new.css" ]] || { echo "Theme new.css not found"; exit 1; }
+command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; }
+
+cd "$THEME"
+chmod u+w new.css 2>/dev/null || true
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+css = Path("new.css")
+text = css.read_text()
+marker = "/* Mobile nav clickability fix */"
+block = """
+/* Mobile nav clickability fix */
+@media (max-width: 1080px) {
+ .header__menu.menu-header {
+ display: none !important;
+ pointer-events: none !important;
+ }
+
+ .mobile-nav li.box-featured,
+ .mobile-nav li.rt-custom-menu-field-item,
+ .mobile-nav #menu-item-5842,
+ .mobile-nav .rt-wp-menu-custom-fields-wrapper {
+ display: none !important;
+ pointer-events: none !important;
+ max-height: 0 !important;
+ overflow: hidden !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ min-height: 0 !important;
+ }
+
+ .mobile-nav .menu-item-has-children:not(.active) > .sub-menu {
+ max-height: 0 !important;
+ overflow: hidden !important;
+ visibility: hidden !important;
+ pointer-events: none !important;
+ padding-top: 0 !important;
+ }
+
+ .mobile-nav .menu-item-has-children.active > .sub-menu {
+ max-height: none !important;
+ overflow: visible !important;
+ visibility: visible !important;
+ pointer-events: auto !important;
+ }
+
+ .mobile-nav nav.menu-menu-1-container a {
+ display: block;
+ position: relative;
+ z-index: 5;
+ pointer-events: auto;
+ }
+}
+"""
+
+if marker in text:
+ text = re.sub(r"/\* Mobile nav clickability fix \*/[\s\S]*?(?=\n/\* |\Z)", block.strip() + "\n", text, count=1)
+else:
+ text = text.rstrip() + "\n\n" + block.strip() + "\n"
+
+css.write_text(text)
+print("Patched new.css")
+PY
+
+cd "$WP_ROOT"
+
+PATCH_PHP="$(mktemp)"
+cat > "$PATCH_PHP" <<'PHP'
+ a").forEach(function (menuItem) {
+ menuItem.addEventListener("click", function (event) {
+ var href = menuItem.getAttribute("href") || "";
+ var isVoidHref = !href || href === "#" || href.indexOf("javascript:") === 0;
+ var rect = menuItem.getBoundingClientRect();
+ var clickedChevron = event.clientX >= rect.right - 40;
+
+ if (!isVoidHref && !clickedChevron) {
+ return;
+ }
+
+ event.preventDefault();
+ var parentItem = menuItem.parentElement;
+ var willOpen = !parentItem.classList.contains("active");
+
+ document.querySelectorAll(".mobile-nav nav.menu-menu-1-container .menu-item-has-children").forEach(function (otherItem) {
+ otherItem.classList.remove("active");
+ });
+
+ if (willOpen) {
+ parentItem.classList.add("active");
+ }
+ });
+});
+JS;
+
+$updated = false;
+foreach ($settings as $key => $value) {
+ if (! is_string($value) || strpos($value, 'menuToggle') === false) {
+ continue;
+ }
+
+ $value = preg_replace(
+ '/document\.querySelector\("#menuToggle input"\)\.addEventListener\("change"[\s\S]*?menu-item-has-children > a"\)[\s\S]*?\}\);\s*(?:\}\);\s*)?/m',
+ '',
+ $value
+ );
+ $value = preg_replace(
+ '/document\.querySelectorAll\([\'"]\.?mobile-nav nav\.menu-menu-1-container \.menu-item-has-children > a[\'"]\)[\s\S]*?\}\);\s*(?:\}\);\s*)?/m',
+ '',
+ $value
+ );
+ $value = preg_replace(
+ '/document\.querySelectorAll\([\'"]nav\.menu-menu-1-container \.menu-item-has-children > a[\'"]\)[\s\S]*?\}\);\s*(?:\}\);\s*)?/m',
+ '',
+ $value
+ );
+
+ $insertBefore = '';
+ $pos = strrpos($value, $insertBefore);
+ if ($pos === false) {
+ fwrite(STDERR, "ERROR: no in {$key}\n");
+ exit(1);
+ }
+
+ $settings[$key] = substr($value, 0, $pos) . trim($mobileJs) . "\n" . substr($value, $pos);
+ $updated = true;
+ echo "Rebuilt scoped mobile menu JS in auhfc_settings_sitewide[{$key}]\n";
+ break;
+}
+
+if (! $updated) {
+ fwrite(STDERR, "ERROR: footer script not found\n");
+ exit(1);
+}
+
+update_option('auhfc_settings_sitewide', $settings);
+PHP
+
+wp eval-file "$PATCH_PHP" --allow-root 2>/dev/null | grep -E '^(Patched|Rebuilt|Success)' || true
+rm -f "$PATCH_PHP"
+
+wp cache flush --allow-root 2>/dev/null | tail -1 || true
+for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm; do
+ if systemctl is-active --quiet "$svc" 2>/dev/null; then
+ systemctl reload "$svc"
+ echo "Reloaded $svc"
+ break
+ fi
+done
+
+echo "Done."
diff --git a/scripts/fix-blog-mobile-menu-js-syntax.sh b/scripts/fix-blog-mobile-menu-js-syntax.sh
new file mode 100755
index 000000000..3eced6862
--- /dev/null
+++ b/scripts/fix-blog-mobile-menu-js-syntax.sh
@@ -0,0 +1,92 @@
+#!/usr/bin/env bash
+# Repair blog mobile menu footer JS (broken change listener + menu handler).
+set -euo pipefail
+
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found"; exit 1; }
+command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; }
+
+cd "$WP_ROOT"
+
+PATCH_PHP="$(mktemp)"
+cat > "$PATCH_PHP" <<'PHP'
+ a").forEach(function (menuItem) {
+ menuItem.addEventListener("click", function (event) {
+ var href = menuItem.getAttribute("href") || "";
+ var isVoidHref = !href || href === "#" || href.indexOf("javascript:") === 0;
+ var rect = menuItem.getBoundingClientRect();
+ var clickedChevron = event.clientX >= rect.right - 40;
+
+ if (!isVoidHref && !clickedChevron) {
+ return;
+ }
+
+ event.preventDefault();
+ var parentItem = menuItem.parentElement;
+ parentItem.classList.toggle("active");
+
+ document.querySelectorAll("nav.menu-menu-1-container .menu-item-has-children").forEach(function (otherItem) {
+ if (otherItem !== parentItem) {
+ otherItem.classList.remove("active");
+ }
+ });
+ });
+});
+JS;
+
+$updated = false;
+foreach ($settings as $key => $value) {
+ if (! is_string($value) || strpos($value, 'menuToggle') === false) {
+ continue;
+ }
+
+ // Drop broken mobile-menu block from prior patches.
+ $value = preg_replace(
+ '/document\.querySelector\("#menuToggle input"\)\.addEventListener\("change"[\s\S]*?menu-item-has-children > a"\)[\s\S]*?\}\);\s*(?:\}\);\s*)?/m',
+ '',
+ $value
+ );
+ $value = preg_replace(
+ '/document\.querySelectorAll\([\'"]nav\.menu-menu-1-container \.menu-item-has-children > a[\'"]\)[\s\S]*?\}\);\s*(?:\}\);\s*)?/m',
+ '',
+ $value
+ );
+
+ $insertBefore = '';
+ $pos = strrpos($value, $insertBefore);
+ if ($pos === false) {
+ fwrite(STDERR, "ERROR: no in {$key}\n");
+ exit(1);
+ }
+
+ $settings[$key] = substr($value, 0, $pos) . trim($mobileBlock) . "\n" . substr($value, $pos);
+ $updated = true;
+ echo "Rebuilt mobile menu JS in auhfc_settings_sitewide[{$key}]\n";
+ break;
+}
+
+if (! $updated) {
+ fwrite(STDERR, "ERROR: footer script not found\n");
+ exit(1);
+}
+
+update_option('auhfc_settings_sitewide', $settings);
+PHP
+
+wp eval-file "$PATCH_PHP" --allow-root 2>/dev/null | grep -E '^(Rebuilt|Menu|Done|Success)' || true
+rm -f "$PATCH_PHP"
+wp cache flush --allow-root 2>/dev/null | tail -1 || true
+echo "Done."
diff --git a/scripts/fix-blog-mobile-menu-laravel-match.sh b/scripts/fix-blog-mobile-menu-laravel-match.sh
new file mode 100755
index 000000000..9c6247477
--- /dev/null
+++ b/scripts/fix-blog-mobile-menu-laravel-match.sh
@@ -0,0 +1,330 @@
+#!/usr/bin/env bash
+# Match blog mobile menu to Laravel: inline chevrons, visible submenus, sibling hide.
+set -euo pipefail
+
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}"
+CACHE_VER="${CACHE_VER:-$(date +%Y%m%d%H%M)}"
+
+[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found"; exit 1; }
+command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; }
+
+cd "$THEME"
+chmod u+w new.css header.php header-v2.php 2>/dev/null || true
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+css = Path("new.css")
+text = css.read_text()
+marker = "/* Mobile nav clickability fix */"
+block = """/* Mobile nav clickability fix */
+@media (max-width: 1080px) {
+ .header__menu.menu-header {
+ display: none !important;
+ pointer-events: none !important;
+ }
+
+ /* Laravel-style parent row: label left, chevron right */
+ #menuToggle .menu-item-has-children > a {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: space-between !important;
+ gap: 8px !important;
+ width: 100% !important;
+ font-size: 16px !important;
+ font-weight: 600 !important;
+ color: #1C4DA1 !important;
+ padding: 12px 0 !important;
+ text-decoration: none !important;
+ }
+
+ #menuToggle .menu-item-has-children > a::after {
+ display: none !important;
+ }
+
+ #menuToggle .menu-item-has-children > a .arrow-icon {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+ margin-left: auto;
+ transition: transform 0.3s ease;
+ }
+
+ #menuToggle .menu-item-has-children.active > a .arrow-icon {
+ transform: scaleY(-1);
+ }
+
+ /* Hide duplicate submenu header rows with WP custom arrow blocks */
+ #menuToggle #menu-item-5845,
+ #menuToggle #menu-item-5847,
+ #menuToggle #menu-item-5848,
+ #menuToggle li#menu-item-5842,
+ #menuToggle li.box-featured,
+ #menuToggle li.rt-custom-menu-field-item,
+ #menuToggle .rt-wp-menu-custom-fields-wrapper {
+ display: none !important;
+ pointer-events: none !important;
+ height: 0 !important;
+ max-height: 0 !important;
+ overflow: hidden !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ visibility: hidden !important;
+ }
+
+ /* Laravel: when a section is open, hide other top-level items */
+ #menuToggle:has(.menu-wrapper > .menu-item-has-children.active) .menu-wrapper > .menu-item-has-children:not(.active),
+ #menuToggle:has(.menu-wrapper > .menu-item-has-children.active) .menu-wrapper > .menu-item:not(.menu-item-has-children) {
+ display: none !important;
+ }
+
+ /* Collapsed submenus */
+ #menuToggle .menu-item-has-children:not(.active) > .sub-menu {
+ max-height: 0 !important;
+ overflow: hidden !important;
+ visibility: hidden !important;
+ pointer-events: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ }
+
+ /* Expanded direct submenu */
+ #menuToggle .menu-item-has-children.active > .sub-menu {
+ max-height: none !important;
+ overflow: visible !important;
+ visibility: visible !important;
+ pointer-events: auto !important;
+ display: block !important;
+ padding: 0 0 24px !important;
+ }
+
+ #menuToggle .menu-item-has-children.active > .sub-menu > li {
+ display: list-item !important;
+ visibility: visible !important;
+ pointer-events: auto !important;
+ margin-top: 16px !important;
+ }
+
+ #menuToggle .menu-item-has-children.active > .sub-menu > li > a {
+ display: block !important;
+ font-size: 16px !important;
+ font-weight: 600 !important;
+ color: #1C4DA1 !important;
+ padding: 0 !important;
+ }
+
+ /* Mega-menu: nested column sections */
+ #menuToggle .mega-menu-nav.active > .sub-menu > li.col-menu {
+ display: list-item !important;
+ margin-top: 20px !important;
+ }
+
+ #menuToggle .mega-menu-nav.active > .sub-menu > li.col-menu > a {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: space-between !important;
+ font-size: 16px !important;
+ font-weight: 600 !important;
+ }
+
+ #menuToggle .mega-menu-nav .col-menu:not(.active) > .sub-menu {
+ max-height: 0 !important;
+ overflow: hidden !important;
+ visibility: hidden !important;
+ pointer-events: none !important;
+ }
+
+ #menuToggle .mega-menu-nav .col-menu.active > .sub-menu {
+ max-height: none !important;
+ overflow: visible !important;
+ visibility: visible !important;
+ pointer-events: auto !important;
+ padding-left: 0 !important;
+ margin-top: 8px !important;
+ }
+
+ #menuToggle .mega-menu-nav .col-menu.active > .sub-menu > li {
+ display: list-item !important;
+ margin-top: 12px !important;
+ }
+
+ #menuToggle .mega-menu-nav .col-menu.active > .sub-menu a {
+ display: block !important;
+ font-weight: 600 !important;
+ color: #1C4DA1 !important;
+ }
+}
+"""
+
+if marker in text:
+ text = re.sub(r"/\* Mobile nav clickability fix \*/[\s\S]*?(?=\n/\* |\Z)", block.strip() + "\n", text, count=1)
+else:
+ text = text.rstrip() + "\n\n" + block.strip() + "\n"
+
+css.write_text(text)
+print("Patched new.css")
+PY
+
+for header in header.php header-v2.php; do
+ [[ -f "$header" ]] || continue
+ python3 - "$header" "$CACHE_VER" <<'PY'
+import re, sys
+path, ver = sys.argv[1], sys.argv[2]
+text = open(path).read()
+new = re.sub(
+ r"(get_stylesheet_directory_uri\(\)\s*\.\s*'/new\.css')(\?v=[^']*)?'",
+ rf"\1'?v={ver}'",
+ text,
+ count=1,
+)
+if new == text:
+ new = text.replace(
+ "get_stylesheet_directory_uri(). '/new.css' ?>",
+ f"get_stylesheet_directory_uri(). '/new.css?v={ver}' ?>",
+ )
+open(path, "w").write(text if new == text else new)
+print(f"Cache-busted {path} -> ?v={ver}")
+PY
+done
+
+cd "$WP_ROOT"
+
+PATCH_PHP="$(mktemp)"
+cat > "$PATCH_PHP" <<'PHP'
+ a").forEach(function (menuItem) {
+ if (!menuItem.querySelector(".arrow-icon")) {
+ var arrow = document.createElement("img");
+ arrow.className = "arrow-icon";
+ arrow.src = ARROW_SRC;
+ arrow.alt = "";
+ arrow.setAttribute("aria-hidden", "true");
+ menuItem.appendChild(arrow);
+ }
+
+ menuItem.addEventListener("click", function (event) {
+ var href = menuItem.getAttribute("href") || "";
+ var isVoidHref = !href || href === "#" || href.indexOf("javascript:") === 0;
+ var clickedArrow = event.target.closest(".arrow-icon");
+
+ if (clickedArrow || isVoidHref) {
+ event.preventDefault();
+ toggleItem(menuItem.parentElement);
+ }
+ });
+ });
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", initBlogMobileMenu);
+ } else {
+ initBlogMobileMenu();
+ }
+})();
+JS;
+
+$updated = false;
+foreach ($settings as $key => $value) {
+ if (! is_string($value) || strpos($value, 'menuToggle') === false) {
+ continue;
+ }
+
+ $value = preg_replace(
+ '/document\.querySelector\("#menuToggle input[\s\S]*?initBlogMobileMenu[\s\S]*?\}\)\(\);\s*/m',
+ '',
+ $value
+ );
+ $value = preg_replace(
+ '/document\.querySelectorAll\([\'"]#menuToggle \.menu-item-has-children > a[\'"]\)[\s\S]*?\}\);\s*/m',
+ '',
+ $value
+ );
+ $value = preg_replace(
+ '/document\.querySelectorAll\([\'"]\.mobile-nav nav\.menu-menu-1-container \.menu-item-has-children > a[\'"]\)[\s\S]*?\}\);\s*/m',
+ '',
+ $value
+ );
+ $value = preg_replace(
+ '/document\.querySelector\("#menuToggle input, #menuToggleCheckbox"\)\.addEventListener\("change"[\s\S]*?\}\);\s*/m',
+ '',
+ $value
+ );
+
+ $insertBefore = '';
+ $pos = strrpos($value, $insertBefore);
+ if ($pos === false) {
+ fwrite(STDERR, "ERROR: no in {$key}\n");
+ exit(1);
+ }
+
+ $settings[$key] = substr($value, 0, $pos) . trim($mobileJs) . "\n" . substr($value, $pos);
+ $updated = true;
+ echo "Rebuilt Laravel-style mobile menu JS in auhfc_settings_sitewide[{$key}]\n";
+ break;
+}
+
+if (! $updated) {
+ fwrite(STDERR, "ERROR: footer script not found\n");
+ exit(1);
+}
+
+update_option('auhfc_settings_sitewide', $settings);
+PHP
+
+wp eval-file "$PATCH_PHP" --allow-root
+rm -f "$PATCH_PHP"
+
+wp cache flush --allow-root 2>/dev/null | tail -1 || true
+
+for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm; do
+ if systemctl is-active --quiet "$svc" 2>/dev/null; then
+ systemctl reload "$svc"
+ echo "Reloaded $svc"
+ break
+ fi
+done
+
+echo "Done. new.css cache version: $CACHE_VER"
diff --git a/scripts/fix-blog-mobile-menu-snappy.sh b/scripts/fix-blog-mobile-menu-snappy.sh
new file mode 100755
index 000000000..90320848c
--- /dev/null
+++ b/scripts/fix-blog-mobile-menu-snappy.sh
@@ -0,0 +1,178 @@
+#!/usr/bin/env bash
+# Snappier blog mobile menu: instant toggles, no max-height animation, larger chevron tap targets.
+set -euo pipefail
+
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}"
+CACHE_VER="${CACHE_VER:-$(date +%Y%m%d%H%M)}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found"; exit 1; }
+
+cd "$THEME"
+chmod u+w new.css 2>/dev/null || true
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+css = Path("new.css")
+text = css.read_text()
+marker = "/* Mobile nav clickability fix */"
+block = """/* Mobile nav clickability fix */
+@media (max-width: 1080px) {
+ .header__menu.menu-header {
+ display: none !important;
+ pointer-events: none !important;
+ }
+
+ #menuToggle,
+ #menuToggle a,
+ #menuToggle .sub-menu {
+ -webkit-tap-highlight-color: transparent;
+ touch-action: manipulation;
+ }
+
+ /* Kill slow max-height animations from legacy menu CSS */
+ #menuToggle .sub-menu,
+ #menuToggle .menu-menu-1-container ul .sub-menu {
+ transition: none !important;
+ }
+
+ /* Laravel-style parent row: label left, chevron right */
+ #menuToggle .menu-item-has-children > a {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: space-between !important;
+ gap: 8px !important;
+ width: 100% !important;
+ font-size: 16px !important;
+ font-weight: 600 !important;
+ color: #1C4DA1 !important;
+ padding: 12px 0 !important;
+ text-decoration: none !important;
+ }
+
+ #menuToggle .menu-item-has-children > a::after {
+ display: none !important;
+ }
+
+ #menuToggle .menu-item-has-children > a .arrow-icon {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+ margin-left: auto;
+ padding: 12px;
+ margin-right: -12px;
+ box-sizing: content-box;
+ cursor: pointer;
+ transition: none;
+ }
+
+ #menuToggle .menu-item-has-children.active > a .arrow-icon {
+ transform: scaleY(-1);
+ }
+
+ #menuToggle #menu-item-5845,
+ #menuToggle #menu-item-5847,
+ #menuToggle #menu-item-5848,
+ #menuToggle li#menu-item-5842,
+ #menuToggle li.box-featured,
+ #menuToggle li.rt-custom-menu-field-item,
+ #menuToggle .rt-wp-menu-custom-fields-wrapper {
+ display: none !important;
+ pointer-events: none !important;
+ height: 0 !important;
+ max-height: 0 !important;
+ overflow: hidden !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ visibility: hidden !important;
+ }
+
+ /* Faster than :has() — class toggled in JS */
+ #menuToggle.menu-section-open .menu-wrapper > .menu-item-has-children:not(.active),
+ #menuToggle.menu-section-open .menu-wrapper > .menu-item:not(.menu-item-has-children) {
+ display: none !important;
+ }
+
+ #menuToggle .menu-item-has-children:not(.active) > .sub-menu {
+ display: none !important;
+ pointer-events: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+ }
+
+ #menuToggle .menu-item-has-children.active > .sub-menu {
+ display: block !important;
+ pointer-events: auto !important;
+ padding: 0 0 24px !important;
+ }
+
+ #menuToggle .menu-item-has-children.active > .sub-menu > li {
+ display: list-item !important;
+ pointer-events: auto !important;
+ margin-top: 16px !important;
+ }
+
+ #menuToggle .menu-item-has-children.active > .sub-menu > li > a {
+ display: block !important;
+ font-size: 16px !important;
+ font-weight: 600 !important;
+ color: #1C4DA1 !important;
+ padding: 0 !important;
+ }
+
+ #menuToggle .mega-menu-nav.active > .sub-menu > li.col-menu {
+ display: list-item !important;
+ margin-top: 20px !important;
+ }
+
+ #menuToggle .mega-menu-nav.active > .sub-menu > li.col-menu > a {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: space-between !important;
+ font-size: 16px !important;
+ font-weight: 600 !important;
+ }
+
+ #menuToggle .mega-menu-nav .col-menu:not(.active) > .sub-menu {
+ display: none !important;
+ pointer-events: none !important;
+ }
+
+ #menuToggle .mega-menu-nav .col-menu.active > .sub-menu {
+ display: block !important;
+ pointer-events: auto !important;
+ padding-left: 0 !important;
+ margin-top: 8px !important;
+ }
+
+ #menuToggle .mega-menu-nav .col-menu.active > .sub-menu > li {
+ display: list-item !important;
+ margin-top: 12px !important;
+ }
+
+ #menuToggle .mega-menu-nav .col-menu.active > .sub-menu a {
+ display: block !important;
+ font-weight: 600 !important;
+ color: #1C4DA1 !important;
+ }
+}
+"""
+
+if marker in text:
+ text = re.sub(r"/\* Mobile nav clickability fix \*/[\s\S]*?(?=\n/\* |\Z)", block.strip() + "\n", text, count=1)
+else:
+ text = text.rstrip() + "\n\n" + block.strip() + "\n"
+
+css.write_text(text)
+print("Patched new.css for snappy toggles")
+PY
+
+for header in header.php header-v2.php; do
+ [[ -f "$header" ]] || continue
+ sed -i "s|/new.css?v=[0-9]*|/new.css?v=$CACHE_VER|g; s|/new.css' ?>|/new.css?v=$CACHE_VER' ?>|g" "$header"
+done
+
+bash "$SCRIPT_DIR/fix-blog-footer-script-clean.sh"
diff --git a/scripts/fix-blog-mobile-menu-v2.sh b/scripts/fix-blog-mobile-menu-v2.sh
new file mode 100755
index 000000000..85ef06057
--- /dev/null
+++ b/scripts/fix-blog-mobile-menu-v2.sh
@@ -0,0 +1,131 @@
+#!/usr/bin/env bash
+# Fix blog mobile menu v2: nested mega-menu pointer-events, hide promo, cache-bust new.css.
+set -euo pipefail
+
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+THEME="${THEME:-$WP_ROOT/wp-content/themes/eucodewe-1389}"
+CACHE_VER="${CACHE_VER:-$(date +%Y%m%d%H%M)}"
+
+[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found"; exit 1; }
+[[ -f "$THEME/new.css" ]] || { echo "Theme new.css not found"; exit 1; }
+command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; }
+
+cd "$THEME"
+chmod u+w new.css header.php header-v2.php 2>/dev/null || true
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+css = Path("new.css")
+text = css.read_text()
+marker = "/* Mobile nav clickability fix */"
+block = """/* Mobile nav clickability fix */
+@media (max-width: 1080px) {
+ .header__menu.menu-header {
+ display: none !important;
+ pointer-events: none !important;
+ }
+
+ /* Always hide desktop promo card in mobile drawer */
+ #menuToggle li#menu-item-5842,
+ #menuToggle li.box-featured,
+ #menuToggle li.rt-custom-menu-field-item,
+ .mobile-nav li#menu-item-5842,
+ .mobile-nav li.box-featured,
+ .mobile-nav li.rt-custom-menu-field-item,
+ .mobile-nav .rt-wp-menu-custom-fields-wrapper {
+ display: none !important;
+ pointer-events: none !important;
+ height: 0 !important;
+ max-height: 0 !important;
+ overflow: hidden !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ min-height: 0 !important;
+ opacity: 0 !important;
+ visibility: hidden !important;
+ }
+
+ /* Collapse all submenu levels when parent is closed (mega-menu nests ul.sub-menu) */
+ #menuToggle .menu-item-has-children:not(.active) > .sub-menu,
+ #menuToggle .menu-item-has-children:not(.active) .sub-menu .sub-menu {
+ max-height: 0 !important;
+ overflow: hidden !important;
+ visibility: hidden !important;
+ pointer-events: none !important;
+ padding-top: 0 !important;
+ margin: 0 !important;
+ }
+
+ #menuToggle .menu-item-has-children:not(.active) .sub-menu *,
+ #menuToggle .menu-item-has-children:not(.active) .sub-menu .sub-menu * {
+ pointer-events: none !important;
+ }
+
+ #menuToggle .menu-item-has-children.active > .sub-menu {
+ max-height: none !important;
+ overflow: visible !important;
+ visibility: visible !important;
+ pointer-events: auto !important;
+ }
+
+ #menuToggle .menu-item-has-children.active .sub-menu a,
+ #menuToggle .menu-item-has-children.active .sub-menu li {
+ pointer-events: auto !important;
+ visibility: visible !important;
+ }
+
+ #menuToggle nav.menu-menu-1-container a {
+ display: block;
+ position: relative;
+ z-index: 5;
+ pointer-events: auto;
+ }
+}
+"""
+
+if marker in text:
+ text = re.sub(r"/\* Mobile nav clickability fix \*/[\s\S]*?(?=\n/\* |\Z)", block.strip() + "\n", text, count=1)
+else:
+ text = text.rstrip() + "\n\n" + block.strip() + "\n"
+
+css.write_text(text)
+print("Patched new.css")
+PY
+
+for header in header.php header-v2.php; do
+ if [[ -f "$header" ]]; then
+ python3 - "$header" "$CACHE_VER" <<'PY'
+import re, sys
+path, ver = sys.argv[1], sys.argv[2]
+text = open(path).read()
+new = re.sub(
+ r"(get_stylesheet_directory_uri\(\)\s*\.\s*'/new\.css')(\s*\?v=[^']*)?'",
+ rf"\1'?v={ver}'",
+ text,
+ count=1,
+)
+if new == text:
+ new = text.replace(
+ "get_stylesheet_directory_uri(). '/new.css' ?>",
+ f"get_stylesheet_directory_uri(). '/new.css?v={ver}' ?>",
+ )
+open(path, "w").write(new)
+print(f"Cache-busted {path} -> ?v={ver}")
+PY
+ fi
+done
+
+cd "$WP_ROOT"
+wp cache flush --allow-root 2>/dev/null | tail -1 || true
+
+for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm; do
+ if systemctl is-active --quiet "$svc" 2>/dev/null; then
+ systemctl reload "$svc"
+ echo "Reloaded $svc"
+ break
+ fi
+done
+
+echo "Done. new.css cache version: $CACHE_VER"
diff --git a/scripts/patch-blog-menu-share-stories-icon.sh b/scripts/patch-blog-menu-share-stories-icon.sh
index 70d1d75df..b43670f5b 100755
--- a/scripts/patch-blog-menu-share-stories-icon.sh
+++ b/scripts/patch-blog-menu-share-stories-icon.sh
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
-# Fix "Share your stories" Blog submenu: arrow icon + Office form URL.
+# Fix "Share your stories" Blog submenu: Office form URL (no arrow icon).
# Run on server: WP_ROOT=/var/www/html/blog bash patch-blog-menu-share-stories-icon.sh
set -euo pipefail
@@ -7,7 +7,6 @@ set -euo pipefail
WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
SHARE_TEXT="Share your stories"
SHARE_URL="https://forms.office.com/Pages/ResponsePage.aspx?id=18F13DIal06vkB3AGRHqbCnyIKB_vXdLsUgagfjd7DRUN1dZTVYxSkJNQ1VWSlVZNlpBOFAyN0g4UC4u&embed=true"
-ARROW_HTML='
'
[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found: $WP_ROOT"; exit 1; }
command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; }
@@ -28,27 +27,12 @@ fi
echo "Patching menu item ID $SHARE_ID"
-# Copy arrow icon meta from an existing submenu item that already shows the arrow
-REF_ID=$(wp menu item list 2 --fields=db_id,title --format=csv --allow-root 2>/dev/null \
- | awk -F, '$2 ~ /^Activities$/ {print $1; exit}') || true
-REF_ID="${REF_ID:-5845}"
-
wp eval --allow-root "
-\$ref = (int) ${REF_ID};
\$target = (int) ${SHARE_ID};
-\$meta = get_post_meta(\$ref, 'rt-wp-menu-custom-fields', true);
-if (empty(\$meta) || ! is_array(\$meta)) {
- \$meta = [
- 'selected-feature' => 'html',
- 'html' => [
- 'custom-html' => '${ARROW_HTML}',
- ],
- ];
-}
update_post_meta(\$target, '_menu_item_url', '${SHARE_URL}');
-update_post_meta(\$target, 'rt-wp-menu-custom-fields', \$meta);
+delete_post_meta(\$target, 'rt-wp-menu-custom-fields');
delete_transient('rt-wp-menu-custom-fields-' . \$target);
-echo \"Menu item \$target: URL + arrow icon meta applied (copied from \$ref)\n\";
+echo \"Menu item \$target: URL updated, arrow custom HTML removed\n\";
"
wp cache flush --allow-root 2>/dev/null || true
diff --git a/scripts/patch-blog-mobile-menu-parent-links.sh b/scripts/patch-blog-mobile-menu-parent-links.sh
new file mode 100755
index 000000000..2804dce20
--- /dev/null
+++ b/scripts/patch-blog-mobile-menu-parent-links.sh
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+# Match main codeweek.eu mobile menu: parent labels navigate, chevron/void links expand submenu.
+# Run on blog server: bash patch-blog-mobile-menu-parent-links.sh
+
+set -euo pipefail
+
+WP_ROOT="${WP_ROOT:-/var/www/html/blog}"
+COMMUNITY_URL="${COMMUNITY_URL:-https://codeweek.eu/community}"
+RESOURCES_URL="${RESOURCES_URL:-https://codeweek.eu/educational-resources}"
+BLOG_URL="${BLOG_URL:-https://codeweek.eu/blog/}"
+
+[[ -d "$WP_ROOT" ]] || { echo "WP_ROOT not found: $WP_ROOT"; exit 1; }
+command -v wp >/dev/null 2>&1 || { echo "wp-cli required"; exit 1; }
+
+cd "$WP_ROOT"
+
+PATCH_PHP="$(mktemp)"
+cat > "$PATCH_PHP" <<'PHP'
+ a').forEach(function(menuItem) {
+ menuItem.addEventListener('click', function(event) {
+ var href = menuItem.getAttribute('href') || '';
+ var isVoidHref = !href || href === '#' || href.indexOf('javascript:') === 0;
+ var rect = menuItem.getBoundingClientRect();
+ var clickedChevron = event.clientX >= rect.right - 36;
+
+ if (!isVoidHref && !clickedChevron) {
+ return;
+ }
+
+ event.preventDefault();
+ var parentItem = menuItem.parentElement;
+ parentItem.classList.toggle('active');
+
+ document.querySelectorAll('nav.menu-menu-1-container .menu-item-has-children').forEach(function(otherItem) {
+ if (otherItem !== parentItem) {
+ otherItem.classList.remove('active');
+ }
+ });
+ });
+ });
+JS;
+
+$pattern = '/document\.querySelectorAll\(\x27nav\.menu-menu-1-container \.menu-item-has-children > a\x27\)[\s\S]*?(?:\}\);\s*){1,3}/';
+
+$updated = false;
+foreach ($settings as $key => $value) {
+ if (! is_string($value) || strpos($value, 'menu-item-has-children') === false) {
+ continue;
+ }
+
+ $value = preg_replace($pattern, '', $value);
+ $value = preg_replace('/\s*\}\);\s*\}\);\s*(<\/script>)/', "\n$1", $value);
+
+ $insertBefore = '';
+ $pos = strrpos($value, $insertBefore);
+ if ($pos === false) {
+ continue;
+ }
+
+ $settings[$key] = substr($value, 0, $pos) . "\n" . trim($newHandler) . "\n" . substr($value, $pos);
+ $updated = true;
+ echo "Patched mobile menu script in auhfc_settings_sitewide[{$key}]\n";
+ break;
+}
+
+if (! $updated) {
+ fwrite(STDERR, "ERROR: could not find mobile menu script in auhfc_settings_sitewide\n");
+ exit(1);
+}
+
+update_option('auhfc_settings_sitewide', $settings);
+
+$menuPatches = [
+ 5637 => getenv('COMMUNITY_URL') ?: 'https://codeweek.eu/community',
+ 29 => getenv('RESOURCES_URL') ?: 'https://codeweek.eu/educational-resources',
+ 5643 => getenv('BLOG_URL') ?: 'https://codeweek.eu/blog/',
+];
+
+foreach ($menuPatches as $id => $url) {
+ update_post_meta((int) $id, '_menu_item_url', $url);
+ echo "Menu item {$id} -> {$url}\n";
+}
+PHP
+
+COMMUNITY_URL="$COMMUNITY_URL" RESOURCES_URL="$RESOURCES_URL" BLOG_URL="$BLOG_URL" \
+ wp eval-file "$PATCH_PHP" --allow-root
+rm -f "$PATCH_PHP"
+
+wp cache flush --allow-root 2>/dev/null || true
+for svc in php8.3-fpm php8.2-fpm php8.1-fpm php8.0-fpm; do
+ if systemctl is-active --quiet "$svc" 2>/dev/null; then
+ systemctl reload "$svc"
+ echo "Reloaded $svc"
+ break
+ fi
+done
+
+echo "Done. Hard-refresh https://codeweek.eu/blog/ on mobile."