PageSourceSearch

https://www.choosetosucceed.org/consider-bexar-county/

html choosetosucceed.org collected 2026-09-26 08:40:26 UTC 317,605 bytes, 5,181 lines download raw bytes

1<!DOCTYPE html><html lang="en-US"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><style id="wpconvert-shadcn-vars">:root {
2  --background: 0 0% 100%;
3  --foreground: 222.2 84% 4.9%;
4  --card: 0 0% 100%;
5  --card-foreground: 222.2 84% 4.9%;
6  --primary: 222.2 47.4% 11.2%;
7  --primary-foreground: 210 40% 98%;
8  --muted: 210 40% 96.1%;
9  --muted-foreground: 215.4 16.3% 46.9%;
10  --accent: 210 40% 96.1%;
11  --accent-foreground: 222.2 47.4% 11.2%;
12  --input: 214.3 31.8% 91.4%;
13  --border: 214.3 31.8% 91.4%;
14  --ring: 222.2 84% 4.9%;
15}
16.dark, [class*="bg-slate-950"], [class*="bg-gray-950"], [class*="bg-zinc-950"], [class*="bg-neutral-950"] {
17  --background: 222.2 84% 4.9%;
18  --foreground: 210 40% 98%;
19  --card: 217.2 32.6% 17.5%;
20  --card-foreground: 210 40% 98%;
21  --primary: 210 40% 98%;
22  --primary-foreground: 222.2 47.4% 11.2%;
23  /* EC-NAV-139: Light ink over dark heroes; unlike --primary-foreground in .dark (dark ink for buttons). */
24  --wpconvert-transparent-nav-ink: 210 40% 98%;
25  --muted: 217.2 32.6% 17.5%;
26  --muted-foreground: 215 20.2% 65.1%;
27  --accent: 217.2 32.6% 17.5%;
28  --accent-foreground: 210 40% 98%;
29  --input: 217.2 32.6% 17.5%;
30  --border: 217.2 32.6% 17.5%;
31  --ring: 212.7 26.8% 83.9%;
32}
33/* EC-FAQ-DARK-001: FAQ answers keep light-theme text-slate-* utilities on dark pages (html.dark). */
34html.dark [data-wp-faq-answer="true"] p.text-slate-300,
35html.dark [data-wp-faq-answer="true"] p.text-slate-400,
36html.dark [data-wp-faq-answer="true"] p.text-slate-500,
37html.dark [data-wp-faq-answer="true"] .text-slate-400,
38html.dark [data-wp-faq-answer="true"] .text-slate-500 {
39  color: rgba(255, 255, 255, 0.74) !important;
40}</style><meta name='robots' content='index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1' /><link media="all" href="https://www.choosetosucceed.org/wp-content/cache/autoptimize/css/autoptimize_16c046c69a3f47d4e3884663096543e9.css" rel="stylesheet"><title>Consider Bexar County - Choose to Succeed</title><link rel="canonical" href="https://www.choosetosucceed.org/consider-bexar-county/" /><meta property="og:locale" content="en_US" /><meta property="og:type" content="article" /><meta property="og:title" content="Consider Bexar County - Choose to Succeed" /><meta property="og:description" content="This page is automatically generated by WPConvert. The content is displayed using the page template." /><meta property="og:url" content="https://www.choosetosucceed.org/consider-bexar-county/" /><meta property="og:site_name" content="Choose to Succeed" /><meta property="article:modified_time" content="2026-06-04T21:40:46+00:00" /><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:label1" content="Est. reading time" /><meta name="twitter:data1" content="1 minute" /> 
40<script type="application/ld+json" class="yoast-schema-graph">{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.choosetosucceed.org\/consider-bexar-county\/","url":"https:\/\/www.choosetosucceed.org\/consider-bexar-county\/","name":"Consider Bexar County - Choose to Succeed","isPartOf":{"@id":"https:\/\/www.choosetosucceed.org\/#website"},"datePublished":"2026-06-04T21:27:17+00:00","dateModified":"2026-06-04T21:40:46+00:00","breadcrumb":{"@id":"https:\/\/www.choosetosucceed.org\/consider-bexar-county\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.choosetosucceed.org\/consider-bexar-county\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.choosetosucceed.org\/consider-bexar-county\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.choosetosucceed.org\/"},{"@type":"ListItem","position":2,"name":"Consider Bexar County"}]},{"@type":"WebSite","@id":"https:\/\/www.choosetosucceed.org\/#website","url":"https:\/\/www.choosetosucceed.org\/","name":"Choose to Succeed","description":"Bringing World-class, tuition-free public charter schools to San Antonio","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.choosetosucceed.org\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}</script>
40 <link rel="alternate" title="oEmbed (JSON)" type="application/json+oembed" href="https://www.choosetosucceed.org/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fwww.choosetosucceed.org%2Fconsider-bexar-county%2F" /><link rel="alternate" title="oEmbed (XML)" type="text/xml+oembed" href="https://www.choosetosucceed.org/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fwww.choosetosucceed.org%2Fconsider-bexar-county%2F&#038;format=xml" /> <style id="wp-img-auto-sizes-contain-inline-css">img:is([sizes=auto i],[sizes^="auto," i]){contain-intrinsic-size:3000px 1500px}
41/*# sourceURL=wp-img-auto-sizes-contain-inline-css */</style><style id="wp-emoji-styles-inline-css">img.wp-smiley, img.emoji {
42		display: inline !important;
43		border: none !important;
44		box-shadow: none !important;
45		height: 1em !important;
46		width: 1em !important;
47		margin: 0 0.07em !important;
48		vertical-align: -0.1em !important;
49		background: none !important;
50		padding: 0 !important;
51	}
52/*# sourceURL=wp-emoji-styles-inline-css */</style><style id="wp-block-library-inline-css">:root{--wp-block-synced-color:#7a00df;--wp-block-synced-color--rgb:122,0,223;--wp-bound-block-color:var(--wp-block-synced-color);--wp-editor-canvas-background:#ddd;--wp-admin-theme-color:#007cba;--wp-admin-theme-color--rgb:0,124,186;--wp-admin-theme-color-darker-10:#006ba1;--wp-admin-theme-color-darker-10--rgb:0,107,160.5;--wp-admin-theme-color-darker-20:#005a87;--wp-admin-theme-color-darker-20--rgb:0,90,135;--wp-admin-border-width-focus:2px}@media (min-resolution:192dpi){:root{--wp-admin-border-width-focus:1.5px}}.wp-element-button{cursor:pointer}:root .has-very-light-gray-background-color{background-color:#eee}:root .has-very-dark-gray-background-color{background-color:#313131}:root .has-very-light-gray-color{color:#eee}:root .has-very-dark-gray-color{color:#313131}:root .has-vivid-green-cyan-to-vivid-cyan-blue-gradient-background{background:linear-gradient(135deg,#00d084,#0693e3)}:root .has-purple-crush-gradient-background{background:linear-gradient(135deg,#34e2e4,#4721fb 50%,#ab1dfe)}:root .has-hazy-dawn-gradient-background{background:linear-gradient(135deg,#faaca8,#dad0ec)}:root .has-subdued-olive-gradient-background{background:linear-gradient(135deg,#fafae1,#67a671)}:root .has-atomic-cream-gradient-background{background:linear-gradient(135deg,#fdd79a,#004a59)}:root .has-nightshade-gradient-background{background:linear-gradient(135deg,#330968,#31cdcf)}:root .has-midnight-gradient-background{background:linear-gradient(135deg,#020381,#2874fc)}:root{--wp--preset--font-size--normal:16px;
52--wp--preset--font-size--huge:42px}.has-regular-font-size{font-size:1em}.has-larger-font-size{font-size:2.625em}.has-normal-font-size{font-size:var(--wp--preset--font-size--normal)}.has-huge-font-size{font-size:var(--wp--preset--font-size--huge)}:root .has-text-align-center{text-align:center}:root .has-text-align-left{text-align:left}:root .has-text-align-right{text-align:right}.has-fit-text{white-space:nowrap!important}#end-resizable-editor-section{display:none}.aligncenter{clear:both}.items-justified-left{justify-content:flex-start}.items-justified-center{justify-content:center}.items-justified-right{justify-content:flex-end}.items-justified-space-between{justify-content:space-between}.screen-reader-text{word-wrap:normal!important;border:0;clip-path:inset(50%);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.screen-reader-text:focus{background-color:#ddd;clip-path:none;color:#444;display:block;font-size:1em;height:auto;left:5px;line-height:normal;padding:15px 23px 14px;text-decoration:none;top:5px;width:auto;z-index:100000}html :where(.has-border-color){border-style:solid}html :where([style*=border-color]){border-style:solid}html :where([style*=border-top-color]){border-top-style:solid}html :where([style*=border-right-color]){border-right-style:solid}html :where([style*=border-bottom-color]){border-bottom-style:solid}html :where([style*=border-left-color]){border-left-style:solid}html :where([style*=border-width]){border-style:solid}html :where([style*=border-top-width]){border-top-style:solid}html :where([style*=border-right-width]){border-right-style:solid}html :where([style*=border-bottom-width]){border-bottom-style:solid}html :where([style*=border-left-width]){border-left-style:solid}html :where(img[class*=wp-image-]){height:auto;max-width:100%}:where(figure){margin:0 0 1em}html :where(.is-position-sticky){--wp-admin--admin-bar--position-offset:var(--wp-admin--admin-bar--height,0px)}@media screen and (max-width:600px){html :where(.is-position-sticky){--wp-admin--admin-bar--position-offset:0px}}
53
54/*# sourceURL=/wp-includes/css/dist/block-library/common.min.css */</style><style id="global-styles-inline-css">:root{--wp--preset--aspect-ratio--square: 1;--wp--preset--aspect-ratio--4-3: 4/3;--wp--preset--aspect-ratio--3-4: 3/4;--wp--preset--aspect-ratio--3-2: 3/2;--wp--preset--aspect-ratio--2-3: 2/3;--wp--preset--aspect-ratio--16-9: 16/9;--wp--preset--aspect-ratio--9-16: 9/16;--wp--preset--color--black: #000000;--wp--preset--color--cyan-bluish-gray: #abb8c3;--wp--preset--color--white: #ffffff;--wp--preset--color--pale-pink: #f78da7;--wp--preset--color--vivid-red: #cf2e2e;--wp--preset--color--luminous-vivid-orange: #ff6900;--wp--preset--color--luminous-vivid-amber: #fcb900;--wp--preset--color--light-green-cyan: #7bdcb5;--wp--preset--color--vivid-green-cyan: #00d084;--wp--preset--color--pale-cyan-blue: #8ed1fc;--wp--preset--color--vivid-cyan-blue: #0693e3;--wp--preset--color--vivid-purple: #9b51e0;--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: linear-gradient(135deg,rgb(6,147,227) 0%,rgb(155,81,224) 100%);--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%);--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange: linear-gradient(135deg,rgb(252,185,0) 0%,rgb(255,105,0) 100%);--wp--preset--gradient--luminous-vivid-orange-to-vivid-red: linear-gradient(135deg,rgb(255,105,0) 0%,rgb(207,46,46) 100%);--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%);
54--wp--preset--gradient--cool-to-warm-spectrum: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%);--wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%);--wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%);--wp--preset--gradient--luminous-dusk: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%);--wp--preset--gradient--pale-ocean: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%);--wp--preset--gradient--electric-grass: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%);--wp--preset--gradient--midnight: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%);--wp--preset--font-size--small: 13px;--wp--preset--font-size--medium: 20px;--wp--preset--font-size--large: 36px;--wp--preset--font-size--x-large: 42px;--wp--preset--spacing--20: 0.44rem;--wp--preset--spacing--30: 0.67rem;--wp--preset--spacing--40: 1rem;--wp--preset--spacing--50: 1.5rem;--wp--preset--spacing--60: 2.25rem;--wp--preset--spacing--70: 3.38rem;--wp--preset--spacing--80: 5.06rem;--wp--preset--shadow--natural: 6px 6px 9px rgba(0, 0, 0, 0.2);--wp--preset--shadow--deep: 12px 12px 50px rgba(0, 0, 0, 0.4);--wp--preset--shadow--sharp: 6px 6px 0px rgba(0, 0, 0, 0.2);--wp--preset--shadow--outlined: 6px 6px 0px -3px rgb(255, 255, 255), 6px 6px rgb(0, 0, 0);--wp--preset--shadow--crisp: 6px 6px 0px rgb(0, 0, 0);}:where(body) { margin: 0; }.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}.is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}.is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}.is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){margin-left: auto !important;margin-right: auto !important;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}body{padding-top: 0px;padding-right: 0px;padding-bottom: 0px;padding-left: 0px;}:root :where(.wp-element-button, .wp-block-button__link){background-color: #32373c;border-width: 0;color: #fff;font-family: inherit;font-size: inherit;font-style: inherit;font-weight: inherit;letter-spacing: inherit;line-height: inherit;padding-top: calc(0.667em + 2px);padding-right: calc(1.333em + 2px);padding-bottom: calc(0.667em + 2px);padding-left: calc(1.333em + 2px);text-decoration: none;text-transform: inherit;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-color{color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-color{color: var(--wp--preset--color--white) !important;}.has-pale-pink-color{color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-color{color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-color{color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-color{color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-color{color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-color{color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-color{color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-color{color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-color{color: var(--wp--preset--color--vivid-purple) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-background-color{background-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-pale-pink-background-color{background-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-background-color{background-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-background-color{background-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-background-color{background-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-background-color{background-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-background-color{background-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-background-color{background-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-background-color{background-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-background-color{background-color: var(--wp--preset--color--vivid-purple) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-border-color{border-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-pale-pink-border-color{border-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-border-color{border-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-border-color{border-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-border-color{border-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-border-color{border-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-border-color{border-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-border-color{border-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-border-color{border-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-border-color{border-color: var(--wp--preset--color--vivid-purple) !important;}.has-vivid-cyan-blue-to-vivid-purple-gradient-background{background: var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple) !important;}.has-light-green-cyan-to-vivid-green-cyan-gradient-background{background: var(--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan) !important;}.has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange) !important;}.has-luminous-vivid-orange-to-vivid-red-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-orange-to-vivid-red) !important;}
54.has-very-light-gray-to-cyan-bluish-gray-gradient-background{background: var(--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray) !important;}.has-cool-to-warm-spectrum-gradient-background{background: var(--wp--preset--gradient--cool-to-warm-spectrum) !important;}.has-blush-light-purple-gradient-background{background: var(--wp--preset--gradient--blush-light-purple) !important;}.has-blush-bordeaux-gradient-background{background: var(--wp--preset--gradient--blush-bordeaux) !important;}.has-luminous-dusk-gradient-background{background: var(--wp--preset--gradient--luminous-dusk) !important;}.has-pale-ocean-gradient-background{background: var(--wp--preset--gradient--pale-ocean) !important;}.has-electric-grass-gradient-background{background: var(--wp--preset--gradient--electric-grass) !important;}.has-midnight-gradient-background{background: var(--wp--preset--gradient--midnight) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-medium-font-size{font-size: var(--wp--preset--font-size--medium) !important;}.has-large-font-size{font-size: var(--wp--preset--font-size--large) !important;}.has-x-large-font-size{font-size: var(--wp--preset--font-size--x-large) !important;}
55/*# sourceURL=global-styles-inline-css */</style><style id="wpconvert-ecnav088-inline-css">/* EC-ANIM-008c/009: carousel-only scroll-reveal override */.scroll-fade-up:has([aria-roledescription="carousel"]),.scroll-fade-up:has([data-wpconvert-blog-filter-bar]),.scroll-fade-left:has([aria-roledescription="carousel"]),.scroll-fade-right:has([aria-roledescription="carousel"]),.scroll-scale-in:has([aria-roledescription="carousel"]){opacity:1!important;transform:none!important}/* EC-ACCORDION-STATE: Sync panel visibility with trigger state — Puppeteer snapshots *//* can capture panels as data-state=open while their trigger is data-state=closed */h3[data-state="closed"]+[role="region"],h3[data-state="closed"]+[data-state][role="region"]{display:none!important;max-height:0!important;overflow:hidden!important;animation:none!important}
56/*# sourceURL=wpconvert-ecnav088-inline-css */</style> 
56<script id="jquery-core-js" src="https://www.choosetosucceed.org/wp-includes/js/jquery/jquery.min.js?ver=3.7.1"></script>
56 
56<script id="jquery-js-before">// Prevent React createRoot/hydrateRoot from executing if root element doesn't exist
57        (function() {
58            if (typeof window !== "undefined") {
59                // Intercept createRoot calls before React loads
60                var originalCreateRoot = null;
61                var originalHydrateRoot = null;
62                
63                // Override when React loads
64                Object.defineProperty(window, 'React', {
65                    set: function(value) {
66                        if (value && value.createRoot) {
67                            originalCreateRoot = value.createRoot;
68                            value.createRoot = function(container) {
69                                if (!container || !document.contains(container)) {
70                                    console.warn("[WPConvert] React createRoot prevented - container not found");
71                                    return { render: function() {}, unmount: function() {} };
72                                }
73                                return originalCreateRoot.call(this, container);
74                            };
75                        }
76                        this._react = value;
77                    },
78                    get: function() {
79                        return this._react;
80                    }
81                });
82                
83                Object.defineProperty(window, 'ReactDOM', {
84                    set: function(value) {
85                        if (value) {
86                            if (value.createRoot) {
87                                originalCreateRoot = value.createRoot;
88                                value.createRoot = function(container) {
89                                    if (!container || !document.contains(container)) {
90                                        console.warn("[WPConvert] ReactDOM createRoot prevented");
91                                        return { render: function() {}, unmount: function() {} };
92                                    }
93                                    return originalCreateRoot.call(this, container);
94                                };
95                            }
96                            if (value.hydrateRoot) {
97                                originalHydrateRoot = value.hydrateRoot;
98                                value.hydrateRoot = function(container) {
99                                    if (!container || !document.contains(container)) {
100                                        console.warn("[WPConvert] ReactDOM hydrateRoot prevented");
101                                        return { render: function() {}, unmount: function() {} };
102                                    }
103                                    return originalHydrateRoot.call(this, container);
104                                };
105                            }
106                        }
107                        this._reactDOM = value;
108                    },
109                    get: function() {
110                        return this._reactDOM;
111                    }
112                });
113            }
114        })();
115        (function() {
116            // Run as early as possible, before DOMContentLoaded
117            function revealHiddenElements() {
118                console.log('[WPConvert] Running animation fallback to reveal hidden sections...');
119                
120                var revealed = 0;
121                var elements = document.querySelectorAll('*');
122                
123                elements.forEach(function(el) {
124                    // Skip elements that should stay hidden
125                    var tagName = el.tagName.toLowerCase();
126                    if (tagName === 'script' || tagName === 'style' || tagName === 'noscript') {
127                        return;
128                    }
129                    
130                    // Skip WordPress admin bar and menus
131                    if (el.id === 'wpadminbar' || 
132                        el.classList.contains('sub-menu') || 
133                        el.classList.contains('submenu') ||
134                        el.hasAttribute('data-wpconvert-popup-disabled')) {
135                        return;
136                    }
137                    
138                    // EC-GALLERY-FLASH-001: Skip elements inside stacked gallery containers.
139                    // These images intentionally use opacity:0 for crossfade transitions.
140                    // Revealing them causes a flash before the carousel handler resets them.
141                    if (el.closest('[data-wpconvert-stacked-gallery]') ||
142                        el.closest('[data-wpc-carousel]')) {
143                        return;
144                    }
145                    
146                    var style = el.style;
147                    var computedStyle = window.getComputedStyle(el);
148                    var hasHidingStyle = false;
149                    
150                    // Check for hiding inline styles (Framer Motion patterns)
151                    if (style.opacity === '0' || style.opacity === '0.0') {
152                        // Only reveal if element has content/dimensions
153                        if (el.offsetHeight > 0 || el.offsetWidth > 0 || el.textContent.trim().length > 0) {
154                            style.opacity = '1';
155                            hasHidingStyle = true;
156                        }
157                    }
158                    
159                    // Check for visibility hidden
160                    if (style.visibility === 'hidden' && computedStyle.display !== 'none') {
161                        style.visibility = 'visible';
162                        hasHidingStyle = true;
163                    }
164                    
165                    // Check for transform that moves element off-screen
166                    if (style.transform && (
167                        style.transform.includes('translateY') || 
168                        style.transform.includes('translateX') ||
169                        style.transform.includes('scale(0)')
170                    )) {
171                        // EC-CAROUSEL-009: Embla/shadcn carousels set translateX on the flex track via WPConvert JS.
172                        // Stripping it (especially on the delayed run) breaks the culture image slider.
173                        if (el.closest('[aria-roledescription="carousel"]')) {
174                            /* skip */
175                        } else {
176                        // Reset problematic transforms (preserve intentional ones like rotate)
177                        var transform = style.transform;
178                        transform = transform.replace(/translateY\([^)]+\)/g, 'translateY(0)');
179                        transform = transform.replace(/translateX\([^)]+\)/g, 'translateX(0)');
180                        transform = transform.replace(/scale\(0(?:\.\d+)?\)/g, 'scale(1)');
181                        
182                        if (transform !== style.transform) {
183                            style.transform = transform;
184                            hasHidingStyle = true;
185                        }
186                        }
187                    }
188                    
189                    if (hasHidingStyle) {
190                        revealed++;
191                    }
192                });
193                
194                if (revealed > 0) {
195                    console.log('[WPConvert] ✅ Revealed ' + revealed + ' hidden elements (Framer Motion fallback)');
196                }
197            }
198            
199            // Run immediately (for inline styles in initial HTML)
200            if (document.readyState === 'loading') {
201                document.addEventListener('DOMContentLoaded', revealHiddenElements);
202            } else {
203                revealHiddenElements();
204            }
205            
206            // Also run after a short delay to catch late-loading elements
207            setTimeout(revealHiddenElements, 100);
208        })();
209//# sourceURL=jquery-js-before</script>
209 
209<script id="jquery-js-after">jQuery(document).ready(function($) {
210            // EC-TESTIMONIAL-004b: Shared by universal carousel + EC-CAROUSEL-002 — skip only inner JSON testimonial card
211            function wpcInsideJsonTestimonialCard($el) {
212                var $tsec = $el.closest("[data-wpconvert-testimonials]");
213                if (!$tsec.length) return false;
214                var $ariaPrev = $tsec.find('button[aria-label*="Prev"], button[aria-label*="Previous"], button[aria-label*="previous"]').first();
215                if (!$ariaPrev.length) return true;
216                var $innerCard = $ariaPrev.closest('[class*="rounded-3xl"],[class*="rounded-2xl"],[class*="rounded-xl"]').first();
217                if (!$innerCard.length) return true;
218                return $el.closest($innerCard).length > 0;
219            }
220            try { // [WPConvert: Smooth Scrolling]
221            // Smooth scrolling for anchor links
222            // EC-EDITOR-005: Skip when WPConvert editor is active to avoid scroll conflict
223            $('a[href^="#"]').on("click", function(e) {
224                if (window.location.search.indexOf("wpc_edit=1") !== -1) return;
225                e.preventDefault();
226                var target = $(this).attr("href");
227                if (target === "#") return;
228                
229                var $target = $(target);
230                if ($target.length) {
231                    $("html, body").animate({
232                        scrollTop: $target.offset().top - 80
233                    }, 800);
234                }
235            });
236            } catch(e) { console.warn("[WPConvert] Smooth scrolling error:", e); }
237            
238            try { // [WPConvert: Accordion/Collapsible]
239            // Universal accordion/collapsible functionality
240            // Works for Radix UI, Material UI, Bootstrap, Ant Design, custom accordions
241            // CRITICAL: Only target actual trigger elements (buttons, summary) - NOT wrapper divs with data-state
242            // Using [data-state] in selector caused double-firing because parent wrappers also have data-state
243            // EC-ACCORDION-011: Also target h3[data-state] > a — Radix accordion triggers
244            // that were converted from <button> to <a> during snapshot processing.
245            $(document).on("click", "button[aria-controls], button[aria-expanded], h3[data-state] > button, h3[data-state] > a, .accordion-trigger, summary", function(e) {
246                e.stopPropagation(); // Prevent event bubbling to parent wrappers
247                
248                var $trigger = $(this);
249                
250                // EC-ACCORDION-012: image-card CTA buttons (e.g. MenuCard "+ Tilføj")
251                // are explicitly guarded — never treat them as accordion triggers.
252                if ($trigger.attr("data-wpc-no-accordion-trigger") === "1") {
253                    return;
254                }
255                
256                // CRITICAL: Only handle actual trigger elements (button, summary, a inside accordion h3, or explicit accordion-trigger)
257                if (!$trigger.is("button, summary, a") && !$trigger.hasClass("accordion-trigger")) {
258                    console.log("Skipping non-trigger element:", $trigger.prop("tagName"));
259                    return;
260                }
261                
262                // EC-ACCORDION-011: Track <a> accordion triggers — defer preventDefault
263                // until we confirm a content panel exists. If no panel, let the link navigate.
264                var isAccordionLink = $trigger.is("a") && $trigger.closest("h3[data-state]").length;
265                
266                // CRITICAL: Skip menu/hamburger buttons - these are handled separately
267                var ariaLabel = ($trigger.attr("aria-label") || "").toLowerCase();
268                if (ariaLabel.indexOf("menu") !== -1 || $trigger.hasClass("menu-toggle") || $trigger.hasClass("hamburger-menu")) {
269                    console.log("Skipping accordion handler for menu button");
270                    return; // Let the hamburger menu handler deal with this
271                }
272                
273                var targetId = $trigger.attr("aria-controls");
274                var triggerId = $trigger.attr("id");
275                var $content = null;
276                
277                console.log("Accordion clicked, targetId:", targetId, "triggerId:", triggerId);
278                
279                // Method 1: aria-controls attribute points to content ID
280                if (targetId) {
281                    $content = $(document.getElementById(targetId));
282                    if ($content.length) {
283                        console.log("Found content via aria-controls");
284                    }
285                }
286                
287                // Method 2: Radix UI pattern - look for [role="region"] sibling with aria-labelledby matching trigger
288                if ((!$content || !$content.length) && triggerId) {
289                    var $radixContent = $trigger.siblings('[role="region"][aria-labelledby="' + triggerId + '"]');
290                    if ($radixContent.length) {
291                        $content = $radixContent;
292                        console.log("Found content via Radix aria-labelledby");
293                    }
294                }
295                
296                // Method 3: Look for next sibling with [role="region"] or [data-state] (Radix pattern)
297                if (!$content || !$content.length) {
298                    var $radixSibling = $trigger.next('[role="region"], [data-state]');
299                    if ($radixSibling.length) {
300                        $content = $radixSibling;
301                        console.log("Found content via Radix sibling role/data-state");
302                    }
303                }
304                
305                // Method 4: Look for next sibling element (FAQ-style accordions)
306                // Common pattern: <button aria-expanded="false">Question</button><div style="max-height:0">Answer</div>
307                if (!$content || !$content.length) {
308                    var $nextSibling = $trigger.next('div, [class*="content"], [class*="panel"], [class*="answer"]');
309                    if ($nextSibling.length) {
310                        $content = $nextSibling;
311                        console.log("Using sibling element as accordion content");
312                    }
313                }
314                
315                // Method 5: Content is inside a parent container, sibling of the trigger wrapper
316                if (!$content || !$content.length) {
317                    var $parent = $trigger.parent();
318                    var $siblingContent = $parent.next('div, [role="region"], [class*="content"], [class*="panel"]');
319                    if ($siblingContent.length) {
320                        $content = $siblingContent;
321                        console.log("Using parent sibling as accordion content");
322                    }
323                }
324                
325                // Method 6: Look within the accordion item container for content
326                if (!$content || !$content.length) {
327                    var $accordionItem = $trigger.closest('[data-state], [class*="accordion-item"], [class*="AccordionItem"]');
328                    if ($accordionItem.length) {
329                        var $itemContent = $accordionItem.find('[role="region"], [data-state="open"], [data-state="closed"], [class*="content"], [class*="panel"]').not($trigger);
330                        if ($itemContent.length) {
331                            $content = $itemContent.first();
332                            console.log("Found content within accordion item container");
333                        }
334                    }
335                }
336                
337                // Method 7: Global search for aria-labelledby matching trigger ID (Radix UI)
338                if ((!$content || !$content.length) && triggerId) {
339                    var $globalContent = $('[aria-labelledby="' + triggerId + '"]');
340                    if ($globalContent.length) {
341                        $content = $globalContent.first();
342                        console.log("Found content via global aria-labelledby search");
343                    }
344                }
345                
346                // Method 8: Last resort - find any sibling div that has content
347                if (!$content || !$content.length) {
348                    var $anySibling = $trigger.siblings('div').filter(function() {
349                        return $(this).text().trim().length > 20;
350                    });
351                    if ($anySibling.length) {
352                        $content = $anySibling.first();
353                        console.log("Found content via any sibling div with content");
354                    }
355                }
356                
357                if ($content && $content.length) {
358                    // EC-ACCORDION-011: Now that we confirmed a panel exists,
359                    // prevent <a> link navigation so the accordion can toggle
360                    if (isAccordionLink) e.preventDefault();
361                    
362                    // Also update data-state on the parent h3 trigger wrapper
363                    var $h3 = $trigger.closest("h3[data-state]");
364                    
365                    // Detect if this accordion uses CSS max-height/opacity transitions
366                    var usesMaxHeight = $content.css("max-height") !== undefined && $content.css("max-height") !== "none";
367                    var currentMaxHeight = $content.css("max-height");
368                    var currentHeight = $content.css("height");
369                    var isHidden = $content.attr("hidden") !== undefined || 
370                                   $content.css("display") === "none" ||
371                                   (usesMaxHeight && (currentMaxHeight === "0px" || currentMaxHeight === "0")) ||
372                                   (currentHeight === "0px" || currentHeight === "0");
373                    
374                    // Also check aria-expanded on trigger as source of truth
375                    if ($trigger.attr("aria-expanded") === "false") {
376                        isHidden = true;
377                    } else if ($trigger.attr("aria-expanded") === "true") {
378                        isHidden = false;
379                    }
380                    
381                    // Also check data-state attribute
382                    if ($content.attr("data-state") === "closed") {
383                        isHidden = true;
384                    } else if ($content.attr("data-state") === "open") {
385                        isHidden = false;
386                    }
387                    
388                    if (isHidden) {
389                        // Show - handle both hidden attribute and CSS transitions
390                        // CRITICAL: Remove ALL hiding first, then measure, then apply final styles
391                        $content.removeAttr("hidden");
392                        $content.removeAttr("aria-hidden");
393                        
394                        // CRITICAL: Must make element visible BEFORE measuring scrollHeight
395                        // Otherwise scrollHeight will be 0 for display:none elements
396                        $content.css({
397                            "display": "block",
398                            "visibility": "visible",
399                            "opacity": "1",
400                            "height": "auto",
401                            "max-height": "none",
402                            "overflow": "visible",
403                            "animation": "none"
404                        });
405                        
406                        // Now get scroll height (element is visible so this will work)
407                        var scrollHeight = $content.prop("scrollHeight");
408                        console.log("Accordion scrollHeight:", scrollHeight);
409                        
410                        // If scrollHeight is still 0, the content might be truly empty
411                        // In that case, just leave it visible with auto height
412                        if (scrollHeight === 0) {
413                            console.log("Warning: scrollHeight is 0, content may be empty");
414                        }
415                        
416                        $content.attr("data-state", "open");
417                        $content.removeAttr("aria-hidden");
418                        $trigger.attr("aria-expanded", "true");
419                        $trigger.attr("data-state", "open");
420                        if ($h3 && $h3.length) $h3.attr("data-state", "open");
421                        
422                        // Rotate chevron icon if present
423                        $trigger.find('svg, .chevron, [class*="chevron"]').css("transform", "rotate(180deg)");
424                        if ($h3 && $h3.length) $h3.find('svg').last().css("transform", "rotate(180deg)");
425                        
426                        console.log("Accordion expanded");
427                    } else {
428                        // Hide - handle both hidden attribute and CSS transitions
429                        $content.css({
430                            "max-height": "0px",
431                            "height": "0px",
432                            "opacity": "0",
433                            "overflow": "hidden",
434                            "display": "block",
435                            "animation": "none"
436                        });
437                        
438                        $content.attr("data-state", "closed");
439                        $trigger.attr("aria-expanded", "false");
440                        $trigger.attr("data-state", "closed");
441                        if ($h3 && $h3.length) $h3.attr("data-state", "closed");
442                        
443                        // Reset chevron icon rotation
444                        $trigger.find('svg, .chevron, [class*="chevron"]').css("transform", "rotate(0deg)");
445                        if ($h3 && $h3.length) $h3.find('svg').last().css("transform", "rotate(0deg)");
446                        
447                        console.log("Accordion collapsed");
448                    }
449                } else {
450                    console.warn("Content div not found for accordion trigger");
451                }
452            });
453            
454            // On page load, check for empty accordion content
455            $("[aria-controls]").each(function() {
456                var targetId = $(this).attr("aria-controls");
457                if (targetId) {
458                    var $content = $(document.getElementById(targetId));
459                    if ($content.length && $content.html().trim() === "") {
460                        console.warn("Empty accordion content detected for:", targetId);
461                    }
462                }
463            });
464            } catch(e) { console.warn("[WPConvert] Accordion error:", e); }
465            
466            try { // [WPConvert: CSS Max-Height Accordion] EC-FAQ-002b
467            // Handles FAQ/accordion buttons that use CSS max-h-0/max-h-96 toggling
468            // instead of aria attributes. These buttons have a sibling div with
469            // Tailwind max-height + opacity transition classes.
470            (function() {
471                function isCssAccordionBtn(btn) {
472                    var $btn = $(btn);
473                    var $sib = $btn.next("div");
474                    if (!$sib.length) return false;
475                    var cls = " " + ($sib.attr("class") || "") + " ";
476                    return (cls.indexOf("max-h-0") !== -1 || cls.indexOf("max-h-96") !== -1) && cls.indexOf("transition") !== -1;
477                }
478                
479                $(document).on("click", "button", function(e) {
480                    var btn = this;
481                    // EC-ACCORDION-012: image-card CTA buttons are guarded.
482                    if ($(btn).attr("data-wpc-no-accordion-trigger") === "1") return;
483                    if (!isCssAccordionBtn(btn)) return;
484                    
485                    // Already handled by aria accordion handler
486                    if ($(btn).attr("aria-controls") || $(btn).attr("aria-expanded")) return;
487                    
488                    e.preventDefault();
489                    e.stopPropagation();
490                    
491                    var $btn = $(btn);
492                    var $content = $btn.next("div");
493                    var cls = $content.attr("class") || "";
494                    var isOpen = cls.indexOf("max-h-96") !== -1 && cls.indexOf("max-h-0") === -1;
495                    
496                    // Close all siblings in the same container first (accordion behavior)
497                    var $container = $btn.parent().parent();
498                    $container.find("> div > button").each(function() {
499                        if (this === btn) return;
500                        var $otherContent = $(this).next("div");
501                        var otherCls = $otherContent.attr("class") || "";
502                        if (otherCls.indexOf("max-h-96") !== -1) {
503                            $otherContent.attr("class", otherCls.replace(/max-h-96/g, "max-h-0").replace(/opacity-100/g, "opacity-0"));
504                            // Reset icon rotation
505                            $(this).find(".rotate-45").removeClass("rotate-45");
506                            $(this).find(".bg-primary.text-white").removeClass("bg-primary text-white");
507                            // Reset parent border
508                            var $item = $(this).closest("div[class*='border']");
509                            if ($item.length) {
510                                var ic = $item.attr("class") || "";
511                                $item.attr("class", ic.replace(/border-primary(?!\/)(?! shadow)/g, "border-primary/5").replace(/ shadow-md/g, ""));
512                            }
513                            // Reset question text style
514                            var $qspan = $(this).find("span").first();
515                            var qc = $qspan.attr("class") || "";
516                            $qspan.attr("class", qc.replace(/text-primary font-bold/, "text-primary/70"));
517                        }
518                    });
519                    
520                    if (isOpen) {
521                        // Close this item
522                        $content.attr("class", cls.replace(/max-h-96/g, "max-h-0").replace(/opacity-100/g, "opacity-0"));
523                        $btn.find(".rotate-45").removeClass("rotate-45");
524                        $btn.find(".bg-primary.text-white").removeClass("bg-primary text-white");
525                        var $parentItem = $btn.closest("div[class*='border']");
526                        if ($parentItem.length) {
527                            var pc = $parentItem.attr("class") || "";
528                            $parentItem.attr("class", pc.replace(/border-primary(?!\/)(?! shadow)/g, "border-primary/5").replace(/ shadow-md/g, ""));
529                        }
530                        var $qs = $btn.find("span").first();
531                        var qsc = $qs.attr("class") || "";
532                        $qs.attr("class", qsc.replace(/text-primary font-bold/, "text-primary/70"));
533                    } else {
534                        // Open this item
535                        $content.attr("class", cls.replace(/max-h-0/g, "max-h-96").replace(/opacity-0/g, "opacity-100"));
536                        // Rotate plus icon to X
537                        var $iconSpan = $btn.find("span").last();
538                        if ($iconSpan.find("svg").length) {
539                            $iconSpan.addClass("rotate-45 bg-primary text-white");
540                        }
541                        var $parentItem = $btn.closest("div[class*='border']");
542                        if ($parentItem.length) {
543                            var pc = $parentItem.attr("class") || "";
544                            $parentItem.attr("class", pc.replace(/border-primary\/5/g, "border-primary") + " shadow-md");
545                        }
546                        var $qs = $btn.find("span").first();
547                        var qsc = $qs.attr("class") || "";
548                        $qs.attr("class", qsc.replace(/text-primary\/70/, "text-primary font-bold"));
549                    }
550                });
551            })();
552            } catch(e) { console.warn("[WPConvert] CSS accordion error:", e); }
553            
554            try { // [WPConvert: Pill Tab Group Toggle] EC-FAQ-002b
555            // Handles pill-style tab button groups (rounded-full buttons in a flex container)
556            // where one button is "active" (bg-primary text-white) and others are inactive.
557            // Toggles active styling on click. Content filtering is separate (EC-FILTER-001).
558            (function() {
559                $("button.rounded-full, button[class*='rounded-full']").each(function() {
560                    var $btn = $(this);
561                    var $container = $btn.parent();
562                    if ($container.data("wpc-pill-init")) return;
563                    var $pills = $container.find("> button[class*='rounded-full']");
564                    if ($pills.length < 3) return;
565                    // Verify it's a pill group: check for flex container + buttons with similar sizing
566                    var containerCls = $container.attr("class") || "";
567                    if (containerCls.indexOf("flex") === -1) return;
568                    $container.data("wpc-pill-init", true);
569                    
570                    // Find active and inactive class patterns
571                    var activeBtn = null, inactiveBtn = null;
572                    $pills.each(function() {
573                        var cls = $(this).attr("class") || "";
574                        if (cls.indexOf("bg-primary") !== -1 && cls.indexOf("text-white") !== -1 && cls.indexOf("text-white/") === -1) activeBtn = this;
575                        else if (!inactiveBtn) inactiveBtn = this;
576                    });
577                    if (!activeBtn || !inactiveBtn) return;
578                    
579                    var activeCls = $(activeBtn).attr("class");
580                    var inactiveCls = $(inactiveBtn).attr("class");
581                    
582                    $pills.on("click", function(e) {
583                        e.preventDefault();
584                        $pills.each(function() { $(this).attr("class", inactiveCls); });
585                        $(this).attr("class", activeCls);
586                        var cat = $(this).attr("data-wpconvert-faq-cat");
587                        if (cat) {
588                            var $faq = $("#faq");
589                            if ($faq.length) {
590                                $faq.find("[data-wpconvert-faq-panel]").each(function() {
591                                    var on = ($(this).attr("data-wpconvert-faq-panel") || "") === cat;
592                                    $(this).toggle(on);
593                                });
594                            }
595                        }
596                    });
597                });
598            })();
599            } catch(e) { console.warn("[WPConvert] Pill tab error:", e); }
600            
601            try { // [WPConvert: Category/Filter Buttons]
602            // EC-FILTER-001: Universal category/filter button functionality
603            // Detects filter button groups near grids/lists and enables click-to-filter
604            // Searches section, main, container, and generic wrapper elements
605            (function() {
606                var filterInitialized = {};
607
608                function initFilter($btnGroup, $gridContainer, allowNoAll) {
609                    var groupId = $btnGroup.get(0);
610                    if (filterInitialized[groupId]) return;
611                    if ($btnGroup.attr("data-wpc-filter-group")) return;
612
613                    var $filterButtons = $btnGroup.find("> button, > [role='button'], > a").filter(function() {
614                        var t = $(this).text().trim();
615                        return t.length > 0 && t.length < 30 && !/submit|send|cancel|close|search|login|sign/i.test(t);
616                    });
617                    // EC-FILTER-006: Allow 2+ buttons when explicitly permitted (tab-style groups)
618                    if ($filterButtons.length < 2) return;
619                    if ($filterButtons.length < 3 && !allowNoAll) return;
620
621                    var hasAllBtn = false;
622                    $filterButtons.each(function() {
623                        var t = $(this).text().trim().toLowerCase();
624                        // EC-FILTER-010: also match "all products", "all items", etc.
625                        if (t === "all" || t === "view all" || t === "show all" || /^all\b/.test(t)) hasAllBtn = true;
626                    });
627                    // EC-FILTER-006: Skip "All" requirement when caller explicitly allows it
628                    if (!hasAllBtn && !allowNoAll) return;
629
630                    var $filterableItems = $gridContainer.find("[class*='card'], [class*='product'], [class*='item'], article");
631                    if ($filterableItems.length === 0) {
632                        $filterableItems = $gridContainer.children("a, div, article, li");
633                    }
634                    if ($filterableItems.length < 2) return;
635
636                    filterInitialized[groupId] = true;
637                    console.log("[WPConvert] Category filter detected:", $filterButtons.length, "buttons,", $filterableItems.length, "items");
638
639                    // EC-FILTER-004: Inject category badges at runtime for cards with data-category
640                    $filterableItems.each(function() {
641                        var $item = $(this);
642                        var cat = $item.attr("data-category");
643                        if (!cat) return;
644                        if ($item.find(".wpconvert-category-badge").length) return;
645                        var $imgBox = $item.find("[class*='aspect-video']").first();
646                        if (!$imgBox.length) $imgBox = $item.find("div.relative").filter(function() { return $(this).find("img").length > 0; }).first();
647                        if (!$imgBox.length) $imgBox = $item.find("div").filter(function() { return $(this).find("img").length > 0; }).first();
648                        if ($imgBox.length) {
649                            if ($imgBox.css("position") !== "absolute" && $imgBox.css("position") !== "relative") $imgBox.css("position", "relative");
650                            $imgBox.append('<div class="wpconvert-category-badge" style="position:absolute;top:1rem;right:1rem;padding:0.25rem 0.75rem;border-radius:9999px;font-size:0.75rem;font-weight:500;z-index:10;pointer-events:none;background:var(--primary,hsl(186 100% 50%));color:var(--primary-foreground,#000);opacity:0.9">' + $("<span>").text(cat).html() + "</div>");
651                        }
652                    });
653
654                    function matchesCategory($item, filterText) {
655                        var dataCat = ($item.attr("data-category") || $item.attr("data-filter") || $item.attr("data-type") || "").toLowerCase();
656                        if (dataCat && dataCat === filterText) return true;
657                        if (dataCat && dataCat.indexOf(filterText) !== -1) return true;
658
659                        var fullText = $item.text().toLowerCase();
660                        var imgAlts = [];
661                        $item.find("img").each(function() { imgAlts.push(($(this).attr("alt") || "").toLowerCase()); });
662                        var searchText = fullText + " " + imgAlts.join(" ") + " " + ($item.attr("href") || "").toLowerCase() + " " + ($item.attr("class") || "").toLowerCase();
663
664                        if (searchText.indexOf(filterText) !== -1) return true;
665
666                        var words = filterText.split(/\s+/).filter(function(w) { return w.length > 1; });
667                        if (words.length > 1) {
668                            var allMatch = true;
669                            for (var i = 0; i < words.length; i++) {
670                                var w = words[i];
671                                if (searchText.indexOf(w) === -1) {
672                                    var stem = w.replace(/s$/, "");
673                                    if (stem.length > 2 && searchText.indexOf(stem) !== -1) continue;
674                                    allMatch = false; break;
675                                }
676                            }
677                            if (allMatch) return true;
678                        }
679                        return false;
680                    }
681
682                    var origBtnStyles = [];
683                    $filterButtons.each(function() { origBtnStyles.push($(this).attr("class") || ""); });
684
685                    $filterButtons.on("click", function(e) {
686                        e.preventDefault();
687                        e.stopPropagation();
688                        var $clicked = $(this);
689                        var filterText = $clicked.text().trim().toLowerCase();
690
691                        $filterButtons.each(function(i) {
692                            $(this).attr("class", origBtnStyles[i]);
693                            $(this).css("opacity", "0.6");
694                        });
695                        $clicked.css("opacity", "1");
696
697                        // EC-FILTER-005: Iterate grid children (not filterable items) so
698                        // each cell is toggled exactly once. Finds the categorized element
699                        // inside each cell for matching, hides/shows the cell itself so
700                        // CSS Grid reflows with no gaps from empty wrapper divs.
701                        // EC-FILTER-006/010: Also check if this is the "show all" equivalent
702                        if (filterText === "all" || filterText === "view all" || filterText === "show all" || /^all\b/.test(filterText)) {
703                            $gridContainer.children().each(function() {
704                                $(this).css("display", "");
705                            });
706                            return;
707                        }
708                        // EC-FILTER-006: For tab-style groups without "All", if no grid items
709                        // match ANY button text, treat buttons as content panel toggles instead
710                        // of category filters (handled by pricing toggle / tab toggle below)
711
712                        var visibleCount = 0;
713                        $gridContainer.children().each(function() {
714                            var $cell = $(this);
715                            var $primary = $cell.is("[data-category]") ? $cell : $cell.find("[data-category]").first();
716                            if (!$primary.length) $primary = $cell;
717                            if (matchesCategory($primary, filterText)) {
718                                $cell.css("display", "");
719                                visibleCount++;
720                            } else {
721                                $cell.css("display", "none");
722                            }
723                        });
724                        console.log("[WPConvert] Filtered to", filterText + ":", visibleCount, "items visible");
725                    });
726                }
727
728                // Search broad set of containers (not just section)
729                $("section, main, [role='main'], [class*='container'], [class*='wrapper'], [class*='content'], [class*='page']").each(function() {
730                    var $root = $(this);
731
732                    // Strategy 1: Find div with 3+ child buttons (one being "All") followed by a grid
733                    $root.find("div, [class*='flex'], [class*='tabs'], [class*='button-group'], [class*='filter']").each(function() {
734                        var $candidate = $(this);
735                        var directBtns = $candidate.find("> button, > [role='button'], > a[class*='button']");
736                        if (directBtns.length < 3) return;
737                        var hasAll = false;
738                        directBtns.each(function() {
739                            var t = $(this).text().trim().toLowerCase();
740                            // EC-FILTER-010: also match "all products", "all items", etc.
741                            if (t === "all" || t === "view all" || t === "show all" || /^all\b/.test(t)) hasAll = true;
742                        });
743                        if (!hasAll) return;
744
745                        // Find the grid: next sibling, parent siblings, ancestors, or section-wide
746                        var $grid = $candidate.nextAll("[class*='grid']").first();
747                        if (!$grid.length) $grid = $candidate.nextAll("div").find("[class*='grid']").first();
748                        if (!$grid.length) $grid = $candidate.parent().find("[class*='grid'], [class*='list']").not($candidate).first();
749                        if (!$grid.length) $grid = $candidate.parent().nextAll("[class*='grid']").first();
750                        if (!$grid.length) $grid = $candidate.parent().parent().find("> [class*='grid']").first();
751                        if (!$grid.length) $grid = $candidate.closest("section, [class*='container'], [class*='wrapper']").find("[class*='grid']").filter(function() { return $(this).children().length >= 2; }).first();
752                        // EC-FILTER-010: cross-section — grid may be in the next sibling section
753                        if (!$grid.length) {
754                            var $sect = $candidate.closest("section");
755                            if ($sect.length) $grid = $sect.next("section").find("[class*='grid']").filter(function() { return $(this).children().length >= 2; }).first();
756                        }
757                        if (!$grid.length) $grid = $candidate.nextAll("div, ul").first();
758                        if ($grid.length) initFilter($candidate, $grid);
759                    });
760
761                    // Strategy 2: Heading-based detection (original approach, broadened)
762                    var $heading = $root.find("h1, h2, h3").first();
763                    if (!$heading.length) return;
764                    var $headingParent = $heading.parent();
765                    var $btnGroup = $headingParent.next("div, [class*='flex']").filter(function() {
766                        return $(this).find("> button, > [role='button']").length >= 3;
767                    }).first();
768                    if (!$btnGroup.length) {
769                        $btnGroup = $headingParent.nextAll("div, [class*='flex']").filter(function() {
770                            return $(this).find("> button, > [role='button']").length >= 3;
771                        }).first();
772                    }
773                    if ($btnGroup.length) {
774                        var $grid = $btnGroup.nextAll("[class*='grid'], [class*='list'], div").first();
775                        if (!$grid.length) $grid = $btnGroup.parent().nextAll("[class*='grid']").first();
776                        if (!$grid.length) $grid = $btnGroup.closest("section, [class*='container']").find("[class*='grid']").filter(function() { return $(this).children().length >= 2; }).first();
777                        if ($grid.length) initFilter($btnGroup, $grid);
778                    }
779                });
780
781                // EC-FILTER-006 Strategy 3: Tab-style button groups without "All" button
782                // Detects 2+ sibling buttons in a flex container near a grid, even without "All"
783                // EC-FILTER-008: Only initializes when grid items have data-category attributes,
784                // preventing false positives on tab-panel UIs (services, pricing) where clicking
785                // a tab should switch content panels, not filter a grid.
786                $("section, main, [role='main']").each(function() {
787                    var $root = $(this);
788                    $root.find("div, [class*='flex'], [class*='tabs']").each(function() {
789                        var $candidate = $(this);
790                        if (filterInitialized[$candidate.get(0)]) return;
791                        if ($candidate.attr("data-wpc-filter-group")) return;
792                        var directBtns = $candidate.find("> button, > [role='button'], > a[class*='button']").filter(function() {
793                            var t = $(this).text().trim();
794                            return t.length > 0 && t.length < 30 && !/submit|send|cancel|close|search|login|sign/i.test(t);
795                        });
796                        if (directBtns.length < 2 || directBtns.length > 8) return;
797                        // Must NOT already have an "All" button (handled by strategies 1/2)
798                        var hasAll = false;
799                        directBtns.each(function() {
800                            var t = $(this).text().trim().toLowerCase();
801                            // EC-FILTER-010: also match "all products", "all items", etc.
802                            if (t === "all" || t === "view all" || t === "show all" || /^all\b/.test(t)) hasAll = true;
803                        });
804                        if (hasAll) return;
805                        // Look for a nearby grid
806                        var $grid = $candidate.nextAll("[class*='grid']").first();
807                        if (!$grid.length) $grid = $candidate.parent().find("[class*='grid']").not($candidate).first();
808                        if (!$grid.length) $grid = $candidate.parent().nextAll("[class*='grid']").first();
809                        if (!$grid.length) $grid = $candidate.parent().parent().find("> [class*='grid']").first();
810                        if (!$grid.length) $grid = $candidate.closest("section, [class*='container']").find("[class*='grid']").filter(function() { return $(this).children().length >= 2; }).first();
811                        // EC-FILTER-010: cross-section — grid may be in the next sibling section
812                        if (!$grid.length) {
813                            var $sect = $candidate.closest("section");
814                            if ($sect.length) $grid = $sect.next("section").find("[class*='grid']").filter(function() { return $(this).children().length >= 2; }).first();
815                        }
816                        if (!$grid.length) $grid = $candidate.nextAll("div").first();
817                        if ($grid.length && $grid.children().length >= 2) {
818                            // EC-FILTER-008: Only initialize as filter when items are actually
819                            // categorized. Tab panels (service tabs, pricing tabs) have a single
820                            // content area without data-category — filtering would hide everything.
821                            var categorizedCount = 0;
822                            $grid.children().each(function() {
823                                if ($(this).is("[data-category]") || $(this).find("[data-category]").length) categorizedCount++;
824                            });
825                            if (categorizedCount < 2) {
826                                console.log("[WPConvert] Strategy 3 skipped: only " + categorizedCount + " categorized items in grid (need 2+). Likely a tab panel, not a filter.");
827                                return;
828                            }
829                            initFilter($candidate, $grid, true);
830                        }
831                    });
832                });
833            })();
834            } catch(e) { console.warn("[WPConvert] Category filter error:", e); }
835            
836            try { // [WPConvert: EC-PRICING-001 Pricing Toggle]
837            // EC-PRICING-001: Universal pricing toggle (Monthly/Annual, Monthly/Yearly, etc.)
838            // Handles Radix Switch (button[role="switch"]) and 2-button/label toggle patterns
839            (function() {
840                var periodRx = /monthly|annual|yearly|quarterly|weekly/i;
841                function findPricingGrid($from) {
842                    var $g = $from.nextAll("[class*='grid']").first();
843                    if (!$g.length) $g = $from.parent().nextAll("[class*='grid']").first();
844                    if (!$g.length) $g = $from.parent().parent().find("[class*='grid']").filter(function() { return $(this).children().length >= 2; }).first();
845                    if (!$g.length) $g = $from.closest("section").find("[class*='grid']").filter(function() { return $(this).children().length >= 2; }).first();
846                    // EC-PRICING-002: Also search the NEXT sibling section.
847                    // Common pattern: hero section has the toggle, separate pricing section has the grid.
848                    if (!$g.length) {
849                        var $sec = $from.closest("section");
850                        if ($sec.length) {
851                            $g = $sec.nextAll("section").first().find("[class*='grid']").filter(function() {
852                                return $(this).children().length >= 2;
853                            }).first();
854                        }
855                    }
856                    return $g;
857                }
858                // Pattern A: Radix Switch — span(Monthly) + button[role=switch] + span(Annual)
859                $('button[role="switch"]').each(function() {
860                    var $sw = $(this);
861                    if ($sw.data("pricing-wired")) return;
862                    var $parent = $sw.parent();
863                    var kids = $parent.children();
864                    var labels = [];
865                    kids.each(function() {
866                        var tag = (this.tagName || "").toLowerCase();
867                        var txt = $(this).text().trim();
868                        if ((tag === "span" || tag === "label" || tag === "p") && periodRx.test(txt)) {
869                            labels.push({ el: $(this), text: txt.toLowerCase() });
870                        }
871                    });
872                    if (labels.length < 2) return;
873                    var $grid = findPricingGrid($parent);
874                    if (!$grid.length) return;
875                    $sw.data("pricing-wired", true);
876                    var isAnnual = $sw.attr("data-state") === "checked" || $sw.attr("aria-checked") === "true";
877                    // Read pricing data from section attribute if available
878                    var pricingData = null;
879                    var rawPD = $sw.closest("section, [class*='pricing']").attr("data-wpconvert-pricing");
880                    // EC-PRICING-002: Also check the grid's parent section (toggle may be in hero,
881                    // pricing data attribute on the pricing section)
882                    if (!rawPD && $grid.length) rawPD = $grid.closest("section, [class*='pricing']").attr("data-wpconvert-pricing");
883                    if (rawPD) try { pricingData = JSON.parse(rawPD); } catch(e) {}
884                    // Find price elements in each card
885                    var cardPrices = [];
886                    $grid.children().each(function(ci) {
887                        var $card = $(this);
888                        var $priceEl = $card.find("span, div").filter(function() {
889                            return /^\$[\d,]+/.test($(this).text().trim()) && $(this).children().length === 0;
890                        }).first();
891                        if (!$priceEl.length) $priceEl = $card.find("[class*='font-black'], [class*='font-extrabold']").filter(function() {
892                            return /\$/.test($(this).text().trim());
893                        }).first();
894                        var monthly = null, annual = null;
895                        if (pricingData && pricingData[ci]) {
896                            monthly = pricingData[ci].monthly;
897                            annual = pricingData[ci].annual;
898                        }
899                        cardPrices.push({ el: $priceEl, monthly: monthly, annual: annual });
900                    });
901                    // Toggle label styling
902                    function updateLabels(ann) {
903                        labels.forEach(function(l) {
904                            var isMonthly = /month/i.test(l.text);
905                            if ((isMonthly && !ann) || (!isMonthly && ann)) {
906                                l.el.removeClass("text-slate-500 text-gray-500 text-muted").addClass("text-white");
907                            } else {
908                                l.el.removeClass("text-white").addClass("text-slate-500");
909                            }
910                        });
911                    }
912                    function updatePrices(ann) {
913                        cardPrices.forEach(function(cp) {
914                            if (!cp.el.length) return;
915                            var price = ann ? cp.annual : cp.monthly;
916                            if (price !== null) cp.el.text("$" + Number(price).toLocaleString());
917                        });
918                    }
919                    $sw.on("click", function() {
920                        isAnnual = !isAnnual;
921                        var newState = isAnnual ? "checked" : "unchecked";
922                        $sw.attr("data-state", newState).attr("aria-checked", String(isAnnual));
923                        $sw.find("[data-state]").attr("data-state", newState);
924                        updateLabels(isAnnual);
925                        updatePrices(isAnnual);
926                        console.log("[EC-PRICING-001] Switch toggled:", isAnnual ? "annual" : "monthly");
927                    });
928                    console.log("[EC-PRICING-001] Radix Switch pricing toggle initialized, cards:", cardPrices.length);
929                });
930                // Pattern B: Two buttons/labels with pricing period text (non-switch)
931                $("section, [class*='pricing']").each(function() {
932                    var $sec = $(this);
933                    if ($sec.data("pricing-toggle-init")) return;
934                    $sec.find("div").each(function() {
935                        var $group = $(this);
936                        var $btns = $group.find("> button, > [role='button'], > label, > a").filter(function() {
937                            return periodRx.test($(this).text().trim());
938                        });
939                        if ($btns.length !== 2) return;
940                        if ($btns.filter('[role="switch"]').length > 0) return;
941                        var $grid = findPricingGrid($group);
942                        if (!$grid.length) return;
943                        $sec.data("pricing-toggle-init", true);
944                        var pricingData = null;
945                        var rawPD = $sec.attr("data-wpconvert-pricing");
946                        if (rawPD) try { pricingData = JSON.parse(rawPD); } catch(e) {}
947                        var cardPrices = [];
948                        $grid.children().each(function(ci) {
949                            var $card = $(this);
950                            var $priceEl = $card.find("span, div").filter(function() {
951                                return /^\$[\d,]+/.test($(this).text().trim()) && $(this).children().length === 0;
952                            }).first();
953                            var monthly = null, annual = null;
954                            if (pricingData && pricingData[ci]) {
955                                monthly = pricingData[ci].monthly;
956                                annual = pricingData[ci].annual;
957                            }
958                            cardPrices.push({ el: $priceEl, monthly: monthly, annual: annual });
959                        });
960                        var fc = ($btns.eq(0).attr("class")||"").split(/\s+/);
961                        var sc = ($btns.eq(1).attr("class")||"").split(/\s+/);
962                        var ao = fc.filter(function(c){return sc.indexOf(c)===-1&&c;});
963                        var io = sc.filter(function(c){return fc.indexOf(c)===-1&&c;});
964                        $btns.on("click", function(e) {
965                            e.preventDefault();
966                            var idx = $btns.index($(this));
967                            var ann = idx === 1;
968                            $btns.each(function(bi) {
969                                var $b = $(this);
970                                if (bi === idx) { io.forEach(function(c){$b.removeClass(c);}); ao.forEach(function(c){$b.addClass(c);}); }
971                                else { ao.forEach(function(c){$b.removeClass(c);}); io.forEach(function(c){$b.addClass(c);}); }
972                            });
973                            cardPrices.forEach(function(cp) {
974                                if (!cp.el.length) return;
975                                var price = ann ? cp.annual : cp.monthly;
976                                if (price !== null) cp.el.text("$" + Number(price).toLocaleString());
977                            });
978                        });
979                        console.log("[EC-PRICING-001] Button pricing toggle initialized");
980                    });
981                });
982            })();
983            } catch(e) { console.warn("[WPConvert] Pricing toggle error:", e); }
984            
985            try { // [WPConvert: EC-BLOG-030 Inline Blog Search]
986            // EC-BLOG-030: Wire inline search inputs near post/card grids for client-side filtering
987            // Covers static blog pages where search input exists but isn't connected to anything
988            // EC-BLOG-030b: Skip inputs inside [data-wpconvert-form], skip <form> grids,
989            // and use word boundaries for "post" so "Postcode" does not false-positive.
990            (function() {
991                var allInputs = $('input[type="text"], input[type="search"], input:not([type])').not('#wpconvert-search-input');
992                allInputs.each(function() {
993                    var $inp = $(this);
994                    if ($inp.data("wpc-search-init")) return;
995                    if ($inp.closest("[data-wpconvert-form]").length) return;
996                    var ph = ($inp.attr("placeholder") || "").toLowerCase();
997                    if (!/search|filter|find|blog|\bposts?\b|article/i.test(ph)) return;
998                    // Find nearby card grid (skip <form> elements — they are real forms, not card grids)
999                    var $section = $inp.closest("section, main, [class*='container']");
1000                    if (!$section.length) $section = $inp.parent().parent().parent();
1001                    var $grid = $section.find("[class*='grid']").filter(function() {
1002                        return $(this).children().length >= 2 && !$(this).is("form");
1003                    }).first();
1004                    if (!$grid.length) return;
1005                    var $cards = $grid.children();
1006                    if ($cards.length < 2) return;
1007                    $inp.data("wpc-search-init", true);
1008                    var debounce;
1009                    $inp.on("input", function() {
1010                        clearTimeout(debounce);
1011                        debounce = setTimeout(function() {
1012                            var q = $inp.val().trim().toLowerCase();
1013                            $cards.each(function() {
1014                                var text = $(this).text().toLowerCase();
1015                                $(this).toggle(!q || text.indexOf(q) !== -1);
1016                            });
1017                        }, 150);
1018                    });
1019                    console.log("[EC-BLOG-030] Blog search input wired:", $cards.length, "cards");
1020                });
1021            })();
1022            } catch(e) { console.warn("[WPConvert] Blog search error:", e); }
1023            
1024            try { // [WPConvert: Nav Link Injection]
1025            // Inject navigation links from menus.json (for React/Vite sites with direct <a> links)
1026            var $navContainer = $("[data-wpconvert-nav-container]");
1027            if ($navContainer.length) {
1028                console.log("Found navigation container, loading menu items...");
1029                
1030                // Load menu data
1031                $.getJSON("<?php echo get_template_directory_uri(); ?>/assets/data/menus.json", function(menuData) {
1032                    if (menuData && menuData.primary && menuData.primary.items) {
1033                        var items = menuData.primary.items;
1034                        var $template = $navContainer.find("[data-wpconvert-nav-template]");
1035                        var templateClasses = $template.attr("class") || "";
1036                        var $parent = $template.parent();
1037                        
1038                        console.log("Menu items to inject:", items.length);
1039                        
1040                        // Remove the template link
1041                        $template.remove();
1042                        
1043                        // Inject menu items
1044                        items.forEach(function(item) {
1045                            var $link = $("<a></a>");
1046                            $link.attr("href", item.url);
1047                            $link.text(item.title);
1048                            
1049                            // Preserve original classes from template
1050                            if (item.classes && item.classes.length > 0) {
1051                                $link.attr("class", item.classes.join(" "));
1052                            } else {
1053                                $link.attr("class", templateClasses);
1054                            }
1055                            
1056                            // Add any custom attributes
1057                            if (item.attributes) {
1058                                Object.keys(item.attributes).forEach(function(key) {
1059                                    if (key !== "href" && key !== "class") {
1060                                        $link.attr(key, item.attributes[key]);
1061                                    }
1062                                });
1063                            }
1064                            
1065                            $parent.append($link);
1066                        });
1067                        
1068                        console.log("Navigation links injected successfully");
1069                    }
1070                }).fail(function() {
1071                    console.warn("Could not load menus.json, navigation may not work correctly");
1072                });
1073            }
1074            } catch(e) { console.warn("[WPConvert] Nav link injection error:", e); }
1075            
1076            try { // [WPConvert: Slide-out Panels & Sidebar Layout]
1077            // EC-SB-001: Handle slide-out navigation panels (sidebar-controlled menus)
1078            // These are full-height panels that slide in from the side, controlled by sidebar buttons
1079            var $slideOutPanels = $('nav[data-wpconvert-slideout-panel="true"]');
1080            var $slideOutToggleButtons = $('button[data-wpconvert-slideout-toggle]');
1081            
1082            // EC-SB-003: Hide default footer for full-viewport sidebar layouts
1083            // These sites have fixed sidebar + full-height content - no traditional footer
1084            var hasFixedSidebar = false;
1085            var hasFullHeightLayout = false;
1086            
1087            // Check for fixed sidebar - look for aside with fixed positioning classes
1088            $('aside').each(function() {
1089                var classes = ($(this).attr('class') || '');
1090                if (classes.indexOf('fixed') !== -1 && 
1091                    (classes.indexOf('left-0') !== -1 || classes.indexOf('left-') !== -1)) {
1092                    hasFixedSidebar = true;
1093                    console.log("EC-SB-003: Found fixed sidebar");
1094                    return false;
1095                }
1096            });
1097            
1098            // Also check for slide-out panels as indicator of sidebar layout
1099            if ($('nav[data-wpconvert-slideout-panel]').length > 0) {
1100                hasFixedSidebar = true;
1101                console.log("EC-SB-003: Found slide-out panel (indicates sidebar layout)");
1102            }
1103            
1104            // Check for full-height elements in content area
1105            $('div, section, article, main').each(function() {
1106                var classes = ($(this).attr('class') || '');
1107                if (classes.indexOf('h-screen') !== -1 || 
1108                    classes.indexOf('min-h-screen') !== -1 ||
1109                    (classes.indexOf('h-full') !== -1 && $(this).closest('aside, nav').length === 0)) {
1110                    hasFullHeightLayout = true;
1111                    return false;
1112                }
1113            });
1114            
1115            // Also check if main content has full viewport height via fixed positioning
1116            // EC-CANVAS-001: exclude <canvas> — NeonScrollLine-style overlays use fixed inset-0 but are not layout shells
1117            if ($('[class*="fixed"][class*="w-full"][class*="h-screen"]').not('canvas').length > 0 ||
1118                $('[class*="fixed"][class*="inset-0"]').not('canvas').length > 0) {
1119                hasFullHeightLayout = true;
1120            }
1121            
1122            console.log("EC-SB-003: hasFixedSidebar=" + hasFixedSidebar + ", hasFullHeightLayout=" + hasFullHeightLayout);
1123            
1124            if (hasFixedSidebar && hasFullHeightLayout) {
1125                console.log("EC-SB-003: Hiding default footer for full-viewport sidebar layout");
1126                // Hide the fallback footer for full-viewport layouts
1127                $('footer.site-footer').hide();
1128                $('.site-info').hide();
1129                
1130                // Remove padding/margin that creates white space
1131                $('main.site-main, .site-main, #primary, .content-area, body, html').css({
1132                    'padding-bottom': '0',
1133                    'margin-bottom': '0'
1134                });
1135                
1136                // Hide any footer containing default text
1137                $('footer').each(function() {
1138                    var footerText = $(this).text().toLowerCase();
1139                    if (footerText.indexOf('all rights reserved') !== -1 || 
1140                        footerText.indexOf('converted theme') !== -1) {
1141                        $(this).hide();
1142                        console.log("EC-SB-003: Hidden footer with default text");
1143                    }
1144                });
1145                
1146                // Also ensure the main wrapper doesn't create scroll space
1147                $('#primary, .content-area').css({
1148                    'min-height': '0',
1149                    'overflow': 'hidden'
1150                });
1151            }
1152            
1153            // EC-SB-001: Slide-out panel handling DISABLED - using WPConvert mobile nav instead
1154            // The sidebar menu button will be wired to the mobile nav by hamburger detection
1155            if (false && $slideOutPanels.length > 0 && $slideOutToggleButtons.length > 0) {
1156                console.log("EC-SB-001: Found slide-out navigation panel(s):", $slideOutPanels.length);
1157                
1158                $slideOutToggleButtons.each(function() {
1159                    var $btn = $(this);
1160                    var targetId = $btn.attr("data-wpconvert-slideout-toggle");
1161                    var $targetPanel = targetId ? $("#" + targetId) : $slideOutPanels.first();
1162                    
1163                    if ($targetPanel.length === 0) {
1164                        $targetPanel = $slideOutPanels.first();
1165                    }
1166                    
1167                    console.log("EC-SB-001: Wiring slide-out toggle button to panel:", targetId || "first-panel");
1168                    
1169                    // Set initial state
1170                    var isInitiallyOpen = $targetPanel.attr("aria-hidden") !== "true";
1171                    $btn.attr("aria-expanded", isInitiallyOpen ? "true" : "false");
1172                    
1173                    // Create close button inside panel if not exists
1174                    if ($targetPanel.find(".wpconvert-slideout-close").length === 0) {
1175                        var closeBtn = $('<button class="wpconvert-slideout-close absolute top-4 right-4 p-2 text-slate-600 hover:text-slate-900 z-50" aria-label="Menü schließen">' +
1176                            '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"></path><path d="m6 6 12 12"></path></svg>
1176' +
1177                        '</button>');
1178                        $targetPanel.prepend(closeBtn);
1179                    }
1180                    
1181                    // Toggle function - SIMPLE: just toggle display none/block
1182                    // Panel was captured with correct open-state positioning classes
1183                    // We simply show/hide it with display property
1184                    function toggleSlideOutPanel(open) {
1185                        // Re-select panel each time to ensure we have the right element
1186                        var $panel = $("#" + targetId);
1187                        if ($panel.length === 0) {
1188                            $panel = $('nav[data-wpconvert-slideout-panel="true"]').first();
1189                        }
1190                        
1191                        console.log("EC-SB-001: toggleSlideOutPanel called with open=" + open + ", panel found=" + $panel.length);
1192                        
1193                        if ($panel.length === 0) {
1194                            console.error("EC-SB-001: Could not find slide-out panel!");
1195                            return;
1196                        }
1197                        
1198                        if (open) {
1199                            $panel.attr("aria-hidden", "false");
1200                            // Use multiple methods to ensure visibility
1201                            $panel.css({
1202                                "display": "block",
1203                                "visibility": "visible",
1204                                "opacity": "1",
1205                                "z-index": "9998"  // High z-index to ensure it's on top
1206                            });
1207                            $panel.show();
1208                            $btn.attr("aria-expanded", "true");
1209                            // Change to X icon
1210                            var $svg = $btn.find("svg");
1211                            if ($svg.length > 0) {
1212                                $svg.attr("class", "lucide lucide-x w-8 h-8");
1213                                $svg.html('<path d="M18 6 6 18"></path><path d="m6 6 12 12"></path>');
1214                            }
1215                            console.log("EC-SB-001: Slide-out panel OPENED, display=" + $panel.css("display"));
1216                        } else {
1217                            $panel.attr("aria-hidden", "true");
1218                            $panel.css({
1219                                "display": "none",
1220                                "visibility": "hidden"
1221                            });
1222                            $panel.hide();
1223                            $btn.attr("aria-expanded", "false");
1224                            // Change to Menu icon
1225                            var $svg = $btn.find("svg");
1226                            if ($svg.length > 0) {
1227                                $svg.attr("class", "lucide lucide-menu w-8 h-8");
1228                                $svg.html('<line x1="4" x2="20" y1="12" y2="12"></line><line x1="4" x2="20" y1="6" y2="6"></line><line x1="4" x2="20" y1="18" y2="18"></line>');
1229                            }
1230                            console.log("EC-SB-001: Slide-out panel CLOSED");
1231                        }
1232                    }
1233                    
1234                    // Toggle button click handler - use event delegation on document to survive DOM changes
1235                    $(document).off("click.wpconvert-slideout-" + targetId).on("click.wpconvert-slideout-" + targetId, '[data-wpconvert-slideout-toggle="' + targetId + '"]', function(e) {
1236                        e.preventDefault();
1237                        e.stopPropagation();
1238                        e.stopImmediatePropagation();
1239                        var $clickedBtn = $(this);
1240                        var isOpen = $clickedBtn.attr("aria-expanded") === "true";
1241                        toggleSlideOutPanel(!isOpen);
1242                        return false;
1243                    });
1244                    
1245                    // Close button click handler - use delegation
1246                    $(document).off("click.wpconvert-slideout-close-" + targetId).on("click.wpconvert-slideout-close-" + targetId, "#" + targetId + " .wpconvert-slideout-close", function(e) {
1247                        e.preventDefault();
1248                        e.stopPropagation();
1249                        toggleSlideOutPanel(false);
1250                        return false;
1251                    });
1252                    
1253                    // Close when clicking a link inside the panel (but allow navigation)
1254                    $targetPanel.find("a").on("click.wpconvert-slideout-nav", function() {
1255                        // Close panel after a brief delay to allow navigation
1256                        setTimeout(function() {
1257                            toggleSlideOutPanel(false);
1258                        }, 100);
1259                    });
1260                    
1261                    // Simple initialization: ensure panel is hidden via display:none
1262                    $targetPanel.css("display", "none");
1263                    $targetPanel.attr("aria-hidden", "true");
1264                    $btn.attr("aria-expanded", "false");
1265                    
1266                    console.log("EC-SB-001: Panel initialized in CLOSED state (display:none)");
1267                });
1268            }
1269            } catch(e) { console.warn("[WPConvert] Slide-out panels error:", e); }
1270            
1271            try { // [WPConvert: Hamburger Menu]
1272            // EC-MOBILE-DRAWER: Skip entire hamburger handler when preserved drawer exists.
1273            // The preserved drawer's inline script handles all toggle logic independently.
1274            if (document.querySelector('[data-wpc-preserved-drawer]')) {
1275                console.log('[EC-MOBILE-DRAWER] Preserved drawer detected — skipping fallback hamburger handler');
1276                throw 'skip'; // jumps to catch block, which just warns and continues
1277            }
1278            // Fix empty hamburger menu buttons (React icon components not captured during snapshot)
1279            // Inject SVG icon and make menu functional
1280            // EXPANDED SELECTOR: Also detect Tailwind mobile buttons (lg:hidden, md:hidden) and data-wpconvert-trigger
1281            // EC-SB-001: EXCLUDE buttons with data-wpconvert-slideout-toggle (those are handled above)
1282            var hamburgerSelector = [
1283                'button[aria-label*="Menu"]',
1284                'button[aria-label*="menu"]',
1285                'button[aria-label*="navigation" i]',
1286                'button[aria-label*="toggle" i]',
1287                '.menu-toggle',
1288                '.hamburger-menu',
1289                '[class*="hamburger"]',
1290                'button[data-wpconvert-trigger]',
1291                'button.lg\\:hidden',
1292                'button.md\\:hidden',
1293                'button[class*="lg:hidden"]',
1294                'button[class*="md:hidden"]'
1295            ].join(', ');
1296            
1297            $(hamburgerSelector).each(function() {
1298                var $btn = $(this);
1299                
1300                // EC-SB-001: Skip buttons that have inline onclick for slide-out toggle
1301                if ($btn.attr("onclick") && $btn.attr("onclick").indexOf("main-menu") !== -1) {
1302                    console.log("Skipping button with inline slide-out toggle (EC-SB-001)");
1303                    return;
1304                }
1305                
1306                // EC-NAV-MOBILE-003: Skip buttons that already have an inline onclick for wpconvert-mobile-nav.
1307                // EC-NAV-MOBILE-002 injects inline onclick on hamburger buttons for jQuery-free reliability.
1308                // If we also attach a jQuery handler, the two conflict: inline opens the menu, then
1309                // jQuery sees aria-expanded="true" and immediately closes it in the same click cycle.
1310                if ($btn.attr("onclick") && $btn.attr("onclick").indexOf("wpconvert-mobile-nav") !== -1) {
1311                    console.log("Skipping button with inline mobile nav toggle (EC-NAV-MOBILE-003)");
1312                    return;
1313                }
1314                
1315                // CRITICAL: Skip buttons inside <aside> elements - they use inline onclick handlers
1316                if ($btn.closest("aside").length > 0) {
1317                    console.log("Skipping button inside <aside> - uses inline onclick");
1318                    return;
1319                }
1320                
1321                // EC-SB-001: Skip cookie/settings buttons - these should NOT get hamburger icons
1322                var ariaLabel = ($btn.attr("aria-label") || "").toLowerCase();
1323                var btnHtml = ($btn.html() || "").toLowerCase();
1324                var btnText = ($btn.text() || "").toLowerCase();
1325                var isCookieButton = ariaLabel.indexOf("cookie") !== -1 || 
1326                                     btnText.indexOf("cookie") !== -1 ||
1327                                     btnHtml.indexOf("lucide-cookie") !== -1 ||
1328                                     ariaLabel.indexOf("datenschutz") !== -1 ||
1329                                     ariaLabel.indexOf("privacy") !== -1;
1330                
1331                if (isCookieButton) {
1332                    console.log("Skipping cookie button - not a hamburger menu:", ariaLabel || btnText.substring(0, 30));
1333                    return;
1334                }
1335
1336                // EC-THEME-002b: Skip dark/light mode toggle buttons.
1337                // Theme toggles use 'aria-label="Toggle dark mode"' (matched by the
1338                // 'button[aria-label*="toggle" i]' selector) and a sun/moon SVG.
1339                // EC-THEME-002 wires its own click handler in header.php; if the
1340                // hamburger handler also attaches, the first user click opens the
1341                // mobile menu and the theme only toggles on the second click.
1342                if ($btn.attr("data-wpc-theme-toggle")) {
1343                    console.log("Skipping theme toggle button - has data-wpc-theme-toggle (EC-THEME-002b):", ariaLabel);
1344                    return;
1345                }
1346                var isThemeToggleByAria = /toggles*(?:tos*)?(?:dark|theme|mode|light)/.test(ariaLabel) ||
1347                                          /�(?:dark|light|theme)s*mode�/.test(ariaLabel) ||
1348                                          /switchs*(?:dark|theme|mode|light)/.test(ariaLabel);
1349                var isThemeToggleByIcon = btnHtml.indexOf("lucide-sun") !== -1 ||
1350                                          btnHtml.indexOf("lucide-moon") !== -1;
1351                if (isThemeToggleByAria || isThemeToggleByIcon) {
1352                    console.log("Skipping theme toggle button by shape (EC-THEME-002b):", ariaLabel || btnText.substring(0, 30));
1353                    return;
1354                }
1355                
1356                // CRITICAL: Skip SEARCH buttons - these should open search modal, not hamburger menu
1357                // Detect by: aria-label containing "search", or button contains search SVG icon
1358                var ariaLabel = ($btn.attr("aria-label") || "").toLowerCase();
1359                var btnHtml = ($btn.html() || "").toLowerCase();
1360                var isSearchButton = ariaLabel.indexOf("search") !== -1 || 
1361                                     (btnHtml.indexOf("circle") !== -1 && btnHtml.indexOf("m21 21") !== -1) || // Lucide search icon path
1362                                     btnHtml.indexOf("lucide-search") !== -1;
1363                
1364                if (isSearchButton) {
1365                    console.log("Skipping search button (will be handled by search modal):", ariaLabel);
1366                    return; // Skip - this is a search button, not a hamburger menu
1367                }
1368                
1369                // EC-NAV-MOBILE-001: Skip Radix Sheet/Dialog/Select triggers
1370                // These are component triggers (e.g., mobile filter panels) that should NOT open the hamburger menu
1371                var ariaHaspopup = ($btn.attr("aria-haspopup") || "").toLowerCase();
1372                if (ariaHaspopup === "dialog" || ariaHaspopup === "listbox" || ariaHaspopup === "menu") {
1373                    console.log("Skipping Radix component trigger (aria-haspopup=" + ariaHaspopup + "):", $btn.text().trim().substring(0, 30));
1374                    return;
1375                }
1376                // Skip buttons with data-state + aria-controls pointing to Radix IDs (pattern: radix-:rN:)
1377                var ariaControls = ($btn.attr("aria-controls") || "");
1378                if ($btn.attr("data-state") && /^radix-/.test(ariaControls)) {
1379                    console.log("Skipping Radix-controlled button (aria-controls=" + ariaControls + ")");
1380                    return;
1381                }
1382                // Skip buttons whose text content is clearly a filter/sort trigger
1383                var btnTextLower = ($btn.text() || "").trim().toLowerCase();
1384                if (/^filter/i.test(btnTextLower) || /^sort/i.test(btnTextLower)) {
1385                    console.log("Skipping filter/sort trigger button:", btnTextLower.substring(0, 30));
1386                    return;
1387                }
1388                
1389                // CRITICAL: Skip dropdown trigger buttons - these should NOT open the mobile nav
1390                // Dropdown triggers are buttons inside .group/.relative containers that have a sibling dropdown panel
1391                // This prevents the "first click opens sidebar instead of dropdown" bug
1392                var $parent = $btn.parent();
1393                var parentClasses = ($parent.attr("class") || "").toLowerCase();
1394                var isInsideDropdownGroup = parentClasses.indexOf("group") !== -1 || parentClasses.indexOf("relative") !== -1;
1395                
1396                if (isInsideDropdownGroup) {
1397                    // Check if there's a sibling dropdown panel (div with absolute/invisible/opacity-0/scale-y-0 classes)
1398                    var $siblingDropdown = $btn.siblings("div").filter(function() {
1399                        var sibClasses = ($(this).attr("class") || "").toLowerCase();
1400                        return sibClasses.indexOf("absolute") !== -1 || 
1401                               sibClasses.indexOf("invisible") !== -1 || 
1402                               sibClasses.indexOf("opacity-0") !== -1 || 
1403                               sibClasses.indexOf("scale-y-0") !== -1;
1404                    });
1405                    
1406                    if ($siblingDropdown.length > 0) {
1407                        console.log("Skipping dropdown trigger button (has sibling dropdown panel):", $btn.text().trim().substring(0, 30));
1408                        return; // Skip this button - it's a dropdown trigger, not a hamburger menu
1409                    }
1410                }
1411                
1412                // Also skip if button has ChevronDown icon (common dropdown indicator)
1413                var hasChevronIcon = $btn.find("svg").length > 0 && $btn.text().trim().length > 0;
1414                var btnText = $btn.text().trim().toLowerCase();
1415                var looksLikeDropdownTrigger = hasChevronIcon && (
1416                    btnText.indexOf("services") !== -1 ||
1417                    btnText.indexOf("products") !== -1 ||
1418                    btnText.indexOf("solutions") !== -1 ||
1419                    btnText.indexOf("resources") !== -1 ||
1420                    btnText.indexOf("about") !== -1 ||
1421                    btnText.indexOf("more") !== -1
1422                );
1423                
1424                if (looksLikeDropdownTrigger && isInsideDropdownGroup) {
1425                    console.log("Skipping dropdown trigger button (has chevron + nav text):", btnText.substring(0, 30));
1426                    return;
1427                }
1428                
1429                // Check if button is empty or has no visible content
1430                if ($.trim($btn.html()) === "" || $btn.children().length === 0) {
1431                    console.log("Found empty menu button, injecting hamburger icon");
1432                    
1433                    // Inject hamburger SVG icon (matching lucide-react Menu icon style)
1434                    var hamburgerSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="w-8 h-8"><line x1="4" x2="20" y1="12" y2="12"></line><line x1="4" x2="20" y1="6" y2="6"></line><line x1="4" x2="20" y1="18" y2="18"></line></svg>';
1435                    $btn.html(hamburgerSvg);
1436                }
1437                
1438                // Detect which side of the screen the hamburger button is on
1439                var $mobileNav = $("#wpconvert-mobile-nav");
1440                var btnRect = $btn[0].getBoundingClientRect();
1441                var windowWidth = $(window).width();
1442                var btnCenterX = btnRect.left + (btnRect.width / 2);
1443                
1444                // If button is in left half of screen, slide menu from left; otherwise from right
1445                if (btnCenterX < windowWidth / 2) {
1446                    $mobileNav.removeClass("slide-right").addClass("slide-left");
1447                    console.log("Hamburger on LEFT side - menu will slide from left");
1448                } else {
1449                    $mobileNav.removeClass("slide-left").addClass("slide-right");
1450                    console.log("Hamburger on RIGHT side - menu will slide from right");
1451                }
1452                
1453                // Make the button toggle a mobile navigation menu
1454                $btn.off("click.wpconvert").on("click.wpconvert", function(e) {
1455                    e.preventDefault();
1456                    e.stopPropagation();
1457                    
1458                    var isOpen = $btn.attr("aria-expanded") === "true";
1459                    
1460                    if (isOpen) {
1461                        // Close menu
1462                        $mobileNav.removeClass("open").addClass("closed");
1463                        $btn.attr("aria-expanded", "false");
1464                        $("body").removeClass("mobile-nav-open");
1465                    } else {
1466                        // Open menu
1467                        $mobileNav.removeClass("closed").addClass("open");
1468                        $btn.attr("aria-expanded", "true");
1469                        $("body").addClass("mobile-nav-open");
1470                    }
1471                });
1472            });
1473            } catch(e) { console.warn("[WPConvert] Hamburger menu error:", e); }
1474            
1475            try { // [WPConvert: Search Modal]
1476            // UNIVERSAL SEARCH MODAL - works for any site with search icons
1477            // Handles search button clicks, Ctrl+K shortcut, and provides live search
1478            (function() {
1479                var $searchModal = $("#wpconvert-search-modal");
1480                var $searchInput = $("#wpconvert-search-input");
1481                var $searchResults = $("#wpconvert-search-results");
1482                var searchData = null;
1483                
1484                if (!$searchModal.length) return;
1485                
1486                // Load searchable pages from routes.json and menus.json
1487                function loadSearchData() {
1488                    if (searchData && searchData.length > 0) return Promise.resolve(searchData);
1489                    
1490                    searchData = [];
1491                    var promises = [];
1492                    
1493                    // Load routes.json (can be object or array)
1494                    promises.push(
1495                        $.getJSON("<?php echo get_template_directory_uri(); ?>/assets/data/routes.json")
1496                            .then(function(routes) {
1497                                if (routes) {
1498                                    // Handle both object format {"/path": {...}}
1498 and array format [{...}]
1499                                    if (Array.isArray(routes)) {
1500                                        routes.forEach(function(route) {
1501                                            if (route.path && route.title) {
1502                                                searchData.push({
1503                                                    title: route.title,
1504                                                    path: route.path || route.url,
1505                                                    keywords: (route.title + " " + (route.path || route.url || "")).toLowerCase()
1506                                                });
1507                                            }
1508                                        });
1509                                    } else if (typeof routes === "object") {
1510                                        // Object format: {"/path": {title, url, slug}}
1511                                        Object.keys(routes).forEach(function(path) {
1512                                            var route = routes[path];
1513                                            if (route && route.title && !path.includes(":")) { // Skip dynamic routes
1514                                                searchData.push({
1515                                                    title: route.title,
1516                                                    path: path,
1517                                                    keywords: (route.title + " " + path).toLowerCase()
1518                                                });
1519                                            }
1520                                        });
1521                                    }
1522                                }
1523                            })
1524                            .catch(function() { /* routes.json may not exist */ })
1525                    );
1526                    
1527                    // Also load menus.json
1528                    promises.push(
1529                        $.getJSON("<?php echo get_template_directory_uri(); ?>/assets/data/menus.json")
1530                            .then(function(menus) {
1531                                if (menus && menus.primary && menus.primary.items) {
1532                                    menus.primary.items.forEach(function(item) {
1533                                        if (item.url && !searchData.some(function(s) { return s.path === item.url; })) {
1534                                            searchData.push({
1535                                                title: item.title,
1536                                                path: item.url,
1537                                                keywords: item.title.toLowerCase()
1538                                            });
1539                                        }
1540                                        // Add children
1541                                        if (item.children && Array.isArray(item.children)) {
1542                                            item.children.forEach(function(child) {
1543                                                if (child.url && !searchData.some(function(s) { return s.path === child.url; })) {
1544                                                    searchData.push({
1545                                                        title: child.title,
1546                                                        path: child.url,
1547                                                        keywords: (child.title + " " + item.title).toLowerCase()
1548                                                    });
1549                                                }
1550                                            });
1551                                        }
1552                                    });
1553                                }
1554                                // Also check footer menu
1555                                if (menus && menus.footer && menus.footer.items) {
1556                                    menus.footer.items.forEach(function(item) {
1557                                        if (item.url && !searchData.some(function(s) { return s.path === item.url; })) {
1558                                            searchData.push({
1559                                                title: item.title,
1560                                                path: item.url,
1561                                                keywords: item.title.toLowerCase()
1562                                            });
1563                                        }
1564                                    });
1565                                }
1566                            })
1567                            .catch(function() { /* menus.json may not exist */ })
1568                    );
1569                    
1570                    return Promise.all(promises).then(function() {
1571                        console.log("WPConvert Search: Loaded " + searchData.length + " searchable pages");
1572                        return searchData;
1573                    });
1574                }
1575                
1576                function openSearch() {
1577                    $searchModal.addClass("open");
1578                    $searchInput.val("").focus();
1579                    $searchResults.empty();
1580                    $("body").css("overflow", "hidden");
1581                    loadSearchData();
1582                }
1583                
1584                function closeSearch() {
1585                    $searchModal.removeClass("open");
1586                    $("body").css("overflow", "");
1587                }
1588                
1589                function performSearch(query) {
1590                    if (!query || query.length < 1) {
1591                        $searchResults.empty();
1592                        return;
1593                    }
1594                    
1595                    loadSearchData().then(function(data) {
1596                        var q = query.toLowerCase();
1597                        var results = data.filter(function(item) {
1598                            return item.keywords.indexOf(q) !== -1 || 
1599                                   item.title.toLowerCase().indexOf(q) !== -1;
1600                        }).slice(0, 8);
1601                        
1602                        $searchResults.empty();
1603                        
1604                        if (results.length === 0) {
1605                            $searchResults.html('<div style="padding: 1rem; text-align: center; color: hsl(var(--muted-foreground));">No results found for "' + query + '"</div>');
1606                        } else {
1607                            results.forEach(function(item) {
1608                                var $result = $('<a class="wpconvert-search-result" href="<?php echo home_url(); ?>' + item.path + '"></a>');
1609                                $result.html('<div class="wpconvert-search-result-title">' + item.title + '</div><div class="wpconvert-search-result-url">' + item.path + '</div>');
1610                                $result.on("click", function() {
1611                                    closeSearch();
1612                                });
1613                                $searchResults.append($result);
1614                            });
1615                        }
1616                    });
1617                }
1618                
1619                // Expose global functions
1620                window.wpconvertOpenSearch = openSearch;
1621                window.wpconvertCloseSearch = closeSearch;
1622                
1623                // Bind close button and backdrop AFTER functions are defined
1624                $("#wpconvert-search-close").off("click.wpconvert").on("click.wpconvert", function(e) {
1625                    e.preventDefault();
1626                    e.stopPropagation();
1627                    closeSearch();
1628                });
1629                
1630                $("#wpconvert-search-backdrop").off("click.wpconvert").on("click.wpconvert", function(e) {
1631                    e.preventDefault();
1632                    closeSearch();
1633                });
1634                
1635                // Handle search button clicks - find search buttons in HEADER/NAV only
1636                // EC-SEARCH-002: Restrict .lucide-search matching to header/nav context
1637                // to avoid false positives on content buttons (e.g. "SEO Optimization" tab
1638                // with a search icon triggers modal instead of filtering)
1639                var searchSelector = [
1640                    'header button[aria-label*="Search" i]',
1641                    'nav button[aria-label*="Search" i]',
1642                    'header button[aria-label*="search" i]',
1643                    'nav button[aria-label*="search" i]',
1644                    '[data-wpconvert-trigger][aria-label*="search" i]',
1645                    'header button:has(.lucide-search)',
1646                    'nav button:has(.lucide-search)',
1647                    '[class*="navbar"] button:has(.lucide-search)',
1648                    'header a[aria-label*="Search" i]',
1649                    'nav a[aria-label*="Search" i]'
1650                ].join(", ");
1651                
1652                $(searchSelector).off("click.wpconvert-search").on("click.wpconvert-search", function(e) {
1653                    e.preventDefault();
1654                    e.stopPropagation();
1655                    openSearch();
1656                });
1657                
1658                // Keyboard shortcuts
1659                $(document).on("keydown.wpconvert-search", function(e) {
1660                    // Ctrl+K or Cmd+K to open
1661                    if ((e.ctrlKey || e.metaKey) && e.key === "k") {
1662                        e.preventDefault();
1663                        openSearch();
1664                    }
1665                    // Escape to close
1666                    if (e.key === "Escape" && $searchModal.hasClass("open")) {
1667                        closeSearch();
1668                    }
1669                });
1670                
1671                // Search input handler
1672                var searchTimeout;
1673                $searchInput.on("input", function() {
1674                    clearTimeout(searchTimeout);
1675                    var query = $(this).val();
1676                    searchTimeout = setTimeout(function() {
1677                        performSearch(query);
1678                    }, 150);
1679                });
1680                
1681                // Enter key on input navigates to first result
1682                $searchInput.on("keydown", function(e) {
1683                    if (e.key === "Enter") {
1684                        var $firstResult = $searchResults.find("a").first();
1685                        if ($firstResult.length) {
1686                            window.location.href = $firstResult.attr("href");
1687                        }
1688                    }
1689                });
1690                
1691                console.log("WPConvert Search Modal initialized");
1692            })();
1693            } catch(e) { console.warn("[WPConvert] Search modal error:", e); }
1694            
1695            try { // [WPConvert: Mobile Menu Toggle]
1696            // UNIVERSAL MOBILE MENU TOGGLE (for preserved mobile menus with data-wpc-* attributes)
1697            // This handles mobile menus that were preserved from the original site with wp_nav_menu() integration
1698            (function() {
1699                var toggles = document.querySelectorAll("[data-wpc-menu-toggle]");
1700                var panel = document.querySelector("[data-wpc-menu-panel]");
1701                var overlay = document.querySelector("[data-wpc-menu-overlay]");
1702                
1703                if (!toggles.length || !panel) return;
1704                
1705                console.log("WPC Mobile Menu: Found preserved mobile menu with data attributes");
1706                
1707                function openMenu() {
1708                    panel.classList.add("is-open");
1709                    if (overlay) overlay.classList.add("is-open");
1710                    document.body.classList.add("wpc-menu-open");
1711                    toggles.forEach(function(t) { t.setAttribute("aria-expanded", "true"); });
1712                }
1713                
1714                function closeMenu() {
1715                    panel.classList.remove("is-open");
1716                    if (overlay) overlay.classList.remove("is-open");
1717                    document.body.classList.remove("wpc-menu-open");
1718                    toggles.forEach(function(t) { t.setAttribute("aria-expanded", "false"); });
1719                }
1720                
1721                function toggleMenu() {
1722                    if (panel.classList.contains("is-open")) {
1723                        closeMenu();
1724                    } else {
1725                        openMenu();
1726                    }
1727                }
1728                
1729                // Toggle button click
1730                toggles.forEach(function(toggle) {
1731                    toggle.addEventListener("click", function(e) {
1732                        e.preventDefault();
1733                        e.stopPropagation();
1734                        toggleMenu();
1735                    });
1736                });
1737                
1738                // Close on overlay click
1739                if (overlay) {
1740                    overlay.addEventListener("click", closeMenu);
1741                }
1742                
1743                // Close on ESC key
1744                document.addEventListener("keydown", function(e) {
1745                    if (e.key === "Escape" && panel.classList.contains("is-open")) {
1746                        closeMenu();
1747                    }
1748                });
1749                
1750                // Close on link click (important for mobile UX - menu closes after navigation)
1751                panel.querySelectorAll("a").forEach(function(link) {
1752                    link.addEventListener("click", function() {
1753                        // Small delay to allow navigation to start
1754                        setTimeout(closeMenu, 100);
1755                    });
1756                });
1757            })();
1758            } catch(e) { console.warn("[WPConvert] Mobile menu toggle error:", e); }
1759            
1760            try { // [WPConvert: URL Tab Switching]
1761            // URL-based tab switching (for services, contact pages with ?tab= parameter)
1762            // GENERIC: Works with any color scheme by detecting active/inactive classes dynamically
1763            var urlParams = new URLSearchParams(window.location.search);
1764            var activeTab = urlParams.get("tab");
1765            if (activeTab) {
1766                console.log("URL has tab parameter:", activeTab);
1767                
1768                // Find tab container (group of sibling buttons)
1769                var $tabGroups = $("button").parent().filter(function() {
1770                    return $(this).find("button").length >= 2;
1771                });
1772                
1773                $tabGroups.each(function() {
1774                    var $container = $(this);
1775                    var $buttons = $container.find("button");
1776                    
1777                    // Detect active/inactive classes from existing buttons
1778                    // First button is typically "active" by default, others are "inactive"
1779                    var $firstBtn = $buttons.first();
1780                    var $otherBtn = $buttons.eq(1);
1781                    
1782                    if ($firstBtn.length && $otherBtn.length) {
1783                        var activeClasses = $firstBtn.attr("class") || "";
1784                        var inactiveClasses = $otherBtn.attr("class") || "";
1785                        
1786                        // Find and activate the matching tab
1787                        $buttons.each(function() {
1788                            var $tabBtn = $(this);
1789                            var btnText = $tabBtn.text().toLowerCase().trim().replace(/\s+/g, "-");
1790                            var tabMatch = btnText.includes(activeTab.toLowerCase()) || 
1791                                           btnText.replace(/[^a-z0-9]/g, "").includes(activeTab.toLowerCase().replace(/[^a-z0-9]/g, "")) ||
1792                                           $tabBtn.attr("data-tab") === activeTab ||
1793                                           $tabBtn.attr("id") === "tab-" + activeTab;
1794                            
1795                            if (tabMatch) {
1796                                console.log("Found matching tab button:", btnText);
1797                                
1798                                // Swap classes: make all buttons inactive, then make matched one active
1799                                $buttons.attr("class", inactiveClasses);
1800                                $tabBtn.attr("class", activeClasses);
1801                                
1802                                // Trigger click for any JavaScript handlers
1803                                $tabBtn.trigger("click");
1804                            }
1805                        });
1806                    }
1807                });
1808            }
1809            } catch(e) { console.warn("[WPConvert] URL tab switching error:", e); }
1810            
1811            try { // [WPConvert: Universal Carousel]
1812            // Universal Carousel/Slider Functionality
1813            // Detects and enables carousels/sliders (testimonials, product sliders, etc.)
1814            
1815            // Helper function to initialize carousel behavior
1816            function initializeCarousel($carousel, $prevBtn, $nextBtn) {
1817                // EC-GALLERY-FLASH-001: Never carousel-ify a stacked gallery section.
1818                if ($carousel.closest("[data-wpconvert-stacked-gallery-section]").length ||
1819                    $carousel.find("[data-wpconvert-stacked-gallery]").length) {
1820                    return;
1821                }
1822                var $track = null;
1823                var $items = null;
1824                
1825                $track = $carousel.find('[class*="grid"]').first();
1826                if ($track.length) {
1827                    $items = $track.children().filter(function() {
1828                        var $el = $(this);
1829                        // Include items that have testimonial/review content (quotes, names, ratings)
1830                        var hasContent = $el.find('p, h1, h2, h3, h4, h5, h6, [class*="quote"], [class*="testimonial"], [class*="review"]').length > 0;
1831                        var isNotButton = !$el.is("button") && !$el.find("button[aria-label]").length;
1832                        return isNotButton && hasContent;
1833                    });
1834                    console.log("Found " + $items.length + " carousel items in grid");
1835                }
1836                
1837                if (!$items || $items.length <= 1) {
1838                    $track = $carousel.find('[class*="track"], [class*="slider-inner"], [class*="items"]').first();
1839                    if ($track.length) {
1840                        $items = $track.children().not("button");
1841                    }
1842                }
1843                
1844                if (!$items || $items.length <= 1) {
1845                    $items = $carousel.children().not("button").filter(function() {
1846                        var $el = $(this);
1847                        return !$el.find("button[aria-label]").length;
1848                    });
1849                }
1850                
1851                if (!$items || $items.length === 0) {
1852                    return;
1853                }
1854                
1855                var totalItems = $items.length;
1856                
1857                function parseColumns($el) {
1858                    var configs = { base: null, sm: null, md: null, lg: null, xl: null };
1859                    var classString = ($el.attr("class") || "") + " " + ($el.parent().attr("class") || "");
1860                    classString.split(/\s+/).forEach(function(cls) {
1861                        var match = cls.match(/^(sm:|md:|lg:|xl:)?grid-cols-(\d+)/);
1862                        if (match) {
1863                            var bp = match[1] ? match[1].replace(":", "") : "base";
1864                            configs[bp] = parseInt(match[2], 10);
1865                        }
1866                    });
1867                    return configs;
1868                }
1869                
1870                function columnsForWidth(configs, width) {
1871                    if (width >= 1280 && configs.xl) return configs.xl;
1872                    if (width >= 1024 && configs.lg) return configs.lg;
1873                    if (width >= 768 && configs.md) return configs.md;
1874                    if (width >= 640 && configs.sm) return configs.sm;
1875                    return configs.base || configs.lg || configs.md || 1;
1876                }
1877                
1878                var columnConfig = parseColumns($track.length ? $track : $carousel);
1879                var itemsPerView = Math.max(1, Math.min(totalItems, columnsForWidth(columnConfig, window.innerWidth)));
1880                var currentIndex = 0;
1881                
1882                function updateDots(activeIndex) {
1883                    var $dots = $carousel.find('[class*="dot"], [class*="pagination"] button, button[role="tab"]');
1884                    if ($dots.length === 0) return;
1885                    $dots.removeClass("active bg-arta-red w-6");
1886                    $dots.eq(activeIndex % $dots.length).addClass("active bg-arta-red w-6");
1887                }
1888                
1889                function showCurrentItems() {
1890                    $items.hide();
1891                    for (var i = 0; i < itemsPerView; i++) {
1892                        var idx = (currentIndex + i) % totalItems;
1893                        $items.eq(idx).fadeIn(200);
1894                    }
1895                    updateDots(currentIndex);
1896                }
1897                
1898                function recalcItemsPerView() {
1899                    var newView = Math.max(1, Math.min(totalItems, columnsForWidth(columnConfig, window.innerWidth)));
1900                    if (newView !== itemsPerView) {
1901                        itemsPerView = newView;
1902                        currentIndex = 0;
1903                        showCurrentItems();
1904                    }
1905                }
1906                
1907                // EC-CAROUSEL-002 FIX: Remove stale disabled attrs from React initial state
1908                $prevBtn.removeAttr("disabled").css("opacity", "");
1909                $nextBtn.removeAttr("disabled").css("opacity", "");
1910
1911                var autoAdvanceInterval = null;
1912                
1913                if (totalItems > itemsPerView) {
1914                    showCurrentItems();
1915                    autoAdvanceInterval = setInterval(function() {
1916                        currentIndex = (currentIndex + itemsPerView) % totalItems;
1917                        showCurrentItems();
1918                    }, 6000);
1919                } else {
1920                    $items.show();
1921                    updateDots(0);
1922                    // When all items fit, disable buttons since there's nothing to cycle
1923                    $prevBtn.prop("disabled", true).css("opacity", 0.3);
1924                    $nextBtn.prop("disabled", true).css("opacity", 0.3);
1925                    console.log("All carousel items fit on screen, buttons disabled");
1926                }
1927                
1928                $items.each(function(index) {
1929                    $(this).attr('data-original-index', index);
1930                });
1931
1932                $prevBtn.off("click.wpconvertCarousel").on("click.wpconvertCarousel", function(e) {
1933                    e.preventDefault();
1934                    e.stopPropagation();
1935                    if (totalItems <= itemsPerView) return;
1936                    if (autoAdvanceInterval) clearInterval(autoAdvanceInterval);
1937                    
1938                    var $last = $items.last();
1939                    $items = $items.not($last);
1940                    
1941                    $last.hide().prependTo($track).fadeIn(300);
1942                    $items = $last.add($items);
1943                    
1944                    var currentOriginalIndex = parseInt($items.first().attr('data-original-index') || 0);
1945                    updateDots(currentOriginalIndex);
1946                    
1947                    if (totalItems >
1947 itemsPerView) {
1948                        startAutoAdvance();
1949                    }
1950                });
1951                
1952                $nextBtn.off("click.wpconvertCarousel").on("click.wpconvertCarousel", function(e) {
1953                    e.preventDefault();
1954                    e.stopPropagation();
1955                    if (totalItems <= itemsPerView) return;
1956                    if (autoAdvanceInterval) clearInterval(autoAdvanceInterval);
1957                    
1958                    var $first = $items.first();
1959                    $items = $items.not($first);
1960                    
1961                    $first.hide().appendTo($track).fadeIn(300);
1962                    $items = $items.add($first);
1963                    
1964                    var currentOriginalIndex = parseInt($items.first().attr('data-original-index') || 0);
1965                    updateDots(currentOriginalIndex);
1966                    
1967                    if (totalItems > itemsPerView) {
1968                        startAutoAdvance();
1969                    }
1970                });
1971                
1972                function startAutoAdvance() {
1973                    autoAdvanceInterval = setInterval(function() {
1974                        $nextBtn.trigger('click');
1975                    }, 6000);
1976                }
1977                
1978                if (totalItems > itemsPerView) {
1979                    startAutoAdvance();
1980                }
1981                
1982                var $dots = $carousel.find('[class*="dot"], [class*="pagination"] button, button[role="tab"]');
1983                if ($dots.length > 0) {
1984                    $dots.on("click", function(e) {
1985                        e.preventDefault();
1986                        clearInterval(autoAdvanceInterval);
1987                        currentIndex = $(this).index();
1988                        showCurrentItems();
1989                    });
1990                }
1991                
1992                var resizeTimer = null;
1993                $(window).on("resize.wpconvertCarousel", function() {
1994                    clearTimeout(resizeTimer);
1995                    resizeTimer = setTimeout(function() {
1996                        recalcItemsPerView();
1997                    }, 200);
1998                });
1999                
2000                // CRITICAL: Add drag/swipe support for touch and mouse
2001                var isDragging = false;
2002                var startX = 0;
2003                var startY = 0;
2004                var scrollLeft = 0;
2005                var scrollTop = 0;
2006                
2007                // Mouse drag support
2008                $track.on("mousedown.wpconvertCarousel", function(e) {
2009                    isDragging = true;
2010                    startX = e.pageX - $(this).offset().left;
2011                    startY = e.pageY - $(this).offset().top;
2012                    scrollLeft = $(this).scrollLeft();
2013                    scrollTop = $(this).scrollTop();
2014                    $(this).css("cursor", "grabbing");
2015                });
2016                
2017                $(document).on("mousemove.wpconvertCarousel", function(e) {
2018                    if (!isDragging) return;
2019                    e.preventDefault();
2020                    var x = e.pageX - $track.offset().left;
2021                    var y = e.pageY - $track.offset().top;
2022                    var walkX = (x - startX) * 2;
2023                    var walkY = (y - startY) * 2;
2024                    
2025                    // If horizontal drag is greater than vertical, it's a swipe
2026                    if (Math.abs(walkX) > Math.abs(walkY) && Math.abs(walkX) > 50) {
2027                        if (walkX > 0) {
2028                            // Swipe right = previous
2029                            $prevBtn.trigger("click");
2030                        } else {
2031                            // Swipe left = next
2032                            $nextBtn.trigger("click");
2033                        }
2034                        isDragging = false;
2035                        $track.css("cursor", "");
2036                    }
2037                });
2038                
2039                $(document).on("mouseup.wpconvertCarousel", function() {
2040                    isDragging = false;
2041                    $track.css("cursor", "");
2042                });
2043                
2044                // Touch swipe support
2045                var touchStartX = 0;
2046                var touchStartY = 0;
2047                
2048                $track.on("touchstart.wpconvertCarousel", function(e) {
2049                    var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
2050                    touchStartX = touch.clientX;
2051                    touchStartY = touch.clientY;
2052                });
2053                
2054                $track.on("touchend.wpconvertCarousel", function(e) {
2055                    var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
2056                    if (!touch) {
2057                        touch = e.originalEvent.changedTouches[0];
2058                    }
2059                    if (!touch) return;
2060                    
2061                    var touchEndX = touch.clientX;
2062                    var touchEndY = touch.clientY;
2063                    var diffX = touchStartX - touchEndX;
2064                    var diffY = touchStartY - touchEndY;
2065                    
2066                    // If horizontal swipe is greater than vertical, it's a swipe
2067                    if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) {
2068                        if (diffX > 0) {
2069                            // Swipe left = next
2070                            $nextBtn.trigger("click");
2071                        } else {
2072                            // Swipe right = previous
2073                            $prevBtn.trigger("click");
2074                        }
2075                    }
2076                });
2077            }
2078            
2079            // Method 1: Find all buttons with Previous/Next aria-labels and trace back to their carousel container
2080            $("button[aria-label*='Previous'], button[aria-label*='Prev'], button[aria-label*='previous']").each(function() {
2081                var $prevBtn = $(this);
2082                if ($prevBtn.data("carousel-processed")) return;
2083                // EC-CAROUSEL-004 + EC-TESTIMONIAL-004b: Skip inner testimonial card only (EC-CAROUSEL-001)
2084                if (wpcInsideJsonTestimonialCard($prevBtn)) return;
2085                // EC-CAROUSEL-016: Skip stacked-image / video galleries (crossfade, NOT horizontal carousels).
2086                // Buttons like "Previous photo", "Previous video" live inside a container where 2+
2087                // children are absolutely positioned at inset-0. Wrapping the parent grid in a flex
2088                // track causes blank flashes and layout glitches.
2089                var prevLabel = ($prevBtn.attr("aria-label") || "").toLowerCase();
2090                if (/\bphoto\b|\bvideo\b|\bimage\b|\bpicture\b|\bgallery\b|\bslide\b/.test(prevLabel)) {
2091                    var $galBox = $prevBtn.parent();
2092                    var stackedImgs = $galBox.find("img.absolute, img[class*='absolute'][class*='inset'], video.absolute, video[class*='absolute'][class*='inset'], [class*='absolute'][class*='inset-0']");
2093                    if (stackedImgs.length >= 2) {
2094                        $prevBtn.data("carousel-processed", true);
2095                        var $sibNext = $galBox.find("button[aria-label*='Next'], button[aria-label*='next']").not($prevBtn);
2096                        if ($sibNext.length) $sibNext.data("carousel-processed", true);
2097                        // EC-CAROUSEL-016f: Wire a proper opacity-cycle handler instead
2098                        // of leaving dead buttons. Crossfade between stacked images.
2099                        (function($box, $prev, $next) {
2100                            var $imgs = $box.find("img.absolute, img[class*='absolute'][class*='inset-0'], video.absolute, video[class*='absolute'][class*='inset-0']");
2101                            if ($imgs.length < 2) return;
2102                            var idx = 0;
2103                            $imgs.css({ transition: "opacity 0.5s ease" });
2104                            $imgs.each(function(i) { $(this).css("opacity", i === 0 ? 1 : 0); });
2105                            var $dots = $box.find("button[aria-label*='View']");
2106                            function show(n) {
2107                                idx = ((n % $imgs.length) + $imgs.length) % $imgs.length;
2108                                $imgs.each(function(i) { $(this).css("opacity", i === idx ? 1 : 0); });
2109                                if ($dots.length) $dots.each(function(i) {
2110                                    var $d = $(this);
2111                                    $d.css("opacity", i === idx ? 1 : 0.5);
2112                                    if (i === idx) $d.css("width", "1.25rem"); else $d.css("width", "0.5rem");
2113                                });
2114                            }
2115                            show(0);
2116                            $prev.off("click.wpcGallery").on("click.wpcGallery", function(e) { e.preventDefault(); e.stopPropagation(); clearInterval(timer); show(idx - 1); startTimer(); });
2117                            $next.off("click.wpcGallery").on("click.wpcGallery", function(e) { e.preventDefault(); e.stopPropagation(); clearInterval(timer); show(idx + 1); startTimer(); });
2118                            $dots.each(function(i) { $(this).data("carousel-processed", true).css("cursor","pointer").off("click.wpcGallery").on("click.wpcGallery", function(e) { e.preventDefault(); clearInterval(timer); show(i); startTimer(); }); });
2119                            var timer = setInterval(function() { show(idx + 1); }, 5000);
2120                            function startTimer() { clearInterval(timer); timer = setInterval(function() { show(idx + 1); }, 5000); }
2121                            $box.data("carousel-processed", true).on("mouseenter", function() { clearInterval(timer); }).on("mouseleave", function() { startTimer(); });
2122                            // EC-CAROUSEL-016h: Mark the ENTIRE enclosing section as carousel-processed.
2123                            // The stacked gallery lives inside a grid column. Without this guard,
2124                            // later carousel methods (3/5/6) can find other buttons or overflow-hidden
2125                            // elements in the same section and call initializeOverflowCarousel on a
2126                            // parent container — turning the hero grid itself into a carousel that
2127                            // scrolls the text and gallery columns as separate "slides."
2128                            var $galSection = $box.closest("section");
2129                            if ($galSection.length) {
2130                                $galSection.find("button, .overflow-hidden, [style*='overflow']").data("carousel-processed", true);
2131                            }
2132                            console.log("[EC-CAROUSEL-016f] Stacked gallery opacity-cycle: " + $imgs.length + " images");
2133                        })($galBox, $prevBtn, $sibNext.length ? $sibNext.first() : $());
2134                        return;
2135                    }
2136                    // EC-CAROUSEL-016d: Also skip horizontal thumbnail scroll controls.
2137                    // Buttons labelled "Previous video" / "Next photo" sit beside an
2138                    // overflow-x-auto scrollable strip of thumbnails — they are scroll
2139                    // controls, not carousel prev/next buttons.
2140                    var $scrollSibling = $galBox.find("[class*='overflow-x-auto'], [class*='overflow-auto'], [class*='scrollbar-hide']");
2141                    if ($scrollSibling.length && $scrollSibling.find("button, a").length >= 2) {
2142                        $prevBtn.data("carousel-processed", true);
2143                        var $sibNext2 = $galBox.find("button[aria-label*='Next'], button[aria-label*='next']").not($prevBtn);
2144                        if ($sibNext2.length) $sibNext2.data("carousel-processed", true);
2145                        console.log("[EC-CAROUSEL-016d] Skipped thumbnail scroll strip (aria-label: " + prevLabel + ")");
2146                        return;
2147                    }
2148                }
2149                
2150                // Find the common parent container (usually a div with "relative" class that contains both buttons and grid)
2151                var $parentContainer = $prevBtn.parent();
2152                
2153                // Find the corresponding Next button - check siblings first (most common case)
2154                var $nextBtn = $prevBtn.siblings("button[aria-label*='Next'], button[aria-label*='next'], button[aria-label*='testimonial']");
2155                if (!$nextBtn.length) {
2156                    // If not siblings, search within the parent container
2157                    $nextBtn = $parentContainer.find("button[aria-label*='Next'], button[aria-label*='next']").not($prevBtn);
2158                }
2159                
2160                if (!$nextBtn.length) return; // No next button found
2161                
2162                // Find the grid/carousel container - look for grid within the parent container
2163                // CRITICAL: The carousel container should be the parent that contains BOTH buttons AND grid
2164                var $gridContainer = $parentContainer.find('[class*="grid"], [class*="carousel"], [class*="slider"]').first();
2165                
2166                // Use the parent container as carousel if it contains a grid (buttons are siblings of grid)
2167                // Otherwise, try to find a section parent
2168                var $carousel = $gridContainer.length ? $parentContainer : $prevBtn.closest('section, [class*="carousel"], [class*="slider"], [class*="testimonial"], [class*="client"]');
2169                
2170                // Final fallback: use parent container if nothing else found
2171                if (!$carousel.length) {
2172                    $carousel = $parentContainer;
2173                }
2174                
2175                if ($nextBtn.length && $carousel.length) {
2176                    console.log("Found carousel with prev/next buttons:", {
2177                        carousel: $carousel.attr("class"),
2178                        prevBtn: $prevBtn.attr("aria-label"),
2179                        nextBtn: $nextBtn.attr("aria-label"),
2180                        hasGrid: $gridContainer.length > 0,
2181                        carouselLength: $carousel.length,
2182                        nextBtnLength: $nextBtn.length
2183                    });
2184                    
2185                    try {
2186                        $prevBtn.data("carousel-processed", true);
2187                        $nextBtn.data("carousel-processed", true);
2188                        initializeCarousel($carousel.first(), $prevBtn.first(), $nextBtn.first());
2189                        console.log("✅ Carousel initialized successfully");
2190                    } catch (error) {
2191                        console.error("❌ Error initializing carousel:", error);
2192                    }
2193                } else {
2194                    console.log("⚠️ Carousel not initialized - missing requirements:", {
2195                        hasNextBtn: $nextBtn.length > 0,
2196                        hasCarousel: $carousel.length > 0
2197                    });
2198                }
2199            });
2200            
2201            // Method 2: Detect by class name patterns (fallback)
2202            var $carousels = $('[class*="carousel"], [class*="slider"], [class*="swiper"], [data-carousel]');
2203            console.log("Found " + $carousels.length + " potential carousels by class");
2204            $carousels.each(function() {
2205                var $carousel = $(this);
2206                if ($carousel.data("carousel-processed")) return;
2207                if (wpcInsideJsonTestimonialCard($carousel)) return;
2208                
2209                var $prevBtn = $carousel.find('button[aria-label*="Previous"], button[aria-label*="Prev"], [class*="prev"], [class*="left"]');
2210                var $nextBtn = $carousel.find('button[aria-label*="Next"], [class*="next"], [class*="right"]');
2211                
2212                if ($prevBtn.length && $nextBtn.length) {
2213                    console.log("Found carousel by class pattern:", $carousel.attr("class"));
2214                    $carousel.data("carousel-processed", true);
2215                    initializeCarousel($carousel, $prevBtn.first(), $nextBtn.first());
2216                }
2217            });
2218            } catch(e) { console.warn("[WPConvert] Universal carousel error:", e); }
2219            
2220            try { // [WPConvert: EC-CAROUSEL-002 SVG Arrow + Overflow Carousel Detection]
2221            // EC-CAROUSEL-002: Enhanced carousel detection for AI-builder patterns
2222            // Covers: Embla/ShadCN (overflow-hidden+flex), Lucide/Heroicon SVG buttons,
2223            // sr-only text, and generic scrollable card containers.
2224
2225            function initializeOverflowCarousel($region, $viewport, $track, $slides, $prevBtn, $nextBtn) {
2226                if (!$slides.length) return;
2227                // EC-CAROUSEL-016h: Never convert a section that contains a stacked gallery
2228                // into an overflow carousel — the gallery already has its own 016f handler.
2229                if ($region.closest("[data-wpconvert-stacked-gallery-section]").length ||
2230                    $viewport.closest("[data-wpconvert-stacked-gallery-section]").length) {
2231                    console.log("[EC-CAROUSEL-016h] Skipped initializeOverflowCarousel inside stacked gallery section");
2232                    return;
2233                }
2234                var currentSlide = 0;
2235
2236                // Remove stale inline onclick handlers and take full control
2237                [$prevBtn, $nextBtn].forEach(function($b) {
2238                    var oc = $b.attr("onclick") || "";
2239                    if (oc) {
2240                        var fnName = oc.split("(")[0].replace(/[^a-zA-Z0-9_$]/g, "");
2241                        if (fnName && typeof window[fnName] === "function") {
2242                            $b[0].onclick = null;
2243                        }
2244                    }
2245                    $b.removeAttr("onclick");
2246                });
2247
2248                // EC-CAROUSEL-003: Clear stale Embla/ShadCN inline transforms
2249                // Embla loop mode applies translate3d() on both the track AND individual slides
2250                // at runtime. Puppeteer captures these mid-animation values, positioning slides
2251                // off-screen on initial load (blank carousel). We must clear them before init.
2252                $track.css("transform", "");
2253                $slides.css("transform", "");
2254
2255                // Ensure viewport has overflow hidden (might be set via CSS class or style)
2256                if ($viewport.css("overflow") !== "hidden") {
2257                    $viewport.css("overflow", "hidden");
2258                }
2259                // Ensure track has flex display for translateX to work
2260                if ($track.css("display") !== "flex" && $track.css("display") !== "inline-flex") {
2261                    $track.css({ display: "flex", flexWrap: "nowrap" });
2262                }
2263
2264                // Detect dot/pagination elements within the region
2265                var $dots = $region.find('[class*="dot"], [class*="pagination"] > *, [class*="progress"] > *').filter(function() {
2266                    var $d = $(this);
2267                    if ($d.find("p, h1, h2, h3, img").length) return false;
2268                    if ($d.children().length > 3) return false;
2269                    return true;
2270                });
2271
2272                function getMetrics() {
2273                    var vw = $viewport.outerWidth();
2274                    var sw = $slides.first().outerWidth(true);
2275                    if (!sw) sw = vw;
2276                    // Account for flex gap (not included in outerWidth)
2277                    var gap = parseFloat($track.css("gap") || $track.css("column-gap")) || 0;
2278                    var step = sw + gap;
2279                    var perView = Math.max(1, Math.round(vw / step));
2280                    var maxIdx = Math.max(0, $slides.length - perView);
2281                    return { slideWidth: step, perView: perView, maxIdx: maxIdx };
2282                }
2283
2284                // EC-CAROUSEL-014: Images/layout can report 0 width for one frame after paint;
2285                // translating with bad metrics blanks the hero or shifts the wrong flex row.
2286                var wpcMetricsRetries = 0;
2287                function goTo(idx) {
2288                    var m = getMetrics();
2289                    var bad = !m.slideWidth || m.slideWidth < 2 || !isFinite(m.slideWidth) || !isFinite(m.maxIdx);
2290                    if (bad && wpcMetricsRetries < 32) {
2291                        wpcMetricsRetries++;
2292                        window.setTimeout(function() { goTo(idx); }, 40);
2293                        return;
2294                    }
2295                    wpcMetricsRetries = 0;
2296                    if (!m.slideWidth || m.slideWidth < 2 || !isFinite(m.slideWidth)) {
2297                        console.warn("[EC-CAROUSEL-014] Overflow carousel: slide metrics invalid after retries, skip transform");
2298                        return;
2299                    }
2300                    currentSlide = Math.max(0, Math.min(idx, m.maxIdx));
2301                    var offset = -(currentSlide * m.slideWidth);
2302                    $track.css({ transform: "translateX(" + offset + "px)", transition: "transform 0.5s cubic-bezier(0.25,0.46,0.45,0.94)" });
2303                    $prevBtn.prop("disabled", currentSlide <= 0).css("opacity", currentSlide <= 0 ? 0.3 : 1);
2304                    $nextBtn.prop("disabled", currentSlide >= m.maxIdx).css("opacity", currentSlide >= m.maxIdx ? 0.3 : 1);
2305                    // Update dots
2306                    if ($dots.length) {
2307                        $dots.removeClass("active");
2308                        var dotIdx = Math.min(currentSlide, $dots.length - 1);
2309                        $dots.eq(dotIdx).addClass("active");
2310                    }
2311                }
2312
2313                $prevBtn.off("click.wpcOverflow").on("click.wpcOverflow", function(e) {
2314                    e.preventDefault(); e.stopPropagation();
2315                    goTo(currentSlide - 1);
2316                });
2317                $nextBtn.off("click.wpcOverflow").on("click.wpcOverflow", function(e) {
2318                    e.preventDefault(); e.stopPropagation();
2319                    goTo(currentSlide + 1);
2320                });
2321
2322                // Dot click navigation
2323                if ($dots.length) {
2324                    $dots.css("cursor", "pointer").on("click.wpcOverflow", function(e) {
2325                        e.preventDefault();
2326                        goTo($(this).index());
2327                    });
2328                }
2329
2330                // Touch/swipe support
2331                var touchX = 0;
2332                $viewport.on("touchstart.wpcOverflow", function(e) {
2333                    touchX = (e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]).clientX;
2334                });
2335                $viewport.on("touchend.wpcOverflow", function(e) {
2336                    var t = e.originalEvent.changedTouches[0]; if (!t) return;
2337                    var diff = touchX - t.clientX;
2338                    if (Math.abs(diff) > 50) goTo(currentSlide + (diff > 0 ? 1 : -1));
2339                });
2340
2341                $(window).on("resize.wpcOverflow", function() { goTo(currentSlide); });
2342                goTo(0);
2343                console.log("[EC-CAROUSEL-002] ✅ Overflow carousel: " + $slides.length + " slides, dots: " + $dots.length);
2344            }
2345
2346            // Detect left/right arrow icon inside a button
2347            function classifyArrowBtn($btn) {
2348                var iconCls = "";
2349                $btn.find("svg, i").each(function() { iconCls += " " + ($(this).attr("class") || ""); });
2350                iconCls = iconCls.toLowerCase();
2351                var srText = ($btn.find(".sr-only").text() || "").toLowerCase();
2352                var btnText = ($btn.text() || "").replace(/\s+/g, "").toLowerCase();
2353
2354                var leftPats = ["chevron-left", "arrow-left", "angle-left", "caret-left", "move-left"];
2355                var rightPats = ["chevron-right", "arrow-right", "angle-right", "caret-right", "move-right"];
2356
2357                for (var i = 0; i < leftPats.length; i++) {
2358                    if (iconCls.indexOf(leftPats[i]) >= 0) return "prev";
2359                }
2360                for (var i = 0; i < rightPats.length; i++) {
2361                    if (iconCls.indexOf(rightPats[i]) >= 0) return "next";
2362                }
2363                if (/\bprev|previous\b/.test(srText)) return "prev";
2364                if (/\bnext\b/.test(srText)) return "next";
2365                if (/^[<‹←]$/.test(btnText)) return "prev";
2366                if (/^[>›→]$/.test(btnText)) return "next";
2367                // Check inline onclick for carousel function calls (e.g., slideCards(-1), slideCards(1))
2368                var oc = ($btn.attr("onclick") || "");
2369                if (/slide|scroll|carousel|rotate/i.test(oc)) {
2370                    if (/\(\s*-/.test(oc) || /prev|back|left/i.test(oc)) return "prev";
2371                    if (/\(\s*1\b/.test(oc) || /next|forward|right/i.test(oc)) return "next";
2372                }
2373                return null;
2374            }
2375
2376            // EC-CAROUSEL-008: Prefer direct-child overflow viewport (Embla/ShadCN). A plain .find() can match
2377            // slide-inner .overflow-hidden before the real track wrapper in some DOM orders.
2378            // EC-CAROUSEL-014: The first .overflow-hidden in a wide section may be a 2-col grid shell;
2379            // translating that track shifts copy + images together (blank or "flipped" layout on slide change).
2380            function wpcResolveCarouselViewport($root) {
2381                function scoreViewport($vp) {
2382                    var cls = $vp.attr("class") || "";
2383                    if (/\bgrid\b/.test(cls) && /grid-cols-/.test(cls)) return -100;
2384                    var $track = $vp.children().first();
2385                    if (!$track.length) return 0;
2386                    var trackCls = $track.attr("class") || "";
2387                    var disp = ($track.css("display") || "").toLowerCase();
2388                    var flexLike = trackCls.indexOf("flex") >= 0 || disp.indexOf("flex") >= 0;
2389                    var $kids = $track.children();
2390                    if ($kids.length < 2) return flexLike ? 6 : 0;
2391                    // EC-CAROUSEL-015: Two-column hero (image track + prose) — not a horizontal slide strip;
2392                    // translating this viewport blanks the image column or shifts copy with the flex row.
2393                    if ($kids.length === 2) {
2394                        var withImg = 0, textHeavy = 0;
2395                        $kids.each(function() {
2396                            var imgs = $(this).find("img").length;
2397                            if (imgs > 0) withImg++;
2398                            var tl = $(this).text().trim().length;
2399                            if (imgs === 0 && tl > 100) textHeavy++;
2400                        });
2401                        if (withImg === 1 && textHeavy >= 1) return -80;
2402                    }
2403                    var ariaSlides = $track.children('[aria-roledescription="slide"]').length;
2404                    var slideLike = 0;
2405                    $kids.each(function() {
2406                        var c = $(this).attr("class") || "";
2407                        if (c.indexOf("shrink-0") >= 0 || c.indexOf("min-w-") >= 0 || c.indexOf("basis-") >= 0) slideLike++;
2408                        if ($(this).find("img").length) slideLike++;
2409                    });
2410                    var score = 0;
2411                    if (flexLike) score += 20;
2412                    if (ariaSlides >= 2) score += 45;
2413                    else score += 12;
2414                    if (slideLike >= 2) score += 28;
2415                    if ($vp.closest('[role="region"][aria-roledescription="carousel"]').length) score += 32;
2416                    return score;
2417                }
2418                var $all = $root.find(".overflow-hidden");
2419                var best = null, bestS = -9999;
2420                if ($all.length) {
2421                    $all.each(function() {
2422                        var $cand = $(this);
2423                        var s = scoreViewport($cand);
2424                        if (s > bestS) { bestS = s; best = $cand; }
2425                    });
2426                }
2427                if (best && bestS >= 20) {
2428                    console.log("[EC-CAROUSEL-014] wpcResolveCarouselViewport picked score=" + bestS);
2429                    return best;
2430                }
2431                var $vp = $root.children(".overflow-hidden").first();
2432                if ($vp.length) return $vp;
2433                $vp = $root.find("> .overflow-hidden").first();
2434                if ($vp.length) return $vp;
2435                return $root.find(".overflow-hidden").first();
2436            }
2437
2438            // Method 3: Detect by SVG icon class on buttons (lucide, heroicons, FA)
2439            // EC-CAROUSEL-013: Also match <i> with arrow icon classes (Remix Icon, etc.)
2440            var svgProcessed = [];
2441            $("button:has(svg), button:has(i[class*='fa-']), button:has(i[class*='arrow'])").each(function() {
2442                var $btn = $(this);
2443                if ($btn.data("carousel-processed")) return;
2444                if ($btn.closest("[data-wpconvert-stacked-gallery]").length) { $btn.data("carousel-processed", true); return; }
2445                if (wpcInsideJsonTestimonialCard($btn)) return;
2446                // EC-CAROUSEL-016e: Skip Radix/HeadlessUI accordion/collapsible triggers.
2447                // These have data-state="open"/"closed" + aria-controls and use rotated
2448                // chevrons that classifyArrowBtn would wrongly classify as carousel arrows.
2449                if ($btn.attr("data-state") && $btn.attr("aria-controls")) return;
2450                var role = classifyArrowBtn($btn);
2451                if (role !== "prev") return; // start from prev buttons
2452
2453                // Find matching next button: sibling buttons or nearby buttons in same parent
2454                var $nextBtn = null;
2455                $btn.parent().children("button").each(function() {
2456                    if ($(this).is($btn)) return;
2457                    if (classifyArrowBtn($(this)) === "next") { $nextBtn = $(this); return false; }
2458                });
2459
2460                if (!$nextBtn || !$nextBtn.length) {
2461                    // Try grandparent scope (buttons may be in separate wrapper divs)
2462                    $btn.parent().parent().find("button").each(function() {
2463                        if ($(this).is($btn)) return;
2464                        if ($(this).data("carousel-processed")) return;
2465                        if (classifyArrowBtn($(this)) === "next") { $nextBtn = $(this); return false; }
2466                    });
2467                }
2468                if (!$nextBtn || !$nextBtn.length) return;
2469
2470                // EC-CAROUSEL-016b: Skip stacked-image/video galleries in Method 3 too.
2471                // The button pair lives inside a container where 2+ children overlap at inset-0
2472                // (opacity-crossfade). Treating it as a horizontal carousel causes blank flashes.
2473                var $galParent = $btn.parent();
2474                var stackedKids = $galParent.find("[class*='absolute'][class*='inset-0']");
2475                if (stackedKids.length >= 2 && $galParent.find("img, video").length >= 2) {
2476                    $btn.data("carousel-processed", true);
2477                    $nextBtn.data("carousel-processed", true);
2478                    svgProcessed.push("skip-stacked-gallery");
2479                    console.log("[EC-CAROUSEL-016b] Method 3 skipped stacked gallery (" + stackedKids.length + " layers)");
2480                    return;
2481                }
2482
2483                // EC-CAROUSEL-016c: Skip category filter bar sections. A sticky/scrollable
2484                // row of <button> pills with chevron scroll arrows is NOT a carousel.
2485                // Detect: closest section has overflow-x-auto child with 3+ buttons and no card grid.
2486                var $closestSec = $btn.closest("section");
2487                if ($closestSec.length) {
2488                    var $scrollRow = $closestSec.find("[class*='overflow-x-auto'], [class*='overflow-auto'], [class*='scrollbar-hide']");
2489                    if ($scrollRow.length && $scrollRow.find("button").length >= 3) {
2490                        var sectionHasCardGrid = $closestSec.find("[class*='grid-cols']").length > 0;
2491                        if (!sectionHasCardGrid) {
2492                            $btn.data("carousel-processed", true);
2493                            $nextBtn.data("carousel-processed", true);
2494                            svgProcessed.push("skip-filter-bar");
2495                            console.log("[EC-CAROUSEL-016c] Method 3 skipped category filter bar section");
2496                            return;
2497                        }
2498                    }
2499                }
2500
2501                // Find the carousel container — walk up to section or a container with cards
2502                var $container = $btn.closest('[role="region"][aria-roledescription="carousel"]');
2503                if (!$container.length) $container = $btn.closest("section");
2504                if (!$container.length) $container = $btn.parent().parent().parent();
2505                if ($container.data("carousel-processed")) return;
2506
2507                // EC-CAROUSEL-013b: Run centered-transform detection BEFORE overflow-carousel.
2508                // wpcResolveCarouselViewport uses the first .overflow-hidden in the section; nested
2509                // card chrome inside slides matches first and wrongly triggers flex-overflow mode.
2510                var centeredTransformOk = (function() {
2511                    // EC-CAROUSEL-013: Centered-transform carousel — absolutely-positioned
2512                    // slides using translateX/scale for a "card fan" effect.
2513                    var $wrapper = null;
2514                    $container.find(".absolute.inset-0").each(function() {
2515                        var $w = $(this);
2516                        var absKids = $w.children(".absolute[style*='translateX'], .absolute[style*='translate']");
2517                        if (absKids.length >= 2) { $wrapper = $w; return false; }
2518                    });
2519                    if (!$wrapper) return false;
2520
2521                    var $slides = $wrapper.children(".absolute");
2522                    if ($slides.length < 2) return false;
2523
2524                    var positions = [];
2525                    $slides.each(function(idx) {
2526                        var s = $(this).attr("style") || "";
2527                        var tx = 0, sc = 1, op = 1, z = 10;
2528                        var txM = s.match(/translateX(([^)]+))/);
2529                        if (txM) tx = parseFloat(txM[1]) || 0;
2530                        var scM = s.match(/scale(([^)]+))/);
2531                        if (scM) sc = parseFloat(scM[1]) || 1;
2532                        var opM = s.match(/opacity:s*([0-9.]+)/);
2533                        if (opM) op = parseFloat(opM[1]);
2534                        var zM = s.match(/z-index:s*(d+)/);
2535                        if (zM) z = parseInt(zM[1], 10);
2536                        positions.push({ idx: idx, tx: tx, scale: sc, opacity: op, zIndex: z });
2537                    });
2538
2539                    positions.sort(function(a, b) { return Math.abs(a.tx) - Math.abs(b.tx); });
2540                    var centerOrigIdx = positions[0].idx;
2541                    var sorted = positions.slice().sort(function(a, b) { return a.tx - b.tx; });
2542                    var gap = sorted.length > 1 ? Math.abs(sorted[1].tx - sorted[0].tx) : 768;
2543
2544                    var scaleCenter = 1, scaleAdj = 0.83, scaleFar = 0.68;
2545                    var opCenter = 1, opAdj = 0.7, opFar = 0.35;
2546                    var zCenter = 30, zAdj = 20, zFar = 10;
2547
2548                    $btn.data("carousel-processed", true);
2549                    $nextBtn.data("carousel-processed", true);
2550                    $container.data("carousel-processed", true);
2551
2552                    var total = $slides.length;
2553                    var current = centerOrigIdx;
2554                    // Dots live in the row below the slider (sibling of .relative wrapper), not inside $container.find(all rounded-full)
2555                    var $dotRow = $btn.parent().nextAll("div").filter(function() {
2556                        return $(this).find("button.rounded-full").length >= 2;
2557                    }).first();
2558                    var $dots = $dotRow.length ? $dotRow.find("button.rounded-full") : $();
2559
2560                    function renderTransform(idx) {
2561                        $slides.each(function(si) {
2562                            var offset = si - idx;
2563                            var absOff = Math.abs(offset);
2564                            var tx = offset * gap;
2565                            var sc = absOff === 0 ? scaleCenter : absOff === 1 ? scaleAdj : scaleFar;
2566                            var op = absOff === 0 ? opCenter : absOff === 1 ? opAdj : opFar;
2567                            var z = absOff === 0 ? zCenter : absOff === 1 ? zAdj : zFar;
2568                            $(this).css({
2569                                transform: "translateX(" + tx + "px) scale(" + sc + ")",
2570                                opacity: op,
2571                                zIndex: z,
2572                                pointerEvents: absOff <= 1 ? "auto" : "none"
2573                            });
2574                        });
2575                        if ($dots.length) {
2576                            $dots.css({ width: "8px" }).removeClass("bg-revo-orange bg-white").addClass("bg-white/20");
2577                            $dots.eq(idx % $dots.length).css({ width: "32px" }).removeClass("bg-white/20").addClass("bg-revo-orange");
2578                        }
2579                    }
2580
2581                    $btn.off("click.wpcTransform").on("click.wpcTransform", function(e) {
2582                        e.preventDefault(); e.stopPropagation();
2583                        current = (current - 1 + total) % total;
2584                        renderTransform(current);
2585                    });
2586                    $nextBtn.off("click.wpcTransform").on("click.wpcTransform", function(e) {
2587                        e.preventDefault(); e.stopPropagation();
2588                        current = (current + 1) % total;
2589                        renderTransform(current);
2590                    });
2591                    if ($dots.length) {
2592                        $dots.css("cursor", "pointer").on("click.wpcTransform", function(e) {
2593                            e.preventDefault();
2594                            current = $(this).index();
2595                            renderTransform(current);
2596                        });
2597                    }
2598
2599                    renderTransform(current);
2600                    console.log("[EC-CAROUSEL-013] ✅ Centered-transform carousel: " + total + " slides, gap=" + ga
2600p);
2601                    return true;
2602                })();
2603
2604                if (centeredTransformOk) {
2605                    svgProcessed.push("centered-transform:" + $container.find(".absolute.inset-0 > .absolute").length);
2606                } else {
2607                var $viewport = wpcResolveCarouselViewport($container);
2608                var $track = $viewport.children().first();
2609                var $slides = $track.children('[aria-roledescription="slide"]');
2610
2611                if ($viewport.length && $track.length && $slides.length > 1) {
2612                    $btn.data("carousel-processed", true);
2613                    $nextBtn.data("carousel-processed", true);
2614                    $container.data("carousel-processed", true);
2615                    initializeOverflowCarousel($container, $viewport, $track, $slides, $btn, $nextBtn);
2616                    svgProcessed.push("overflow:" + $slides.length);
2617                } else if ($viewport.length && $track.length && $track.children().length > 1) {
2618                    $slides = $track.children();
2619                    $btn.data("carousel-processed", true);
2620                    $nextBtn.data("carousel-processed", true);
2621                    $container.data("carousel-processed", true);
2622                    initializeOverflowCarousel($container, $viewport, $track, $slides, $btn, $nextBtn);
2623                    svgProcessed.push("flex-overflow:" + $slides.length);
2624                } else {
2625                    // No overflow viewport found — check for a CSS grid with cards
2626                    // and convert it to a scrollable overflow carousel
2627                    var $grid = $container.find('[class*="grid"]').first();
2628                    if ($grid.length && $grid.children().length >= 2) {
2629                        // EC-CAROUSEL-016: Two-column hero (copy + stacked image gallery with chevrons) — never
2630                        // re-wrap the section grid as a horizontal overflow track (blank flashes, copy/image swap sides).
2631                        var skipHeroGrid = (function() {
2632                            var $kids = $grid.children();
2633                            if ($kids.length !== 2) return false;
2634                            var galleryIdx = -1;
2635                            $kids.each(function(i) {
2636                                if ($.contains(this, $btn[0])) { galleryIdx = i; return false; }
2637                            });
2638                            if (galleryIdx < 0) return false;
2639                            var $gal = $kids.eq(galleryIdx);
2640                            if ($gal.find("img").length < 2) return false;
2641                            var $copy = $kids.not($gal).first();
2642                            if (!$copy.length || $copy.find("h1,h2").length === 0) return false;
2643                            if (($copy.text() || "").trim().length < 80) return false;
2644                            return true;
2645                        })();
2646                        if (skipHeroGrid) {
2647                            $btn.data("carousel-processed", true);
2648                            $nextBtn.data("carousel-processed", true);
2649                            $container.data("carousel-processed", true);
2650                            svgProcessed.push("skip-hero-grid");
2651                        } else {
2652                        var containerW = $grid.outerWidth();
2653                        var numKids = $grid.children().length;
2654                        var origChildW = $grid.children().first().outerWidth();
2655                        var gapVal = parseFloat($grid.css("gap") || $grid.css("column-gap")) || 20;
2656
2657                        // Size children so at least one is partially off-screen
2658                        // (shows N-1 full items, ensuring the carousel has content to scroll)
2659                        var idealW = numKids > 1 ? (containerW / Math.max(1, numKids - 1)) * 0.95 : origChildW;
2660                        var childW = Math.max(origChildW, idealW);
2661
2662                        // Wrap all children in a new flex track
2663                        $grid.children().wrapAll('<div class="wpc-carousel-track" style="display:flex;gap:' + gapVal + 'px;flex-wrap:nowrap;transition:transform 0.5s cubic-bezier(0.25,0.46,0.45,0.94)"></div>');
2664                        var $newTrack = $grid.find(".wpc-carousel-track");
2665                        $newTrack.children().css({ flexShrink: 0, width: childW + "px" });
2666
2667                        // Convert grid container to overflow viewport
2668                        var oldCls = $grid.attr("class") || "";
2669                        $grid.attr("class", oldCls.replace(/\b(grid|grid-cols-\S+|gap-\S+)\b/g, "").trim());
2670                        $grid.css({ overflow: "hidden", display: "block" });
2671
2672                        $btn.data("carousel-processed", true);
2673                        $nextBtn.data("carousel-processed", true);
2674                        $container.data("carousel-processed", true);
2675                        initializeOverflowCarousel($container, $grid, $newTrack, $newTrack.children(), $btn, $nextBtn);
2676                        svgProcessed.push("grid-to-overflow:" + $newTrack.children().length);
2677                        }
2678                    } else {
2679                        $btn.data("carousel-processed", true);
2680                        $nextBtn.data("carousel-processed", true);
2681                        $container.data("carousel-processed", true);
2682                        initializeCarousel($container, $btn, $nextBtn);
2683                        svgProcessed.push("grid:" + ($container.find('[class*="grid"]').first().children().length || "?"));
2684                    }
2685                }
2686                }
2687            });
2688            if (svgProcessed.length) console.log("[EC-CAROUSEL-002] Method 3 (SVG icons): " + svgProcessed.join(", "));
2689
2690            // Method 4: Detect by aria-roledescription="carousel" (Embla/ShadCN semantic)
2691            $('[role="region"][aria-roledescription="carousel"]').each(function() {
2692                var $region = $(this);
2693                if ($region.data("carousel-processed")) return;
2694
2695                var $viewport = wpcResolveCarouselViewport($region);
2696                var $track = $viewport.children().first();
2697                var $slides = $track.children('[aria-roledescription="slide"]');
2698                if (!$slides.length) $slides = $track.children();
2699                if ($slides.length < 2) return;
2700
2701                var $prevBtn = $region.find("button").filter(function() { return classifyArrowBtn($(this)) === "prev"; }).first();
2702                var $nextBtn = $region.find("button").filter(function() { return classifyArrowBtn($(this)) === "next"; }).first();
2703
2704                if (!$prevBtn.length || !$nextBtn.length) {
2705                    // Embla buttons may be outside the overflow container but inside region
2706                    $prevBtn = $region.children("button").filter(function() { return classifyArrowBtn($(this)) === "prev"; }).first();
2707                    $nextBtn = $region.children("button").filter(function() { return classifyArrowBtn($(this)) === "next"; }).first();
2708                }
2709
2710                if ($prevBtn.length && $nextBtn.length && $viewport.length && $track.length) {
2711                    $region.data("carousel-processed", true);
2712                    $prevBtn.data("carousel-processed", true);
2713                    $nextBtn.data("carousel-processed", true);
2714                    initializeOverflowCarousel($region, $viewport, $track, $slides, $prevBtn, $nextBtn);
2715                    console.log("[EC-CAROUSEL-002] Method 4 (ARIA): " + $slides.length + " slides");
2716                }
2717            });
2718
2719            // Method 5: Generic overflow-hidden scrollable containers with nearby arrow buttons
2720            $(".overflow-hidden, [style*='overflow: hidden'], [style*='overflow:hidden']").each(function() {
2721                var $viewport = $(this);
2722                if ($viewport.data("carousel-processed")) return;
2723                if ($viewport.attr("data-wpconvert-stacked-gallery")) return;
2724                var $track = $viewport.children().first();
2725                if (!$track.length) return;
2726                var $children = $track.children();
2727                if ($children.length < 3) return;
2728
2729                // Check if children form a horizontal row (flex or inline)
2730                var hasFlex = ($track.attr("class") || "").indexOf("flex") >= 0;
2731                var hasInline = ($track.css("display") || "").indexOf("flex") >= 0;
2732                if (!hasFlex && !hasInline) return;
2733
2734                // Check children have shrink-0 or min-w-0 (carou
2734sel slide indicators)
2735                var looksLikeSlides = $children.filter(function() {
2736                    var cls = $(this).attr("class") || "";
2737                    return cls.indexOf("shrink-0") >= 0 || cls.indexOf("min-w-0") >= 0 || cls.indexOf("basis-") >= 0;
2738                }).length >= 2;
2739                if (!looksLikeSlides) return;
2740
2741                // Find nearby arrow buttons (sibling of viewport, or in parent)
2742                var $parent = $viewport.parent();
2743                var $prevBtn = $parent.find("button").filter(function() { return classifyArrowBtn($(this)) === "prev"; }).first();
2744                var $nextBtn = $parent.find("button").filter(function() { return classifyArrowBtn($(this)) === "next"; }).first();
2745                if (!$prevBtn.length || !$nextBtn.length) return;
2746                if ($prevBtn.data("carousel-processed") || $nextBtn.data("carousel-processed")) return;
2747
2748                $viewport.data("carousel-processed", true);
2749                $prevBtn.data("carousel-processed", true);
2750                $nextBtn.data("carousel-processed", true);
2751                initializeOverflowCarousel($parent, $viewport, $track, $children, $prevBtn, $nextBtn);
2752                console.log("[EC-CAROUSEL-002] Method 5 (generic overflow): " + $children.length + " items");
2753            });
2754
2755            // Method 6: onclick handlers + text arrow buttons + computed CSS overflow
2756            // Catches custom-coded carousels (not React/Tailwind based) like FormAcademy
2757            $("button").each(function() {
2758                var $btn = $(this);
2759                if ($btn.data("carousel-processed")) return;
2760                if ($btn.closest("[data-wpconvert-stacked-gallery-section]").length) { $btn.data("carousel-processed", true); return; }
2761                if ($btn.find("svg").length > 0) return;
2762                var role = classifyArrowBtn($btn);
2763                if (role !== "prev") return;
2764
2765                var $nextBtn = null;
2766                $btn.siblings("button").each(function() {
2767                    if ($(this).is($btn) || $(this).data("carousel-processed")) return;
2768                    if (classifyArrowBtn($(this)) === "next") { $nextBtn = $(this); return false; }
2769                });
2770                if (!$nextBtn || !$nextBtn.length) {
2771                    $btn.parent().parent().find("button").not($btn).each(function() {
2772                        if ($(this).data("carousel-processed")) return;
2773                        if (classifyArrowBtn($(this)) === "next") { $nextBtn = $(this); return false; }
2774                    });
2775                }
2776                if (!$nextBtn || !$nextBtn.length) return;
2777
2778                var $section = $btn.closest("section");
2779                if (!$section.length) $section = $btn.parent().parent().parent();
2780                if ($section.data("carousel-processed")) return;
2781
2782                var $viewport = null, $track = null, $slides = null;
2783                // Strategy 1: detect by computed CSS overflow + flex
2784                $section.find("div").each(function() {
2785                    var $el = $(this);
2786                    if ($el.data("carousel-processed")) return;
2787                    var ov = $el.css("overflow") || $el.css("overflow-x") || "";
2788                    var hasOverflow = ov === "hidden" || $el.hasClass("overflow-hidden");
2789                    if (!hasOverflow) return;
2790                    var $t = $el.children().first();
2791                    if (!$t.length) return;
2792                    var disp = $t.css("display") || "";
2793                    if (disp !== "flex" && disp !== "inline-flex") return;
2794                    if ($t.children().length < 2) return;
2795                    $viewport = $el;
2796                    $track = $t;
2797                    $slides = $t.children();
2798                    return false;
2799                });
2800                // Strategy 2: detect by element ID / class name patterns (fallback for CSS timing)
2801                if (!$viewport || !$viewport.length || !$slides || $slides.length < 2) {
2802                    $viewport = null; $track = null; $slides = null;
2803                    var vpPat = /viewport|slider-?wrap|carousel-?wrap|slide-?contain/i;
2804                    var trPat = /track|slider-?inner|carousel-?inner|slides-?row/i;
2805                    $section.find("div").each(function() {
2806                        var $el = $(this);
2807                        var elId = $el.attr("id") || "";
2808                        var elCls = $el.attr("class") || "";
2809                        if (!vpPat.test(elId) && !vpPat.test(elCls)) return;
2810                        var $t = $el.children().first();
2811                        if (!$t.length || $t.children().length < 2) return;
2812                        var tId = $t.attr("id") || "";
2813                        var tCls = $t.attr("class") || "";
2814                        if (trPat.test(tId) || trPat.test(tCls) || $t.css("display") === "flex") {
2815                            $viewport = $el;
2816                            $track = $t;
2817                            $slides = $t.children();
2818                            return false;
2819                        }
2820                    });
2821                }
2822
2823                if (!$viewport || !$viewport.length || !$slides || $slides.length < 2) return;
2824
2825                $btn.data("carousel-processed", true);
2826                $nextBtn.data("carousel-processed", true);
2827                $section.data("carousel-processed", true);
2828                initializeOverflowCarousel($section, $viewport, $track, $slides, $btn, $nextBtn);
2829                console.log("[EC-CAROUSEL-002] Method 6 (onclick/text): " + $slides.length + " items");
2830            });
2831
2832            // EC-CAROUSEL-003: Method 7 — Testimonial/card grids WITHOUT arrow buttons
2833            // Detects sections with 3+ similar child elements (testimonial cards, review cards)
2834            // in a grid that exceeds the viewport, and enables auto-advance carousel with dots/swipe
2835            $("section").each(function() {
2836                var $sec = $(this);
2837                if ($sec.data("carousel-processed")) return;
2838                var secCls = ($sec.attr("class") || "").toLowerCase();
2839                var secText = ($sec.find("h2, h3").first().text() || "").toLowerCase();
2840                var isTestimonialSection = /testimonial|review|feedback|client|what.*say|hear.*from/i.test(secCls + " " + secText);
2841                if (!isTestimonialSection) return;
2842                // Must have NO prev/next buttons already (those are handled above)
2843                var hasPrevNext = $sec.find("button").filter(function() {
2844                    return classifyArrowBtn($(this)) !== null;
2845                }).length >= 2;
2846                if (hasPrevNext) return;
2847                // Find the grid/flex container with 3+ card children
2848                var $grid = $sec.find("[class*='grid'], [class*='flex']").filter(function() {
2849                    var kids = $(this).children();
2850                    if (kids.length < 3) return false;
2851                    // Children should be similarly sized (cards)
2852                    var hasContent = kids.filter(function() {
2853                        return $(this).find("p, blockquote, [class*='quote']").length > 0;
2854                    }).length >= 3;
2855                    return hasContent;
2856                }).first();
2857                if (!$grid.length) return;
2858                var $items = $grid.children();
2859                if ($items.length < 3) return;
2860                var totalItems = $items.length;
2861                var currentIndex = 0;
2862                var columnConfig = { base: 1, sm: 1, md: null, lg: null, xl: null };
2863                var cls = $grid.attr("class") || "";
2864                cls.split(/\s+/).forEach(function(c) {
2865                    var m = c.match(/^(sm:|md:|lg:|xl:)?grid-cols-(\d+)/);
2866                    if (m) {
2867                        var bp = m[1] ? m[1].replace(":", "") : "base";
2868                        columnConfig[bp] = parseInt(m[2], 10);
2869                    }
2870                    // EC-CAROUSEL-018: Parse non-Tailwind CSS grid class names (grid--2, grid--3, etc.)
2871                    var cssGridMatch = c.match(/^grid--(\d+)$/);
2872                    if (cssGridMatch) {
2873                        var n = parseInt(cssGridMatch[1], 10);
2874                        if (n > (columnConfig.base || 1)) columnConfig.base = n;
2875                    }
2876                });
2877                // EC-CAROUSEL-006: Skip sections where the grid already shows all items.
2878                // If any breakpoint defines enough columns to display all items,
2879                // this is a static testimonial grid, not a carousel.
2880                var maxCols = Math.max(
2881                    columnConfig.base || 1, columnConfig.sm || 0,
2882                    columnConfig.md || 0, columnConfig.lg || 0, columnConfig.xl || 0
2883                );
2884                // EC-CAROUSEL-006b: For CSS-defined grids (no Tailwind grid-cols-N classes),
2885                // check computed gridTemplateColumns to detect multi-column layouts.
2886                // Sites built with custom CSS (e.g. Claude/AI-generated static HTML) define
2887                // columns via stylesheet rules, not utility classes.
2888                if (maxCols <= 1 && $grid[0]) {
2889                    try {
2890                        var gtc = window.getComputedStyle($grid[0]).gridTemplateColumns;
2891                        if (gtc && gtc !== "none") {
2892                            var cssCols = gtc.split(/\s+/).filter(function(v) { return v && v !== "0px"; }).length;
2893                            if (cssCols > maxCols) {
2894                                maxCols = cssCols;
2895                                console.log("[EC-CAROUSEL-006b] Detected " + cssCols + " CSS grid columns via computed style");
2896                            }
2897                        }
2898                    } catch(e) {}
2899                }
2900                if (maxCols >= totalItems) {
2901                    console.log("[EC-CAROUSEL-006] Skipped static testimonial grid: " + totalItems + " items, max " + maxCols + " cols");
2902                    return;
2903                }
2904                // EC-CAROUSEL-007/018: Multi-row grids with 2+ cols that fit in ≤4 rows are static.
2905                // Testimonial grids often have 4+ rows (e.g. 8 items in 2 cols) — these are
2906                // intentional scrollable page sections, not carousels.
2907                if (maxCols >= 2 && totalItems <= maxCols * 4) {
2908                    console.log("[EC-CAROUSEL-007] Skipped static multi-row testimonial grid: " + totalItems + " items, max " + maxCols + " cols");
2909                    return;
2910                }
2911                // EC-CAROUSEL-003 FIX: Skip flex-row layouts — these render all items
2912                // side-by-side at the responsive breakpoint, not as a scrollable carousel.
2913                // Flex-row has no grid-cols-N classes, so columnConfig stays at {base:1}
2914                // which falsely makes it look like it needs a carousel.
2915                var gridCls = cls;
2916                if (/\b(flex-row|md:flex-row|lg:flex-row|sm:flex-row)\b/.test(gridCls)) {
2917                    console.log("[EC-CAROUSEL-003] Skipped flex-row testimonial layout (" + totalItems + " items) — static side-by-side display");
2918                    return;
2919                }
2920                // Also check the parent: flex flex-col md:flex-row means items are rows on mobile
2921                // but side-by-side on md+. The grid container itself may be the flex parent.
2922                var parentCls = ($grid.parent().attr("class") || "");
2923                if (/\b(flex-row|md:flex-row|lg:flex-row|sm:flex-row)\b/.test(parentCls)) {
2924                    console.log("[EC-CAROUSEL-003] Skipped flex-row testimonial layout via parent (" + totalItems + " items)");
2925                    return;
2926                }
2927                // EC-CAROUSEL-019: Skip CSS marquee tracks. A marquee is already an
2928                // auto-scrolling carousel (translateX animation on a width:max-content
2929                // flex track) — it is NOT a paginated grid. If we treat it like one,
2930                // showItems() hides all-but-perView siblings with display:none, which
2931                // collapses the track from sum-of-card-widths down to a single card's
2932                // width. The remaining card then animates -50% (~ -170px) and snaps
2933                // back, producing the "one card sliding left, then switching" symptom
2934                // (Lovable / shadcn site marquees: .marquee-track, .marquee-track-*).
2935                var isMarqueeTrack = /\bmarquee\b|\bmarquee-[a-z0-9_-]+\b|\bw-max\b/i.test(cls);
2936                if (!isMarqueeTrack && $grid[0]) {
2937                    try {
2938                        var gcs = window.getComputedStyle($grid[0]);
2939                        var animName = gcs.animationName;
2940                        var widthBag = (gcs.width || "") + " " + (gcs.minWidth || "") + " " + (gcs.maxWidth || "");
2941                        // width:max-content + a running CSS animation = unambiguous marquee
2942                        if (animName && animName !== "none" && /max-content|fit-content/.test(widthBag)) {
2943                            isMarqueeTrack = true;
2944                        }
2945                    } catch(e) {}
2946                }
2947                if (isMarqueeTrack) {
2948                    console.log("[EC-CAROUSEL-019] Skipped marquee track (" + totalItems + " items) — CSS auto-scrolling animation, not a paginated carousel");
2949                    return;
2950                }
2951                // EC-CAROUSEL-020: Skip native horizontal-scroll testimonial rows.
2952                // Lovable / shadcn projects emit testimonials as a flex track with
2953                // overflow-x-auto + scrollbar-hide + snap-x + snap-mandatory and
2954                // child cards carrying flex-shrink-0 + w-[Npx] + snap-start. The
2955                // user scrolls horizontally with the trackpad / touch / programmatic
2956                // arrow buttons; every card is meant to be visible by design.
2957                //
2958                // Earlier skips do NOT catch this:
2959                //   - EC-CAROUSEL-006/007/018 (grid math): no grid-cols-N classes,
2960                //     so columnConfig stays base:1 and maxCols < totalItems.
2961                //   - EC-CAROUSEL-003 flex-row: class is "flex" not "flex-row".
2962                //   - EC-CAROUSEL-019 marquee: no marquee/w-max class, no CSS animation.
2963                //
2964                // Without this guard EC-CAROUSEL-003 calls items.hide() and
2965                // showItems(perView=1) — only the first card stays visible while a
2966                // 5-second autoplay rotates the rest, replacing the original
2967                // multi-card scrolling layout with a single-item slideshow.
2968                //
2969                // Detection (cheap class check first, then computed style):
2970                //   - track class contains overflow-x-auto, overflow-x-scroll,
2971                //     snap-x, or scrollbar-hide
2972                //   - OR computed overflow-x is auto or scroll
2973                var isOverflowScrollRow = /\boverflow-x-(auto|scroll)\b|\bsnap-x\b|\bscrollbar-hide\b/i.test(cls);
2974                if (!isOverflowScrollRow && $grid[0]) {
2975                    try {
2976                        var ovs = window.getComputedStyle($grid[0]);
2977                        var overflowX = ovs.overflowX || "";
2978                        if (/^(auto|scroll)$/.test(overflowX)) {
2979                            isOverflowScrollRow = true;
2980                        }
2981                    } catch(e) {}
2982                }
2983                if (isOverflowScrollRow) {
2984                    console.log("[EC-CAROUSEL-020] Skipped native overflow-x scroll row (" + totalItems + " items) — horizontal scroll-snap layout, not a paginated carousel");
2985                    return;
2986                }
2987                $sec.data("carousel-processed", true);
2988                function colsForWidth(w) {
2989                    if (w >= 1280 && columnConfig.xl) return columnConfig.xl;
2990                    if (w >= 1024 && columnConfig.lg) return columnConfig.lg;
2991                    if (w >= 768 && columnConfig.md) return columnConfig.md;
2992                    if (w >= 640 && columnConfig.sm) return columnConfig.sm;
2993                    return columnConfig.base || columnConfig.lg || columnConfig.md || 1;
2994                }
2995                var perView = Math.max(1, Math.min(totalItems, colsForWidth(window.innerWidth)));
2996                function showItems() {
2997                    $items.hide();
2998                    for (var i = 0; i < perView; i++) {
2999                        $items.eq((currentIndex + i) % totalItems).fadeIn(200);
3000                    }
3001                    // Update dots if present
3002                    var $dots = $sec.find("[class*='dot'], [class*='pagination'] button");
3003                    if ($dots.length > 0) {
3004                        $dots.css("opacity", "0.4");
3005                        $dots.eq(currentIndex % $dots.length).css("opacity", "1");
3006                    }
3007                }
3008                if (totalItems > perView) {
3009                    showItems();
3010                    var autoInterval = setInterval(function() {
3011                        currentIndex = (currentIndex + perView) % totalItems;
3012                        showItems();
3013                    }, 5000);
3014                    // Touch swipe support
3015                    var touchX = 0;
3016                    $grid.on("touchstart.wpcTestimonial", function(e) {
3017                        var t = e.originalEvent.touches[0];
3018                        if (t) touchX = t.clientX;
3019                    });
3020                    $grid.on("touchend.wpcTestimonial", function(e) {
3021                        var t = e.originalEvent.changedTouches[0]; if (!t) return;
3022                        var diff = touchX - t.clientX;
3023                        if (Math.abs(diff) > 50) {
3024                            clearInterval(autoInterval);
3025                            currentIndex = (currentIndex + (diff > 0 ? perView : -perView) + totalItems) % totalItems;
3026                            showItems();
3027                            autoInterval = setInterval(function() {
3028                                currentIndex = (currentIndex + perView) % totalItems;
3029                                showItems();
3030                            }, 5000);
3031                        }
3032                    });
3033                    // Dot click
3034                    $sec.find("[class*='dot'], [class*='pagination'] button").on("click", function(e) {
3035                        e.preventDefault();
3036                        clearInterval(autoInterval);
3037                        currentIndex = $(this).index();
3038                        showItems();
3039                        autoInterval = setInterval(function() {
3040                            currentIndex = (currentIndex + perView) % totalItems;
3041                            showItems();
3042                        }, 5000);
3043                    });
3044                    $(window).on("resize.wpcTestimonial", function() {
3045                        var newView = Math.max(1, Math.min(totalItems, colsForWidth(window.innerWidth))
3045);
3046                        if (newView !== perView) { perView = newView; currentIndex = 0; showItems(); }
3047                    });
3048                    console.log("[EC-CAROUSEL-003] Testimonial carousel (no arrows): " + totalItems + " items, " + perView + " per view");
3049                }
3050            });
3051
3052            } catch(e) { console.warn("[WPConvert] EC-CAROUSEL-002 error:", e); }
3053            
3054            try { // [WPConvert: EC-CAROUSEL-001 Testimonials]
3055            // EC-CAROUSEL-001: Single-item testimonial carousel handler
3056            // React state-based carousels only render one item — data is injected
3057            // as a JSON attribute by the snapshot phase.
3058            $("section[data-wpconvert-testimonials]").each(function() {
3059                var $section = $(this);
3060                if ($section.data("carousel-processed")) return;
3061                
3062                var rawData = $section.attr("data-wpconvert-testimonials");
3063                if (!rawData) return;
3064                
3065                // EC-CAROUSEL-017: Decode Base64-encoded JSON (prevents cheerio mangling double quotes).
3066                // Fallback: try raw JSON for legacy themes.
3067                var testimonials;
3068                try {
3069                    testimonials = JSON.parse(decodeURIComponent(escape(atob(rawData))));
3070                } catch (e) {
3071                    try { testimonials = JSON.parse(rawData); } catch (e2) { return; }
3072                }
3073                if (!testimonials || testimonials.length < 2) return;
3074                
3075                console.log("[WPConvert] Initializing testimonial carousel with " + testimonials.length + " items");
3076                
3077                var activeIndex = 0;
3078                
3079                // EC-TESTIMONIAL-004: Prefer aria-label Prev/Next (inner quote card) before any <i> scan.
3080                // Revo nests an outer slide track (first ri-arrow-* in DOM) + inner card controls; scanning
3081                // icons first bound the wrong buttons and .first() rounded-xl matched empty slide cards.
3082                var $prevBtn = $section.find('button[aria-label*="Previous"], button[aria-label*="Prev"], button[aria-label*="previous"]').first();
3083                var $nextBtn = $section.find('button[aria-label*="Next"], button[aria-label*="next"]').first();
3084                
3085                // Prev/Next buttons: find buttons containing chevron SVGs
3086                if (!$prevBtn.length || !$nextBtn.length) {
3087                    $prevBtn = null;
3088                    $nextBtn = null;
3089                    $section.find("button").each(function() {
3090                        var $btn = $(this);
3091                        var $svg = $btn.find("svg");
3092                        if (!$svg.length) return;
3093                        var svgClass = $svg.attr("class") || "";
3094                        var pathD = "";
3095                        $svg.find("path").each(function() { pathD += ($(this).attr("d") || ""); });
3096                        if (/chevron-left|lucide-chevron-left/.test(svgClass) || /m\s*15\s+18|15\s*18\s*-6\s*-6/i.test(pathD)) {
3097                            if (!$prevBtn) $prevBtn = $btn;
3098                        }
3099                        if (/chevron-right|lucide-chevron-right/.test(svgClass) || /m\s*9\s+18|9\s*18\s*6\s*-6/i.test(pathD)) {
3100                            if (!$nextBtn) $nextBtn = $btn;
3101                        }
3102                    });
3103                }
3104                
3105                // EC-TESTIMONIAL-002: <i> icon arrow buttons (Remix Icon, etc.) — only if aria/SVG pair missing
3106                if (!$prevBtn || !$prevBtn.length || !$nextBtn || !$nextBtn.length) {
3107                    $prevBtn = null;
3108                    $nextBtn = null;
3109                    $section.find("button").each(function() {
3110                        var $btn = $(this);
3111                        var $icon = $btn.find("i");
3112                        if (!$icon.length) return;
3113                        var iconCls = ($icon.attr("class") || "").toLowerCase();
3114                        if (/arrow-left|chevron-left|angle-left|caret-left/.test(iconCls)) {
3115                            if (!$prevBtn) $prevBtn = $btn;
3116                        }
3117                        if (/arrow-right|chevron-right|angle-right|caret-right/.test(iconCls)) {
3118                            if (!$nextBtn) $nextBtn = $btn;
3119                        }
3120                    });
3121                }
3122                
3123                if (!$prevBtn || !$prevBtn.length || !$nextBtn || !$nextBtn.length) {
3124                    console.log("[WPConvert] Testimonial carousel: no prev/next buttons found");
3125                    return;
3126                }
3127                
3128                // Card = rounded shell that contains the quote/author content.
3129                // First try: card that contains the button (buttons inside card).
3130                var $cardArea = $prevBtn.closest('[class*="rounded-3xl"], [class*="rounded-2xl"], [class*="rounded-xl"], blockquote').first();
3131                // Fallback: buttons may be siblings of the card (e.g. Bloom layout where
3132                // prev/next sit below the card in a separate flex row). Find the first
3133                // rounded card within the section that contains quote-length text.
3134                if (!$cardArea.length || $cardArea.is($section)) {
3135                    $cardArea = $section.find('[class*="rounded-3xl"], [class*="rounded-2xl"], [class*="rounded-xl"]').filter(function() {
3136                        return $(this).find("p").length > 0 && $(this).text().trim().length > 40;
3137                    }).first();
3138                }
3139                if (!$cardArea.length) $cardArea = $section;
3140                
3141                // Author name: bold/semibold text inside author area
3142                var $authorContainer = $cardArea.find('[class*="items-center"]').filter(function() {
3143                    return $(this).find("button").length === 0;
3144                }).first();
3145                if (!$authorContainer.length) $authorContainer = $cardArea.find('[class*="items-center"]').first();
3146                
3147                // Quote: longest <p> in card, excluding paragraphs inside the author row
3148                var $quoteEl = null;
3149                var bestQLen = 0;
3150                $cardArea.find("p").each(function() {
3151                    var $p = $(this);
3152                    if ($authorContainer.length && $p.closest($authorContainer).length) return;
3153                    var txt = $p.text().trim();
3154                    if (txt.length > bestQLen && txt.length > 40) {
3155                        bestQLen = txt.length;
3156                        $quoteEl = $p;
3157                    }
3158                });
3159                if (!$quoteEl || !$quoteEl.length) {
3160                    $cardArea.find("blockquote").each(function() {
3161                        var txt = $(this).text().trim();
3162                        if (txt.length > bestQLen && txt.length > 40) {
3163                            bestQLen = txt.length;
3164                            $quoteEl = $(this);
3165                        }
3166                    });
3167                    if (!$quoteEl && $cardArea.is("blockquote") && $cardArea.text().trim().length > 40) {
3168                        $quoteEl = $cardArea;
3169                    }
3170                }
3171                
3172                if (!$quoteEl || !$quoteEl.length) {
3173                    console.log("[WPConvert] Testimonial carousel: no quote element found");
3174                    return;
3175                }
3176                
3177                var $authorName = null;
3178                var $authorMeta = null;
3179                
3180                // Search for author name/role in the author container (items-center flex row)
3181                var $authorSearchArea = $authorContainer.length ? $authorContainer : null;
3182                if ($authorSearchArea) {
3183                    $authorSearchArea.find("p, div, span, h4, h5").each(function() {
3184                        var cls = $(this).attr("class") || "";
3185                        if (/font-semibold|font-bold|font-extrabold|font-medium/.test(cls) && !$authorName) {
3186                            var txt = $(this).text().trim();
3187                            if (txt.length > 1 && txt.length < 60) $authorName = $(this);
3188                        } else if (/text-sm|text-xs|opacity|text-cream|text-gray|text-slate|text-muted/.test(cls) && !$authorMeta) {
3189                            $authorMeta = $(this);
3190                        }
3191                    });
3192                }
3193                
3194                // EC-CAROUSEL-017b: Fallback — when no items-center container exists,
3195                // search directly in the card area for bold author names and small/muted role text.
3196                // Skip elements that are already identified as the quote or inside buttons.
3197                if (!$authorName) {
3198                    $cardArea.find("p, h4, h5, span").each(function() {
3199                        var $el = $(this);
3200                        if ($el.closest("button").length) return;
3201                        if ($quoteEl && $el.is($quoteEl)) return;
3202                        if ($quoteEl && $el.find($quoteEl).length) return;
3203                        var cls = $el.attr("class") || "";
3204                        var txt = $el.text().trim();
3205                        if (/font-semibold|font-bold|font-extrabold|font-medium/.test(cls) && !$authorName) {
3206                            if (txt.length > 1 && txt.length < 60 && txt.length < bestQLen) $authorName = $el;
3207                        }
3208                    });
3209                }
3210                if (!$authorMeta) {
3211                    $cardArea.find("p, span").each(function() {
3212                        var $el = $(this);
3213                        if ($el.closest("button").length) return;
3214                        if ($quoteEl && $el.is($quoteEl)) return;
3215                        if ($authorName && $el.is($authorName)) return;
3216                        var cls = $el.attr("class") || "";
3217                        var txt = $el.text().trim();
3218                        if (/text-sm|text-xs|opacity|text-cream|text-gray|text-slate|text-muted/.test(cls) && !$authorMeta) {
3219                            if (txt.length > 0 && txt.length < 60) $authorMeta = $el;
3220                        }
3221                    });
3222                }
3223                
3224                var $avatarImg = ($authorSearchArea || $cardArea).find("img[class*='rounded-full'], img[class*='rounded']").first();
3225                var $avatar = null;
3226                if (!$avatarImg.length) {
3227                    $avatar = ($authorSearchArea || $cardArea).find('[class*="rounded-full"]').filter(function() {
3228                        return $(this).text().trim().length <= 2 && $(this).children("img").length === 0;
3229                    }).first();
3230                }
3231                
3232                var $dots = $section.find("button").filter(function() {
3233                    var cls = $(this).attr("class") || "";
3234                    return /rounded-full/.test(cls) && /w-2|h-2/.test(cls);
3235                });
3236                
3237                $section.data("carousel-processed", true);
3238                
3239                function updateTestimonial(index) {
3240                    var item = testimonials[index];
3241                    if (!item) return;
3242                    
3243                    // Animate out then in
3244                    var $fadeTargets = $quoteEl;
3245                    if ($authorName) $fadeTargets = $fadeTargets.add($authorName);
3246                    if ($authorMeta) $fadeTargets = $fadeTargets.add($authorMeta);
3247                    
3248                    $fadeTargets.animate({ opacity: 0 }, 150, function() {
3249                        // Update quote
3250                        var quoteText = item.quote;
3251                        if (!/^[""“]/.test(quoteText)) quoteText = "“" + quoteText;
3252                        if (!/[""”]$/.test(quoteText)) quoteText = quoteText + "”";
3253                        $quoteEl.text(quoteText);
3254                        
3255                        // Update author
3256                        if ($authorName) $authorName.text(item.author);
3257                        
3258                        // Update meta (role + location)
3259                        // EC-CAROUSEL-017b: Only update meta text if the new value is non-empty.
3260                        // Some sites have a static label (e.g. "Parent") that applies to all
3261                        // testimonials — clearing it when role is empty looks broken.
3262                        if ($authorMeta) {
3263                            var meta = "";
3264                            if (item.role) meta += item.role;
3265                            if (item.role && item.location) meta += " • ";
3266                            if (item.location) meta += item.location;
3267                            if (meta) $authorMeta.text(meta);
3268                        }
3269                        
3270                        // Update avatar (img src or text initial)
3271                        if ($avatarImg && $avatarImg.length && item.avatar) {
3272                            $avatarImg.attr("src", item.avatar);
3273                        } else if ($avatar && $avatar.length && item.author) {
3274                            $avatar.text(item.author.charAt(0).toUpperCase());
3275                        }
3276                        
3277                        $fadeTargets.animate({ opacity: 1 }, 200);
3278                    });
3279                    
3280                    // Update dots
3281                    if ($dots.length >= testimonials.length) {
3282                        $dots.each(function(i) {
3283                            var $dot = $(this);
3284                            if (i === index) {
3285                                $dot.addClass("w-8").css("background-color", "");
3286                                $dot.removeClass("bg-cream/30");
3287                            } else {
3288                                $dot.removeClass("w-8");
3289                            }
3290                        });
3291                    }
3292                }
3293                
3294                // Wire up prev/next
3295                if ($prevBtn && $prevBtn.length) {
3296                    $prevBtn.off("click.wpconvertTestimonial").on("click.wpconvertTestimonial", function(e) {
3297                        e.preventDefault();
3298                        e.stopPropagation();
3299                        activeIndex = (activeIndex - 1 + testimonials.length) % testimonials.length;
3300                        updateTestimonial(activeIndex);
3301                    });
3302                }
3303                
3304                if ($nextBtn && $nextBtn.length) {
3305                    $nextBtn.off("click.wpconvertTestimonial").on("click.wpconvertTestimonial", function(e) {
3306                        e.preventDefault();
3307                        e.stopPropagation();
3308                        activeIndex = (activeIndex + 1) % testimonials.length;
3309                        updateTestimonial(activeIndex);
3310                    });
3311                }
3312                
3313                // Wire up dot clicks
3314                if ($dots.length >= testimonials.length) {
3315                    $dots.each(function(i) {
3316                        $(this).off("click.wpconvertTestimonial").on("click.wpconvertTestimonial", function(e) {
3317                            e.preventDefault();
3318                            e.stopPropagation();
3319                            if (i < testimonials.length) {
3320                                activeIndex = i;
3321                                updateTestimonial(activeIndex);
3322                            }
3323                        });
3324                    });
3325                }
3326                
3327                // Optional auto-advance every 8 seconds
3328                var autoTimer = setInterval(function() {
3329                    activeIndex = (activeIndex + 1) % testimonials.length;
3330                    updateTestimonial(activeIndex);
3331                }, 8000);
3332                
3333                // Pause auto-advance on hover
3334                $section.on("mouseenter.wpconvertTestimonial", function() { clearInterval(autoTimer); });
3335                $section.on("mouseleave.wpconvertTestimonial", function() {
3336                    autoTimer = setInterval(function() {
3337                        activeIndex = (activeIndex + 1) % testimonials.length;
3338                        updateTestimonial(activeIndex);
3339                    }, 8000);
3340                });
3341                
3342                // Touch swipe support
3343                var touchStartX = 0;
3344                $cardArea.on("touchstart.wpconvertTestimonial", function(e) {
3345                    touchStartX = (e.originalEvent.touches[0] || e.originalEvent.changedTouches[0]).clientX;
3346                });
3347                $cardArea.on("touchend.wpconvertTestimonial", function(e) {
3348                    var touch = e.originalEvent.changedTouches[0];
3349                    if (!touch) return;
3350                    var diff = touchStartX - touch.clientX;
3351                    if (Math.abs(diff) > 50) {
3352                        if (diff > 0) { activeIndex = (activeIndex + 1) % testimonials.length; }
3353                        else { activeIndex = (activeIndex - 1 + testimonials.length) % testimonials.length; }
3354                        updateTestimonial(activeIndex);
3355                    }
3356                });
3357                
3358                console.log("[WPConvert] ✅ Testimonial carousel ready: " + testimonials.length + " items, prev=" + !!($prevBtn && $prevBtn.length) + ", next=" + !!($nextBtn && $nextBtn.length) + ", dots=" + $dots.length);
3359            });
3360            } catch(e) { console.warn("[WPConvert] Testimonial carousel error:", e); }
3361            
3362            
3363            // No parallax libraries detected - skipping parallax initialization
3364            
3365            
3366            
3367            // No animation libraries detected - skipping animation initialization
3368            
3369            
3370            try { // [WPConvert: Tabs Component]
3371            // WPConvert Tabs Component
3372            // Handles tabbed content sections converted from React components
3373            document.querySelectorAll('.wpconvert-tabs').forEach(function(container) {
3374                var tabCount = parseInt(container.dataset.tabCount) || 0;
3375                var currentTab = 0;
3376                
3377                var buttons = container.querySelectorAll('.wpconvert-tab-btn');
3378                var panels = container.querySelectorAll('.wpconvert-tab-panel');
3379                var dots = container.querySelectorAll('.wpconvert-tab-dot');
3380                var prevBtn = container.querySelector('.wpconvert-tab-prev');
3381                var nextBtn = container.querySelector('.wpconvert-tab-next');
3382                
3383                function showTab(index) {
3384                    if (index < 0) index = tabCount - 1;
3385                    if (index >= tabCount) index = 0;
3386                    currentTab = index;
3387                    
3388                    buttons.forEach(function(btn, i) {
3389                        btn.classList.toggle('active', i === index);
3390                        btn.setAttribute('aria-selected', i === index ? 'true' : 'false');
3391                    });
3392                    
3393                    panels.forEach(function(panel, i) {
3394                        panel.classList.toggle('active', i === index);
3395                        panel.hidden = i !== index;
3396                    });
3397                    
3398                    dots.forEach(function(dot, i) {
3399                        dot.classList.toggle('active', i === index);
3400                    });
3401                }
3402                
3403                buttons.forEach(function(btn, i) {
3404                    btn.addEventListener('click', function() { showTab(i); });
3405                });
3406                
3407                dots.forEach(function(dot, i) {
3408                    dot.addEventListener('click', function() { showTab(i); });
3409                });
3410                
3411                if (prevBtn) {
3412                    prevBtn.addEventListener('click', function() { showTab(currentTab - 1); });
3413                }
3414                
3415                if (nextBtn) {
3416                    nextBtn.addEventListener('click', function() { showTab(currentTab + 1); });
3417                }
3418                
3419                // Auto-rotate every 5 seconds
3420                var autoPlay = setInterval(function() {
3421                    showTab(currentTab + 1);
3422                }, 5000);
3423                
3424                // Pause on hover
3425                container.addEventListener('mouseenter', function() {
3426                    clearInterval(autoPlay);
3427                });
3428                
3429                container.addEventListener('mouseleave', function() {
3430                    autoPlay = setInterval(function() {
3431                        showTab(currentTab + 1);
3432                    }, 5000);
3433                });
3434            });
3435            } catch(e) { console.warn("[WPConvert] Tabs component error:", e); }
3436
3437            try { // [WPConvert: Multi-Step Form]
3438            
3439/* EC-MSTEP-002f: Multi-step form — preserve original design.
3440   Wires up the original Continue/Back buttons and adds selection toggle. */
3441(function() {
3442  function initMultiStepForms() {
3443  document.querySelectorAll('.wpconvert-multistep-form').forEach(function(form) {
3444    var steps = Array.from(form.querySelectorAll('[data-wpc-form-step]'));
3445    if (steps.length < 2) return;
3446    var current = 0;
3447
3448    function showStep(n) {
3449      if (n < 0 || n >= steps.length) return;
3450      current = n;
3451      steps.forEach(function(s, i) { s.style.display = i === current ? '' : 'none'; });
3452      wireButtons();
3453    }
3454
3455    function wireButtons() {
3456      var step = steps[current];
3457      var allBtns = Array.from(step.querySelectorAll('button'));
3458      allBtns.forEach(function(btn) {
3459        var t = (btn.textContent || '').trim().toLowerCase();
3460        /* EC-MSTEP-002g: Scope grid check to step element only.
3461           .closest() traverses the entire DOM — if the form sits inside a
3462           CSS-grid page layout (e.g. "grid lg:grid-cols-3"), Continue/Back
3463           buttons incorrectly appear "in a grid" and never get wired. */
3464        var gridAnc = btn.closest('[class*="grid"]');
3465        var inGrid = gridAnc && step.contains(gridAnc);
3466        /* Wire Continue/Next/Submit buttons (not in selection grids) */
3467        if (!inGrid && (/^continue|^next|^send.*request|^submit|^get started/i.test(t)) && t.length < 50) {
3468          btn.onclick = function(e) {
3469            e.preventDefault();
3470            if (current < steps.length - 1) showStep(current + 1);
3471          };
3472          btn.removeAttribute('disabled');
3473          btn.style.opacity = '1';
3474          btn.style.pointerEvents = 'auto';
3475          btn.classList.remove('disabled:opacity-40', 'disabled:pointer-events-none');
3476        }
3477        /* Wire Back/Previous buttons */
3478        if (!inGrid && /^back$|^previous$|^prev$/i.test(t)) {
3479          btn.onclick = function(e) {
3480            e.preventDefault();
3481            if (current > 0) showStep(current - 1);
3482          };
3483        }
3484      });
3485      wireSelections(step);
3486    }
3487
3488    /* Selection toggle for card-style buttons (services, budget, timeline) */
3489    function wireSelections(step) {
3490      var grids = step.querySelectorAll('[class*="grid"]');
3491      grids.forEach(function(grid) {
3492        var btns = Array.from(grid.querySelectorAll('button')).filter(function(b) {
3493          if (b.closest('[class*="grid"]') !== grid) return false;
3494          var t = (b.textContent || '').trim().toLowerCase();
3495          return !/^(continue|next|previous|back|prev|submit|get started|send)/i.test(t) && t.length > 1 && t.length < 60;
3496        });
3497        if (btns.length < 2) return;
3498        var isMulti = btns.length > 4;
3499        /* Detect accent color from Continue button's inline style */
3500        var accentColor = '', accentBg = '';
3501        var continueBtn = Array.from(step.querySelectorAll('button')).find(function(b) {
3502          var t = (b.textContent || '').trim().toLowerCase();
3503          var gAnc = b.closest('[class*="grid"]');
3504          var bInGrid = gAnc && step.contains(gAnc);
3505          return /continue|next|send|submit/i.test(t) && !bInGrid && b.style.backgroundColor;
3506        });
3507        if (continueBtn) {
3508          accentColor = continueBtn.style.backgroundColor;
3509          accentBg = accentColor.replace('rgb(', 'rgba(').replace(')', ', 0.1)');
3510        }
3511        if (!accentColor) { accentColor = '#3b82f6'; accentBg = 'rgba(59,130,246,0.1)'; }
3512        /* Capture default styles from first button */
3513        var defBorder = btns[0] && btns[0].style.borderColor || '';
3514        var defBg = btns[0] && btns[0].style.backgroundColor || '';
3515        var defColor = btns[0] && btns[0].style.color || '';
3516        btns.forEach(function(btn) {
3517          if (btn._wpcBound) return;
3518          btn._wpcBound = true;
3519          btn.style.cursor = 'pointer';
3520          btn.addEventListener('click', function(e) {
3521            e.preventDefault();
3522            if (isMulti) {
3523              btn.toggleAttribute('data-selected');
3524            } else {
3525              btns.forEach(function(b) { b.removeAttribute('data-selected'); });
3526              btn.setAttribute('data-selected', '');
3527            }
3528            btns.forEach(function(b) {
3529              var sel = b.hasAttribute('data-selected');
3530              b.style.borderColor = sel ? accentColor : defBorder;
3531              b.style.backgroundColor = sel ? accentBg : defBg;
3532              if (sel) b.style.color = 'white';
3533              else if (defColor) b.style.color = defColor;
3534            });
3535          });
3536        });
3537      });
3538    }
3539
3540    showStep(0);
3541  });
3542  }
3543  /* EC-MSTEP-002h: Run immediately if DOM is already loaded (e.g. inside jQuery.ready),
3544     otherwise wait for DOMContentLoaded. jQuery.ready fires after DOMContentLoaded,
3545     so adding a DOMContentLoaded listener inside it would silently never execute. */
3546  if (document.readyState === 'loading') {
3547    document.addEventListener('DOMContentLoaded', initMultiStepForms);
3548  } else {
3549    initMultiStepForms();
3550  }
3551})();
3552
3553            } catch(e) { console.warn("[WPConvert] Multi-step form error:", e); }
3554
3555            try { // [WPConvert: Signature Canvas]
3556            
3557/* EC-CANVAS-002: Activate canvas drawing pads only with a strong positive signal.
3558   See generate.js > generateSignatureCanvasInitJS for the full rationale. */
3559(function(){
3560  var DECORATIVE_TOKEN = /(^|[-_\s])(hero|particle|particles|background|bg|webgl|three|chart|graph|noise|ripple|aurora|stars|matrix|fluid|wave|globe|sphere|mesh|shader|fbo|gl|canvas-bg|main-canvas)([-_\s]|$)/;
3561  function hasStrongPositiveSignal(canvas) {
3562    var id = (canvas.id || '').toLowerCase();
3563    var rawCls = canvas.className;
3564    var cls = (rawCls && typeof rawCls === 'string' ? rawCls : (rawCls && rawCls.baseVal) || '').toLowerCase();
3565    /* 1. explicit attribute */
3566    if (canvas.hasAttribute('data-signature') || canvas.hasAttribute('data-sig-pad')) return true;
3567    /* 2. id/class spells "signature", "sigpad", "sig-pad" */
3568    if (/sig(?:nature|pad|-pad)/.test(id) || /sig(?:nature|pad|-pad)/.test(cls)) return true;
3569    /* 3. <label for="canvasId"> with "signature" text */
3570    if (canvas.id) {
3571      try {
3572        var safeId = canvas.id.replace(/(["\\])/g, '\\$1');
3573        var lbl = canvas.ownerDocument.querySelector('label[for="' + safeId + '"]');
3574        if (lbl && /signature/i.test(lbl.textContent || '')) return true;
3575      } catch (e) { /* invalid selector — fall through */ }
3576    }
3577    /* 4. canvas inside form/multistep panel AND a label/legend/heading in that
3578          scope mentions "signature". We deliberately scope to the form so we
3579          do not match unrelated text elsewhere on the page (e.g. a footer
3580          newsletter "Sign up" or a pricing line "60% on signature"). */
3581    var formScope = canvas.closest('form, .wpconvert-step-panel, .wpconvert-multistep-form');
3582    if (formScope) {
3583      var labels = formScope.querySelectorAll('label, legend, h1, h2, h3, h4, h5, h6');
3584      for (var i = 0; i < labels.length; i++) {
3585        if (/signature/i.test(labels[i].textContent || '')) return true;
3586      }
3587    }
3588    return false;
3589  }
3590
3591  document.querySelectorAll('canvas').forEach(function(canvas) {
3592    if (canvas.dataset.wpcSigInit) return;
3593    /* Strong negative: well-known decorative canvas ids/classes.
3594       Checked BEFORE positive signals — even inside a <form> a canvas
3595       named "hero-canvas" or "particle-canvas" must never become a
3596       signature pad. */
3597    var id = (canvas.id || '').toLowerCase();
3598    var rawCls = canvas.className;
3599    var cls = (rawCls && typeof rawCls === 'string' ? rawCls : (rawCls && rawCls.baseVal) || '').toLowerCase();
3600    if (DECORATIVE_TOKEN.test(id) || DECORATIVE_TOKEN.test(cls)) return;
3601
3602    if (!hasStrongPositiveSignal(canvas)) return;
3603
3604    canvas.dataset.wpcSigInit = 'true';
3605    canvas.width = canvas.offsetWidth || 600;
3606    canvas.height = canvas.offsetHeight || 150;
3607    canvas.style.touchAction = 'none';
3608    canvas.style.cursor = 'crosshair';
3609    var ctx = canvas.getContext && canvas.getContext('2d');
3610    if (!ctx) return;
3611    ctx.strokeStyle = '#1a1a1a';
3612    ctx.lineWidth = 2;
3613    ctx.lineCap = 'round';
3614    ctx.lineJoin = 'round';
3615    var drawing = false;
3616    function getPos(e) {
3617      var rect = canvas.getBoundingClientRect();
3618      var x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
3619      var y = (e.touches ? e.touches[0].clientY : e.clientY) - rect.top;
3620      return { x: x, y: y };
3621    }
3622    canvas.addEventListener('mousedown', function(e) { drawing = true; ctx.beginPath(); var p = getPos(e); ctx.moveTo(p.x, p.y); });
3623    canvas.addEventListener('mousemove', function(e) { if (!drawing) return; var p = getPos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); });
3624    canvas.addEventListener('mouseup', function() { drawing = false; });
3625    canvas.addEventListener('mouseleave', function() { drawing = false; });
3626    canvas.addEventListener('touchstart', function(e) { e.preventDefault(); drawing = true; ctx.beginPath(); var p = getPos(e); ctx.moveTo(p.x, p.y); }, { passive: false });
3627    canvas.addEventListener('touchmove', function(e) { e.preventDefault(); if (!drawing) return; var p = getPos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); }, { passive: false });
3628    canvas.addEventListener('touchend', function() { drawing = false; });
3629    /* Wire a "Clear" button next to the canvas if present. */
3630    var container = canvas.closest('div');
3631    if (container) {
3632      var clearBtn = container.querySelector('button');
3633      if (!clearBtn) clearBtn = container.parentElement ? container.parentElement.querySelector('button') : null;
3634      if (clearBtn && /clear/i.test(clearBtn.textContent || '')) {
3635        clearBtn.addEventListener('click', function(e) {
3636          e.preventDefault();
3637          ctx.clearRect(0, 0, canvas.width, canvas.height);
3638        });
3639      }
3640    }
3641    /* Persist signature data into a hidden input so the form submission
3642       includes the drawn image. */
3643    var hiddenInput = canvas.ownerDocument.createElement('input');
3644    hiddenInput.type = 'hidden';
3645    hiddenInput.name = 'signature_data';
3646    if (canvas.parentElement) canvas.parentElement.appendChild(hiddenInput);
3647    canvas.addEventListener('mouseup', function() { hiddenInput.value = canvas.toDataURL ? canvas.toDataURL() : ''; });
3648    canvas.addEventListener('touchend', function() { hiddenInput.value = canvas.toDataURL ? canvas.toDataURL() : ''; });
3649  });
3650})();
3651
3652            } catch(e) { console.warn("[WPConvert] Signature canvas error:", e); }
3653
3654            try { // [WPConvert: Calculator]
3655            // EC-CALC-001: Interactive calculator
3656            document.querySelectorAll('[data-wpc-calc]').forEach(function(calcEl) {
3657              var configStr = calcEl.getAttribute('data-wpc-calc-formula');
3658              if (!configStr) return;
3659              var config;
3660              try { config = JSON.parse(configStr); } catch(e) { return; }
3661              var state = {};
3662              var sliders = calcEl.querySelectorAll('input[type="range"][data-wpc-calc-var]');
3663              sliders.forEach(function(s) {
3664                var sVar = s.getAttribute('data-wpc-calc-var');
3665                var inputKey = null;
3666                for (var k in config.inputs) {
3667                  if (config.inputs[k].sliderVar === sVar) { inputKey = k; break; }
3668                }
3669                if (inputKey) {
3670                  state[inputKey] = parseFloat(s.value) || 0;
3671                  s.addEventListener('input', function() {
3672                    state[inputKey] = parseFloat(s.value) || 0;
3673                    recalc();
3674                  });
3675                }
3676              });
3677              var toggleBtns = calcEl.querySelectorAll('[data-wpc-calc-toggle]');
3678              toggleBtns.forEach(function(btn) {
3679                var tVar = btn.getAttribute('data-wpc-calc-toggle');
3680                var idx = parseInt(btn.getAttribute('data-wpc-calc-toggle-idx'), 10) || 0;
3681                var inputKey = null;
3682                for (var k in config.inputs) {
3683                  if (config.inputs[k].toggleVar === tVar) { inputKey = k; break; }
3684                }
3685                if (!inputKey) return;
3686                if (!state[inputKey]) {
3687                  state[inputKey] = config.inputs[inputKey]['default'] || config.inputs[inputKey].options[0];
3688                }
3689                btn.addEventListener('click', function() {
3690                  var opts = config.inputs[inputKey].options;
3691                  state[inputKey] = opts[idx] || opts[0];
3692                  calcEl.querySelectorAll('[data-wpc-calc-toggle="' + tVar + '"]').forEach(function(b, bi) {
3693                    var parts = (b.getAttribute('class') || '').split(' ');
3694                    if (bi === idx) {
3695                      parts = parts.map(function(c) {
3696                        if (c === 'bg-card') return 'bg-accent/10';
3697                        if (c === 'border-border/50') return 'border-accent';
3698                        if (c === 'text-muted-foreground') return 'text-accent';
3699                        return c;
3700                      });
3701                      b.classList.add('wpc-calc-toggle-active');
3702                    } else {
3703                      parts = parts.map(function(c) {
3704                        if (c === 'bg-accent/10') return 'bg-card';
3705                        if (c === 'border-accent') return 'border-border/50';
3706                        if (c === 'text-accent' && c.indexOf('text-accent-') !== 0) return 'text-muted-foreground';
3707                        return c;
3708                      });
3709                      b.classList.remove('wpc-calc-toggle-active');
3710                    }
3711                    b.setAttribute('class', parts.join(' '));
3712                  });
3713                  recalc();
3714                });
3715              });
3716              function formatValue(val, fmt) {
3717                if (typeof val === 'string') return val;
3718                var n = parseFloat(val);
3719                if (isNaN(n)) return String(val);
3720                switch (fmt) {
3721                  case 'currency': return '$' + n.toLocaleString('en-US', {minimumFractionDigits: 0, maximumFractionDigits: 0});
3722                  case 'decimal2': return n.toFixed(2);
3723                  case 'percent': return n + '%';
3724                  case 'months': return n + ' months';
3725                  default: return n.toLocaleString('en-US');
3726                }
3727              }
3728              function recalc() {
3729                var computed = {};
3730                for (var k in state) computed[k] = state[k];
3731                if (config.formulas) {
3732                  for (var i = 0; i < config.formulas.length; i++) {
3733                    var f = config.formulas[i];
3734                    try {
3735                      var fn = new Function(f.deps.join(','), 'return (' + f.expr + ')');
3736                      var args = f.deps.map(function(d) { return computed[d]; });
3737                      computed[f['var']] = fn.apply(null, args);
3738                    } catch(e) { /* skip */ }
3739                  }
3740                }
3741                sliders.forEach(function(s) {
3742                  var sVar = s.getAttribute('data-wpc-calc-var');
3743                  var display = calcEl.querySelector('[data-wpc-calc-display="' + sVar + '"]');
3744                  if (!display) return;
3745                  var inputKey = null;
3746                  for (var k in config.inputs) {
3747                    if (config.inputs[k].sliderVar === sVar) { inputKey = k; break; }
3748                  }
3749                  if (inputKey && config.inputs[inputKey]) {
3750                    display.textContent = formatValue(parseFloat(s.value), config.inputs[inputKey].displayFormat || config.inputs[inputKey].format);
3751                  }
3752                });
3753                var outputEls = calcEl.querySelectorAll('[data-wpc-calc-output]');
3754                outputEls.forEach(function(el) {
3755                  var oVar = el.getAttribute('data-wpc-calc-output');
3756                  var outCfg = config.outputs[oVar];
3757                  if (!outCfg) return;
3758                  var val = computed[outCfg.formulaVar];
3759                  if (val !== undefined && val !== null) {
3760                    el.textContent = formatValue(val, outCfg.format);
3761                  }
3762                });
3763              }
3764              recalc();
3765            });
3766            } catch(e) { console.warn("[WPConvert] Calculator error:", e); }
3767        });
3768        (function() {
3769            var selectors = '.scroll-fade-up, .scroll-fade-left, .scroll-fade-right, .scroll-scale-in';
3770            function initScrollReveal() {
3771                var els = document.querySelectorAll(selectors);
3772                if (!els.length || typeof IntersectionObserver === 'undefined') return;
3773                var io = new IntersectionObserver(function(entries) {
3774                    entries.forEach(function(entry) {
3775                        if (entry.isIntersecting) {
3776                            entry.target.classList.add('visible');
3777                            io.unobserve(entry.target);
3778                        }
3779                    });
3780                }, { threshold: 0.15 });
3781                els.forEach(function(el) {
3782                    if (el.closest('[aria-roledescription="carousel"]')) {
3783                        el.classList.add('visible');
3784                    } else {
3785                        io.observe(el);
3786                    }
3787                });
3788            }
3789            if (document.readyState === 'loading') {
3790                document.addEventListener('DOMContentLoaded', initScrollReveal);
3791            } else {
3792                initScrollReveal();
3793            }
3794        })();
3795//# sourceURL=jquery-js-after</script>
3795 <link rel="https://api.w.org/" href="https://www.choosetosucceed.org/wp-json/" /><link rel="alternate" title="JSON" type="application/json" href="https://www.choosetosucceed.org/wp-json/wp/v2/pages/8585" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://www.choosetosucceed.org/xmlrpc.php?rsd" /><meta name="generator" content="WordPress 7.0.6" /><link rel='shortlink' href='https://www.choosetosucceed.org/?p=8585' /> 
3795<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.9.0/slick.min.js" integrity="sha512-HGOnQO9+SP1V92SrtZfjqxxtLmVzqZpjFFekvzZVWoiASSQgSr4cw9Kqd2+l8Llp4Gm0G8GIFJ4ddwZilcdb8A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
3795 <link rel="icon" href="https://www.choosetosucceed.org/wp-content/uploads/2026/06/cropped-CTS-square-1-32x32.jpg" sizes="32x32" /><link rel="icon" href="https://www.choosetosucceed.org/wp-content/uploads/2026/06/cropped-CTS-square-1-192x192.jpg" sizes="192x192" /><link rel="apple-touch-icon" href="https://www.choosetosucceed.org/wp-content/uploads/2026/06/cropped-CTS-square-1-180x180.jpg" /><meta name="msapplication-TileImage" content="https://www.choosetosucceed.org/wp-content/uploads/2026/06/cropped-CTS-square-1-270x270.jpg" /> <style id="wpconvert-scroll-carousel-critical">.scroll-fade-up:has([aria-roledescription="carousel"]),
3796      .scroll-fade-up:has([data-wpconvert-carousel]),
3797      .scroll-fade-up:has([data-wpc-carousel="true"]),
3798      /* EC-BLOG-146: Blog category strip uses horizontal scroll + chevrons, not a carousel — IO scroll-reveal
3799       * still hid the bar at opacity:0 (looks like a “carousel flash”). */
3800      .scroll-fade-up:has([data-wpconvert-blog-filter-bar]) {
3801        opacity: 1 !important;
3802        transform: none !important;
3803        visibility: visible !important;
3804      }
3805      [role="region"][aria-roledescription="carousel"],
3806      [data-wpconvert-carousel="aria"],
3807      [data-wpc-carousel="true"] {
3808        opacity: 1 !important;
3809        visibility: visible !important;
3810      }</style><style id="wpconvert-mobile-sticky-cta-critical">#wpconvert-mobile-sticky-cta {
3811        display: none !important;
3812        visibility: hidden !important;
3813        pointer-events: none !important;
3814      }
3815      @media (max-width: 1023px) {
3816        #wpconvert-mobile-sticky-cta {
3817          display: block !important;
3818          visibility: visible !important;
3819          pointer-events: auto !important;
3820          position: fixed !important;
3821          left: 0 !important;
3822          right: 0 !important;
3823          bottom: 0 !important;
3824          z-index: 50 !important;
3825        }
3826      }</style><style id="wpconvert-header-metrics-critical">:root {
3827        --wpconvert-header-bar-height: 4rem;
3828      }</style><style id="wpconvert-header-nowrap-critical">header .container.flex.items-center.justify-between,
3829      header .container.flex {
3830        flex-wrap: nowrap !important;
3831        flex-direction: row !important;
3832      }
3833      header .container.flex.h-16,
3834      header .container.flex.min-h-16 {
3835        min-height: var(--wpconvert-header-bar-height) !important;
3836      }</style><style id="wpconvert-header-nav-cluster-contents-critical">[data-wpconvert-merge-nav-cluster] {
3837        display: none !important;
3838      }
3839      @media (min-width: 640px) {
3840        [data-wpconvert-merge-nav-cluster="sm"] {
3841          display: contents !important;
3842        }
3843      }
3844      @media (min-width: 768px) {
3845        [data-wpconvert-merge-nav-cluster="md"] {
3846          display: contents !important;
3847        }
3848      }
3849      @media (min-width: 1024px) {
3850        [data-wpconvert-merge-nav-cluster="lg"] {
3851          display: contents !important;
3852        }
3853      }
3854      @media (min-width: 1280px) {
3855        [data-wpconvert-merge-nav-cluster="xl"] {
3856          display: contents !important;
3857        }
3858      }</style><style id="wpconvert-flat-nav-nowrap-critical">/* EC-NAV-179b: target BOTH the menu_id span (when items_wrap preserves it)
3859         AND the stable .wpconvert-flat-nav-items marker class, so the rule keeps
3860         matching even if a future consumer overrides menu_id. Using > a picks up
3861         the walker-emitted bare anchors regardless of whether the span is
3862         display:contents or a real box. */
3863      #wpconvert-flat-nav > a,
3864      .wpconvert-flat-nav-items > a,
3865      [data-wpconvert-merge-nav-cluster] #wpconvert-flat-nav > a,
3866      [data-wpconvert-merge-nav-cluster] .wpconvert-flat-nav-items > a {
3867        white-space: nowrap;
3868        flex: 0 0 auto;
3869        width: auto;
3870      }</style> 
3870<script id="wpconvert-parallax-scroll">// EC-ANIM-012: Scroll-driven parallax translateX handler.
3871      // Replaces Framer Motion useScroll+useTransform for horizontal parallax text sections.
3872      (function() {
3873        function initParallax() {
3874          var els = document.querySelectorAll('[data-wpconvert-parallax-x]');
3875          if (!els.length) return;
3876          var entries = [];
3877          els.forEach(function(el) {
3878            var parts = el.getAttribute('data-wpconvert-parallax-x').split(',');
3879            entries.push({ el: el, start: parseFloat(parts[0]), end: parseFloat(parts[1]) });
3880          });
3881          var ticking = false;
3882          function update() {
3883            var vh = window.innerHeight;
3884            entries.forEach(function(e) {
3885              var parent = e.el.closest('section') || e.el.parentElement;
3886              var rect = parent.getBoundingClientRect();
3887              var progress = Math.max(0, Math.min(1, (vh - rect.top) / (vh + rect.height)));
3888              var x = e.start + (e.end - e.start) * progress;
3889              e.el.style.transform = 'translateX(' + x + '%)';
3890            });
3891            ticking = false;
3892          }
3893          window.addEventListener('scroll', function() {
3894            if (!ticking) { ticking = true; requestAnimationFrame(update); }
3895          }, { passive: true });
3896          update();
3897        }
3898        if (document.readyState === 'loading') {
3899          document.addEventListener('DOMContentLoaded', initParallax);
3900        } else {
3901          initParallax();
3902        }
3903      })();
3904      // EC-ANIM-011c: Overlap fix handled at build time by demoting z-10 on centered pulse cards.</script>
3904 <style id="wpconvert-popup-hide-critical">[data-wpconvert-popup-disabled="true"] {
3905        display: none !important;
3906        visibility: hidden !important;
3907        opacity: 0 !important;
3908        pointer-events: none !important;
3909        position: absolute !important;
3910        left: -99999px !important;
3911        top: -99999px !important;
3912        width: 0 !important;
3913        height: 0 !important;
3914        max-width: 0 !important;
3915        max-height: 0 !important;
3916        overflow: hidden !important;
3917        clip: rect(0,0,0,0) !important;
3918        z-index: -99999 !important;
3919        transform: scale(0) !important;
3920        margin: 0 !important;
3921        padding: 0 !important;
3922      }
3923      [data-wpconvert-popup-disabled="true"] * {
3924        display: none !important;
3925        visibility: hidden !important;
3926        opacity: 0 !important;
3927      }
3928      /* Block any JavaScript from showing popups */
3929      [data-wpconvert-popup-disabled="true"][style*="display: block"],
3930      [data-wpconvert-popup-disabled="true"][style*="display:block"],
3931      [data-wpconvert-popup-disabled="true"][class*="open"],
3932      [data-wpconvert-popup-disabled="true"][class*="show"],
3933      [data-wpconvert-popup-disabled="true"][class*="active"] {
3934        display: none !important;
3935      }
3936      /* Mobile navigation - HIDDEN by default, only visible when .open class is added */
3937      /* Uses CSS variables to match site theme (dark/light mode) */
3938      #wpconvert-mobile-nav {
3939        display: none !important; /* Hidden by default - JavaScript will show it */
3940        position: fixed !important; /* !important needed: framework CSS sets nav:not(.sticky):not(.fixed) to relative !important */
3941        top: 0 !important;
3942        bottom: 0 !important;
3943        width: 280px;
3944        max-width: 85vw;
3945        z-index: 99999 !important; /* MUST be higher than backdrop (99998) */
3946        /* Theme-aware colors using CSS variables with fallbacks */
3947        background: hsl(var(--background, 0 0% 100%));
3948        color: hsl(var(--foreground, 0 0% 10%));
3949        overflow-y: auto;
3950        transition: transform 0.3s ease-in-out;
3951        /* Glassmorphism effect for modern sites */
3952        backdrop-filter: blur(12px);
3953        -webkit-backdrop-filter: blur(12px);
3954      }
3955      /* Dark theme detection - if body/html has dark class or data attribute */
3956      .dark #wpconvert-mobile-nav,
3957      [data-theme="dark"] #wpconvert-mobile-nav,
3958      html.dark #wpconvert-mobile-nav {
3959        background: hsl(var(--background, 222 47% 11%));
3960        color: hsl(var(--foreground, 210 40% 98%));
3961      }
3962      
3963      /* Ensure menu content is clickable and ABOVE backdrop */
3964      /* Note: Do NOT set position on #wpconvert-mobile-nav here - it must stay fixed */
3965      #wpconvert-mobile-nav .nav-inner,
3966      #wpconvert-mobile-nav a,
3967      #wpconvert-mobile-nav button {
3968        pointer-events: auto !important;
3969        position: relative;
3970        z-index: 1;
3971      }
3972      /* Slide from RIGHT (default - for right-side hamburger buttons) */
3973      #wpconvert-mobile-nav.slide-right {
3974        right: 0;
3975        left: auto;
3976        border-left: 1px solid hsl(var(--border, 0 0% 80%) / 0.3);
3977        box-shadow: -4px 0 15px rgba(0, 0, 0, 0.2);
3978        transform: translateX(100%);
3979      }
3980      /* Slide from LEFT (for left-side hamburger buttons) */
3981      #wpconvert-mobile-nav.slide-left {
3982        left: 0;
3983        right: auto;
3984        border-right: 1px solid hsl(var(--border, 0 0% 80%) / 0.3);
3985        box-shadow: 4px 0 15px rgba(0, 0, 0, 0.2);
3986        transform: translateX(-100%);
3987      }
3988      /* Only show mobile nav when explicitly opened via JavaScript */
3989      /* Higher specificity to override slide-right/slide-left transforms */
3990      #wpconvert-mobile-nav.open,
3991      #wpconvert-mobile-nav.slide-right.open,
3992      #wpconvert-mobile-nav.slide-left.open {
3993        display: block !important;
3994        visibility: visible !important;
3995        opacity: 1 !important;
3996        transform: translateX(0) !important;
3997      }
3998      #wpconvert-mobile-nav .nav-inner {
3999        padding: 1rem;
4000        padding-top: 3.5rem;
4001      }
4002      #wpconvert-mobile-nav .close-btn {
4003        position: absolute;
4004        top: 1rem;
4005        right: 1rem;
4006        background: none;
4007        border: none;
4008        cursor: pointer;
4009        padding: 0.5rem;
4010        color: inherit;
4011      }
4012      #wpconvert-mobile-nav ul {
4013        list-style: none;
4014        padding: 0;
4015        margin: 0;
4016        display: flex;
4017        flex-direction: column;
4018        gap: 0;
4019      }
4020      #wpconvert-mobile-nav li {
4021        margin: 0;
4022        border-bottom: 1px solid hsl(var(--border, 0 0% 80%) / 0.15);
4023      }
4024      #wpconvert-mobile-nav li:last-child {
4025        border-bottom: none;
4026      }
4027      #wpconvert-mobile-nav a {
4028        display: block;
4029        padding: 0.875rem 0.5rem;
4030        text-decoration: none;
4031        color: hsl(var(--foreground, 0 0% 10%));
4032        transition: background-color 0.2s;
4033      }
4034      #wpconvert-mobile-nav a:hover {
4035        background: hsl(var(--accent, 0 0% 95%) / 0.5);
4036      }
4037      .dark #wpconvert-mobile-nav a,
4038      [data-theme="dark"] #wpconvert-mobile-nav a {
4039        color: hsl(var(--foreground, 210 40% 98%));
4040      }
4041      .dark #wpconvert-mobile-nav a:hover,
4042      [data-theme="dark"] #wpconvert-mobile-nav a:hover {
4043        background: hsl(var(--accent, 217 33% 17%) / 0.5);
4044      }
4045      /* Current menu item highlighting */
4046      #wpconvert-mobile-nav .current-menu-item > a,
4047      #wpconvert-mobile-nav .current_page_item > a {
4048        color: hsl(var(--primary, 217 91% 60%));
4049        background: hsl(var(--primary, 217 91% 60%) / 0.1);
4050      }
4051      body.mobile-nav-open {
4052        overflow: hidden;
4053      }
4054      /* Mobile nav backdrop - hidden by default, MUST be below the menu */
4055      #wpconvert-mobile-nav-backdrop {
4056        display: none !important;
4057        position: fixed !important; /* !important needed: framework CSS sets nav/div to relative !important */
4058        inset: 0 !important;
4059        background: rgba(0, 0, 0, 0.4);
4060        z-index: 99998 !important; /* Below menu (99999) but above everything else */
4061      }
4062      body.mobile-nav-open #wpconvert-mobile-nav-backdrop {
4063        display: block !important;
4064      }
4065      /* Universal Search Modal - works for any site with search icon */
4066      #wpconvert-search-modal {
4067        display: none;
4068        position: fixed !important;
4069        inset: 0;
4070        z-index: 100000;
4071        padding: 1rem;
4072        padding-top: 15vh;
4073      }
4074      #wpconvert-search-modal.open {
4075        display: flex;
4076        flex-direction: column;
4077        align-items: center;
4078      }
4079      #wpconvert-search-backdrop {
4080        position: absolute;
4081        inset: 0;
4082        background: rgba(0, 0, 0, 0.5);
4083        backdrop-filter: blur(4px);
4084        -webkit-backdrop-filter: blur(4px);
4085      }
4086      #wpconvert-search-container {
4087        position: relative;
4088        width: 100%;
4089        max-width: 32rem;
4090        background: hsl(var(--background, 0 0% 100%));
4091        border-radius: 0.75rem;
4092        box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
4093        overflow: hidden;
4094        animation: wpconvert-search-slide-down 0.2s ease-out;
4095      }
4096      @keyframes wpconvert-search-slide-down {
4097        from { opacity: 0; transform: translateY(-10px) scale(0.98); }
4098        to { opacity: 1; transform: translateY(0) scale(1); }
4099      }
4100      .dark #wpconvert-search-container {
4101        background: hsl(var(--background, 222 47% 11%));
4102      }
4103      #wpconvert-search-header {
4104        display: flex;
4105        align-items: center;
4106        padding: 1rem;
4107        border-bottom: 1px solid hsl(var(--border, 0 0% 80%) / 0.3);
4108      }
4109      #wpconvert-search-icon {
4110        flex-shrink: 0;
4111        margin-right: 0.75rem;
4112        color: hsl(var(--muted-foreground, 0 0% 45%));
4113      }
4114      #wpconvert-search-input {
4115        flex: 1;
4116        border: none;
4117        outline: none;
4118        background: transparent;
4119        font-size: 1rem;
4120        color: hsl(var(--foreground, 0 0% 10%));
4121      }
4122      #wpconvert-search-input::placeholder {
4123        color: hsl(var(--muted-foreground, 0 0% 45%));
4124      }
4125      .dark #wpconvert-search-input {
4126        color: hsl(var(--foreground, 210 40% 98%));
4127      }
4128      #wpconvert-search-close {
4129        flex-shrink: 0;
4130        padding: 0.5rem;
4131        margin-left: 0.5rem;
4132        background: none;
4133        border: none;
4134        cursor: pointer;
4135        color: hsl(var(--muted-foreground, 0 0% 45%));
4136        border-radius: 0.375rem;
4137        transition: background-color 0.2s;
4138      }
4139      #wpconvert-search-close:hover {
4140        background: hsl(var(--accent, 0 0% 95%) / 0.5);
4141      }
4142      #wpconvert-search-results {
4143        max-height: 50vh;
4144        overflow-y: auto;
4145        padding: 0.5rem;
4146      }
4147      #wpconvert-search-results:empty::after {
4148        content: "Type to search...";
4149        display: block;
4150        padding: 1rem;
4151        text-align: center;
4152        color: hsl(var(--muted-foreground, 0 0% 45%));
4153        font-size: 0.875rem;
4154      }
4155      .wpconvert-search-result {
4156        display: block;
4157        padding: 0.75rem 1rem;
4158        text-decoration: none;
4159        color: hsl(var(--foreground, 0 0% 10%));
4160        border-radius: 0.375rem;
4161        transition: background-color 0.2s;
4162      }
4163      .wpconvert-search-result:hover {
4164        background: hsl(var(--accent, 0 0% 95%) / 0.5);
4165      }
4166      .wpconvert-search-result-title {
4167        font-weight: 500;
4168        margin-bottom: 0.25rem;
4169      }
4170      .wpconvert-search-result-url {
4171        font-size: 0.75rem;
4172        color: hsl(var(--muted-foreground, 0 0% 45%));
4173      }
4174      .dark .wpconvert-search-result {
4175        color: hsl(var(--foreground, 210 40% 98%));
4176      }
4177      #wpconvert-search-hint {
4178        padding: 0.75rem 1rem;
4179        font-size: 0.75rem;
4180        color: hsl(var(--muted-foreground, 0 0% 45%));
4181        border-top: 1px solid hsl(var(--border, 0 0% 80%) / 0.3);
4182      }
4183      #wpconvert-search-hint kbd {
4184        display: inline-block;
4185        padding: 0.125rem 0.375rem;
4186        margin: 0 0.125rem;
4187        font-size: 0.7rem;
4188        font-family: inherit;
4189        background: hsl(var(--muted, 0 0% 95%));
4190        border-radius: 0.25rem;
4191        border: 1px solid hsl(var(--border, 0 0% 80%) / 0.5);
4192      }
4193      /* Gradient text fix - ensures background-clip works correctly */
4194      [style*="background"][style*="text-fill-color"] {
4195        -webkit-background-clip: text !important;
4196        background-clip: text !important;
4197      }
4198      /* CSS-only dropdown menus for converted React SPAs */
4199      /* Works with Tailwind's group/group-hover pattern */
4200      /* EC-NAV-072: Exclude .wpconvert-dropdown-panel — those have their own rules
4201         and transform: scaleY(1) would clobber -translate-x-1/2 centering on mega menus */
4202      .group:hover > div[class*="scale-y-0"]:not(.wpconvert-dropdown-panel),
4203      .group:focus-within > div[class*="scale-y-0"]:not(.wpconvert-dropdown-panel),
4204      .relative.group:hover > div[class*="invisible"]:not(.wpconvert-dropdown-panel),
4205      .relative.group:focus-within > div[class*="invisible"]:not(.wpconvert-dropdown-panel),
4206      .relative.group:hover > div[class*="opacity-0"]:not(.wpconvert-dropdown-panel),
4207      .relative.group:focus-within > div[class*="opacity-0"]:not(.wpconvert-dropdown-panel) {
4208        opacity: 1 !important;
4209        transform: scaleY(1) !important;
4210        visibility: visible !important;
4211      }
4212      /* Dropdown triggers */
4213      .group > button,
4214      .relative.group > button {
4215        cursor: pointer;
4216      }
4217      /* Keep dropdown visible while hovering on dropdown content */
4218      .group > div:hover:not(.wpconvert-dropdown-panel),
4219      .relative.group > div:hover:not(.wpconvert-dropdown-panel) {
4220        opacity: 1 !important;
4221        transform: scaleY(1) !important;
4222        visibility: visible !important;
4223      }
4224      /* WPConvert injected dropdown panels.
4225         EC-NAV-DROPDOWN-002: the outer .wpconvert-dropdown-wrapper now
4226         owns position + opacity + transition + the hoverable pt-2 gap
4227         between trigger and panel. The inner .wpconvert-dropdown-panel
4228         only carries visible surface styling. Hovering the parent
4229         .group keeps the wrapper visible, and the wrapper's bottom
4230         padding stays inside the hover box — so traversing the gap
4231         from trigger to panel never drops :hover. */
4232      .wpconvert-dropdown-wrapper {
4233        position: absolute;
4234        left: 0;
4235        top: 100%;
4236        padding-top: 0.5rem;
4237        opacity: 0;
4238        visibility: hidden;
4239        transition: opacity 0.2s, visibility 0.2s;
4240        z-index: 50;
4241      }
4242      .group:hover > .wpconvert-dropdown-wrapper,
4243      .group:focus-within > .wpconvert-dropdown-wrapper,
4244      .wpconvert-dropdown-wrapper:hover {
4245        opacity: 1 !important;
4246        visibility: visible !important;
4247      }
4248      .wpconvert-dropdown-panel {
4249        min-width: 12rem;
4250        border-radius: 0.375rem;
4251        background: hsl(var(--background, 0 0% 100%));
4252        border: 1px solid hsl(var(--border, 0 0% 80%));
4253        /* shadow-card equivalent: 1px ring + soft shadow defines edge
4254           against hero photos / colored backgrounds. */
4255        box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06), 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
4256      }
4257      /* EC-NAV-072: Mega menu centering is now handled by Tailwind
4258         utilities on the wrapper itself (left-1/2 + -translate-x-1/2).
4259         The defensive rule here just documents the contract for the
4260         inner panel class — visible styling is unchanged. */
4261      .wpconvert-mega-menu {
4262        /* outer .wpconvert-dropdown-wrapper provides centering */
4263      }
4264      /* EC-NAV-141: Flat dropdown with many links (no category grandchildren) — 2-column flow */
4265      .wpconvert-dropdown-panel.wpconvert-dropdown-panel--flat-mega {
4266        min-width: min(520px, 92vw);
4267        max-width: 92vw;
4268      }
4269      .wpconvert-dropdown-flat-mega-inner {
4270        column-count: 2;
4271        column-gap: 2.5rem;
4272      }
4273      .wpconvert-dropdown-flat-mega-inner > a {
4274        break-inside: avoid;
4275        -webkit-column-break-inside: avoid;
4276        page-break-inside: avoid;
4277      }
4278      .wpconvert-dropdown-panel a {
4279        display: block;
4280        padding: 0.5rem 1rem;
4281        font-size: 0.875rem;
4282        color: hsl(var(--foreground, 0 0% 10%));
4283        text-decoration: none;
4284        transition: background-color 0.2s;
4285      }
4286      .wpconvert-dropdown-panel a:hover {
4287        background: hsl(var(--accent, 0 0% 95%) / 0.5);
4288      }
4289      .dark .wpconvert-dropdown-panel {
4290        background: hsl(var(--card, 222 47% 11%));
4291      }
4292      .dark .wpconvert-dropdown-panel a {
4293        color: hsl(var(--foreground, 210 40% 98%));
4294      }
4295      .dark .wpconvert-dropdown-panel a:hover {
4296        background: hsl(var(--accent, 217 33% 17%) / 0.5);
4297      }
4298      /* EC-NAV-DROPDOWN-001: Ensure Tailwind group-hover utilities are always available.
4299         Bundled CSS from JIT-compiled Tailwind may not include these if they weren't used
4300         in the original source — but our dropdown walker/fallback menus rely on them.
4301         NOTE: this whole style block is emitted inside a JavaScript template literal, so
4302         a single colon escape would be silently dropped during interpolation. We write
4303         a double backslash to preserve the literal backslash that CSS requires to
4304         escape the colon in compound class names like .group-hover:opacity-100. */
4305      .group:hover .group-hover\:opacity-100 { opacity: 1; }
4306      .group:hover .group-hover\:visible { visibility: visible; }
4307      .group:hover .group-hover\:scale-y-100 { transform: scaleY(1); }
4308      /* WPC Mobile Menu Panel States (for preserved mobile menus) */
4309      [data-wpc-menu-panel] {
4310        /* Original styling preserved via classes */
4311      }
4312      [data-wpc-menu-panel].is-open {
4313        display: block !important;
4314        visibility: visible !important;
4315        opacity: 1 !important;
4316        transform: translateX(0) !important;
4317      }
4318      [data-wpc-menu-overlay] {
4319        display: none;
4320        position: fixed !important;
4321        inset: 0;
4322        background: rgba(0, 0, 0, 0.5);
4323        z-index: 99998;
4324      }
4325      [data-wpc-menu-overlay].is-open {
4326        display: block;
4327      }
4328      body.wpc-menu-open {
4329        overflow: hidden;
4330      }
4331      .wpc-mobile-menu {
4332        list-style: none;
4333        padding: 0;
4334        margin: 0;
4335      }
4336      .wpc-mobile-menu li {
4337        margin: 0;
4338      }
4339      .wpc-mobile-menu a {
4340        display: block;
4341        padding: 0.75rem 1rem;
4342        text-decoration: none;
4343      }</style></head><body class="wp-singular page-template page-template-page-consider-bexar-county page-template-page-consider-bexar-county-php page page-id-8585 wp-theme-cts-website-revised6-4-26-theme"><div id="wpconvert-search-modal" role="dialog" aria-modal="true" aria-label="Search"><div id="wpconvert-search-backdrop"></div><div id="wpconvert-search-container"><div id="wpconvert-search-header"> <svg id="wpconvert-search-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg> <input type="text" id="wpconvert-search-input" placeholder="Search pages..." autocomplete="off" /> <button id="wpconvert-search-close" type="button" aria-label="Close search"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" x2="6" y1="6" y2="18"></line><line x1="6" x2="18" y1="6" y2="18"></line></svg> </button></div><div id="wpconvert-search-results"></div><div id="wpconvert-search-hint">Press <kbd>Esc</kbd> to close &bull; <kbd>Ctrl</kbd>+<kbd>K</kbd> to open</div></div></div><div id="wpconvert-mobile-nav-backdrop" onclick="document.body.classList.remove('mobile-nav-open'); document.getElementById('wpconvert-mobile-nav').classList.remove('open');"></div><nav id="wpconvert-mobile-nav" class="closed" aria-label="Mobile Navigation"><div class="nav-inner"> <button class="close-btn" onclick="document.body.classList.remove('mobile-nav-open'); this.parentElement.parentElement.classList.remove('open');" aria-label="Close menu"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" x2="6" y1="6" y2="18"></line><line x1="6" x2="18" y1="6" y2="18"></line></svg> </button><ul id="menu-main-menu" class="mobile-menu"><li id="menu-item-8606" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8606"><a href="https://www.choosetosucceed.org/about/">About Choose to Succeed</a></li><li id="menu-item-8603" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8603"><a href="https://www.choosetosucceed.org/find-a-school/">Find a School</a></li><li id="menu-item-8605" class="menu-item menu-item-type-post_type menu-item-object-page current-menu-item page_item page-item-8585 current_page_item menu-item-8605"><a href="https://www.choosetosucceed.org/consider-bexar-county/" aria-current="page">Consider Bexar County</a></li><li id="menu-item-8604" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8604"><a href="https://www.choosetosucceed.org/contact/">Contact Us</a></li></ul></div></nav><nav class="nav"> <a href="https://www.choosetosucceed.org/" class="nav-logo"><img src="https://www.choosetosucceed.org/wp-content/themes/cts-website-revised6-4-26-theme/assets/images/CTS_Logo.jpg" alt="Choose to Succeed"></a><div class="nav-links"><span id="wpconvert-flat-nav" class="wpconvert-flat-nav-items" style="display:contents"><a href="https://www.choosetosucceed.org/about/" class="nav-link">About Choose to Succeed</a><a href="https://www.choosetosucceed.org/find-a-school/" class="nav-link">Find a School</a><a href="https://www.choosetosucceed.org/consider-bexar-county/" class="nav-link wpconvert-active-link current-menu-item">Consider Bexar County</a><a href="https://www.choosetosucceed.org/contact/" class="btn-p" data-wpc-id="wpc_navbtn_e8324f2a4b" data-wpc-editable="button">Contact Us</a></span></div> <a href="https://www.choosetosucceed.org/contact/" class="btn-p">Contact us</a></nav><main class="site-main"><div class="page-consider-bexar-county"><div class="page-content"><div class="section-hero"><div class="hero" id="hero"><div class="eyebrow" data-wpc-id="wpc_d83a5211bf" data-wpc-editable="heading">Consider Bexar County</div><h1 class="h1" data-wpc-id="wpc_c4847b178c" data-wpc-editable="heading">Why San Antonio is the<br>right place to <em>grow.</em></h1><p class="hero-sub" data-wpc-id="wpc_3bd682eb68" data-wpc-editable="text">Bexar County is one of the fastest-growing regions in the United States — with surging population, booming housing development, and a cost of living that makes sustainable operations possible. If you run a high-performing school network, San Antonio is ready for you.</p> <a href="https://www.choosetosucceed.org/contact/" class="btn-p" data-wpc-id="wpc_f3f593844e" data-wpc-editable="button">Start a conversation</a></div></div><div class="section-main"><div class="section" id="main"><div class="two-col"><div><div class="section-label" data-wpc-id="wpc_87596bee3c" data-wpc-editable="heading">Why operators choose San Antonio</div><div class="section-title" data-wpc-id="wpc_d346ff6ce1" data-wpc-editable="heading">The conditions are right</div><div class="inline-cards"><div class="inline-card"><div class="card-icon"><svg viewBox="0 0 24 24">
4343<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg></div><div><div class="card-title" data-wpc-id="wpc_df8d83d3fc" data-wpc-editable="heading">New housing = new families</div><div class="card-text">Rapidly developing suburbs in the north, south, east, and west of the city are generating demand for quality schools in neighborhoods that don't yet have them.</div></div></div><div class="inline-card"><div class="card-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><path d="M12 8v4l3 3"></path></svg></div><div><div class="card-title" data-wpc-id="wpc_d80fcb1ff2" data-wpc-editable="heading">Affordable to operate</div><div class="card-text">Lower real estate costs, competitive labor market, and Texas's lack of a state income tax make San Antonio a financially sustainable place to scale.</div></div></div><div class="inline-card"><div class="card-icon"><svg viewBox="0 0 24 24"><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 00-3-3.87"></path><path d="M16 3.13a4 4 0 010 7.75"></path></svg></div><div><div class="card-title" data-wpc-id="wpc_10e549ba5c" data-wpc-editable="heading">Unmet demand is documented</div><div class="card-text">Consistent growth across many of our portfolio schools demonstrates that families are actively seeking better options than what's currently available.</div></div></div><div class="inline-card"><div class="card-icon"><svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"></path></svg></div><div><div class="card-title" data-wpc-id="wpc_208a8887f1" data-wpc-editable="heading">Strong civic infrastructure</div><div class="card-text">A network of local foundations, philanthropists, and civic leaders is ready to support new quality schools with relationships, funding, and advocacy.</div></div></div></div></div><div><div class="section-label" data-wpc-id="wpc_b2bb7a90cf" data-wpc-editable="heading">How CtS supports new operators</div><div class="section-title" data-wpc-id="wpc_aa36982156" data-wpc-editable="heading">More than a funder — a true partner</div><p class="section-sub" data-wpc-id="wpc_1109a2b3aa" data-wpc-editable="text">We work actively with highly promising and proven school operators to help their expansion into Bexar County.</p><div class="step-list" style="margin-bottom:24px;"><div class="step-item"><div class="step-num">1</div><div><div class="step-title" data-wpc-id="wpc_eaefe51e44" data-wpc-editable="heading">Exploratory conversation</div><div class="step-text" data-wpc-id="wpc_9b886d90e6" data-wpc-editable="text">We learn about your model, results, and vision for San Antonio.</div></div></div><div class="step-item"><div class="step-num">2</div><div><div class="step-title" data-wpc-id="wpc_26cc18cae5" data-wpc-editable="heading">Community fit assessment</div><div class="step-text">We help identify neighborhoods with the greatest unmet need aligned to your model.</div></div></div><div class="step-item"><div class="step-num">3</div><div><div class="step-title" data-wpc-id="wpc_f1aa897c4b" data-wpc-editable="heading">Partnership agreement</div><div class="step-text" data-wpc-id="wpc_f0f01ff1b1" data-wpc-editable="text">We define shared goals, support commitments, and accountability structures.</div></div></div><div class="step-item"><div class="step-num">4</div><div><div class="step-title" data-wpc-id="wpc_d2c0bd104f" data-wpc-editable="heading">Launch & ongoing support</div><div class="step-text">Advocacy, enrollment support, and community introductions as you open your doors.</div></div></div></div><div class="checklist"><div class="checklist-title" data-wpc-id="wpc_d30526cd7b" data-wpc-editable="heading">What we look for in partners</div><div class="check-item"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg><span data-wpc-id="wpc_06d57d5d1c" data-wpc-editable="text">Demonstrated academic results, ideally in comparable demographics</span></div><div class="check-item"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg><span data-wpc-id="wpc_d76478dc92" data-wpc-editable="text">Strong financials and a sound financial plan</span></div><div class="check-item"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg><span data-wpc-id="wpc_1ae1397253" data-wpc-editable="text">Track record of leadership success growing schools, and a seasoned leader to launch new schools</span></div><div class="check-item"><svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"></polyline></svg><span data-wpc-id="wpc_a0328a6382" data-wpc-editable="text">A model that answers an unmet need, and realistic early assumptions about enrollment</span></div></div></div></div></div></div><div class="grid-layout-container section-main"><div class="section" id="main"><div class="section-label" data-wpc-id="wpc_10de7ecaad" data-wpc-editable="heading">Bexar County by the numbers</div><div class="section-title" data-wpc-id="wpc_1c6112d16c" data-wpc-editable="heading">A fast-growing region hungry for better school options</div><div class="bexar-grid"><div class="bexar-card"><div class="bexar-num" data-wpc-id="wpc_c991cfe875" data-wpc-editable="heading">~2.1M</div><div class="bexar-label" data-wpc-id="wpc_3eda50df40" data-wpc-editable="heading">residents in Bexar County</div><div class="bexar-fn">Source: <a href="https://www.texas-demographics.com/bexar-county-demographics" target="_blank" data-wpc-id="wpc_a2abe70da8" data-wpc-editable="link">U.S. Census Bureau via Texas Demographics, 2024 ACS</a></div></div><div class="bexar-card"><div class="bexar-num" data-wpc-id="wpc_9f828ff38a" data-wpc-editable="heading">26%</div><div class="bexar-label" data-wpc-id="wpc_b669ef6b8c" data-wpc-editable="heading">population growth since 2010 — one of the fastest large counties in Texas</div><div class="bexar-fn">Source: <a href="https://worldpopulationreview.com/us-counties/texas/bexar-county" target="_blank" data-wpc-id="wpc_268c14f067" data-wpc-editable="link">World Population Review, U.S. Census Bureau data, 2026</a></div></div><div class="bexar-card"><div class="bexar-num" data-wpc-id="wpc_8eb758095c" data-wpc-editable="heading">42,500+</div><div class="bexar-label" data-wpc-id="wpc_32e6e62fb7" data-wpc-editable="heading">new private housing units permitted 2022–2025&nbsp;</div><div class="bexar-fn">Source: <a href="https://fred.stlouisfed.org/series/BPPRIV048029" target="_blank" data-wpc-id="wpc_7dfede1fb4" data-wpc-editable="link">U.S. Census Bureau, Building Permits Survey via FRED, 2025</a></div></div><div class="bexar-card"><div class="bexar-num" data-wpc-id="wpc_b433566300" data-wpc-editable="heading">9% below</div><div class="bexar-label" data-wpc-id="wpc_59cc8b04f5" data-wpc-editable="heading">national average cost of living. It gets better: housing is 25% cheaper than the U.S. average</
4343div><div class="bexar-fn">Source: <a href="https://www.rentcafe.com/cost-of-living-calculator/us/tx/bexar-county/san-antonio/" target="_blank" data-wpc-id="wpc_7731aad44b" data-wpc-editable="link">RentCafe Cost of Living Index, 2026</a></div></div><div class="bexar-card"><div class="bexar-num" data-wpc-id="wpc_4f24b2843e" data-wpc-editable="heading">361,278</div><div class="bexar-label" data-wpc-id="wpc_d09a82d593" data-wpc-editable="heading">K–12 students enrolled in Bexar County public schools (and growing)</div><div class="bexar-fn">Source: <a href="https://www.publicschoolreview.com/texas/bexar-county" target="_blank" data-wpc-id="wpc_015c08b9e6" data-wpc-editable="link">Public School Review, citing TEA / NCES data, 2025</a></div></div><div class="bexar-card"><div class="bexar-num" data-wpc-id="wpc_d6522285ca" data-wpc-editable="heading">34.8</div><div class="bexar-label" data-wpc-id="wpc_d476ed59b7" data-wpc-editable="heading">median age — one of the youngest large cities in Texas</div><div class="bexar-fn">Source: <a href="https://worldpopulationreview.com/us-counties/texas/bexar-county" target="_blank" data-wpc-id="wpc_5f5fda0985" data-wpc-editable="link">World Population Review, U.S. Census Bureau data, 2026</a></div></div></div></div></div></div></div></main><footer class="site-footer"><div class="footer-l">© 2026 Choose to Succeed · 700 N. St. Mary's Street, Suite 875, San Antonio, TX 78205</div><div class="footer-links"> <a href="https://www.choosetosucceed.org/" data-wpc-id="wpc_93577c7077" data-wpc-editable="link">School choice</a> <a href="https://www.choosetosucceed.org/about/" data-wpc-id="wpc_e29658b18d" data-wpc-editable="link">About CtS</a> <a href="https://www.choosetosucceed.org/find-a-school/" data-wpc-id="wpc_416166cf12" data-wpc-editable="link">Find a school</a> <a href="https://www.choosetosucceed.org/consider-bexar-county/" data-wpc-id="wpc_a84e9c4f99" data-wpc-editable="link">Consider Bexar County</a></div></footer> 
4343<script type="speculationrules">{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/cts-website-revised6-4-26-theme/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]}</script>
4343 
4343<script id="wp-hooks-js" src="https://www.choosetosucceed.org/wp-includes/js/dist/hooks.min.js?ver=7496969728ca0f95732d"></script>
4343 
4343<script id="wp-i18n-js" src="https://www.choosetosucceed.org/wp-includes/js/dist/i18n.min.js?ver=781d11515ad3d91786ec"></script>
4343 
4343<script id="wp-i18n-js-after">wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ 'ltr' ] } );
4344//# sourceURL=wp-i18n-js-after</script>
4344 
4344<script id="contact-form-7-js-before">var wpcf7 = {
4345    "api": {
4346        "root": "https:\/\/www.choosetosucceed.org\/wp-json\/",
4347        "namespace": "contact-form-7\/v1"
4348    }
4349};
4350//# sourceURL=contact-form-7-js-before</script>
4350 
4350<script id="wp-emoji-settings" type="application/json">{"baseUrl":"https://s.w.org/images/core/emoji/17.0.2/72x72/","ext":".png","svgUrl":"https://s.w.org/images/core/emoji/17.0.2/svg/","svgExt":".svg","source":{"concatemoji":"https://www.choosetosucceed.org/wp-includes/js/wp-emoji-release.min.js?ver=7.0.6"}}</script>
4350 
4350<script type="module">/*! This file is auto-generated */
4351var e="script#wp-emoji-settings",t=document.querySelector(e);if(!(t instanceof HTMLScriptElement))throw new Error("Element missing: "+e);const r=JSON.parse(t.text),s=(window._wpemojiSettings=r,"wpEmojiSettingsSupports"),o=["flag","emoji"];function i(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(s,JSON.stringify(t))}catch(e){}}function c(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0);const r=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data);return t.every((e,t)=>e===r[t])}function p(e,t){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var n=e.getImageData(16,16,1,1);for(let e=0;e<n.data.length;e++)if(0!==n.data[e])return!1;return!0}function u(e,t,n,r){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\udde8\ud83c\uddf6","\ud83c\udde8\u200b\ud83c\uddf6")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!r(e,"\ud83e\u1fac8")}return!1}function f(e,t,n,r){let a;const s=(a="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):document.createElement("canvas")).getContext("2d",{willReadFrequently:!0}),o=(s.textBaseline="top",s.font="600 32px Arial",{});return e.forEach(e=>{o[e]=t(s,e,n,r)}),o}function a(e){var t=document.createElement("script");t.src=e,t.defer=!0,document.head.appendChild(t)}r.supports={everything:!0,everythingExceptFlag:!0},new Promise(t=>{let n=function(){try{var e=JSON.parse(sessionStorage.getItem(s));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(o),u.toString(),c.toString(),p.toString()].join(",")+"));",r=new Blob([e],{type:"text/javascript"});const a=new Worker(URL.createObjectURL(r),{name:"wpTestEmojiSupports"});return void(a.onmessage=e=>{i(n=e.data),a.terminate(),t(n)})}catch(e){}i(n=f(o,u,c,p))}t(n)}).then(e=>{for(const n in e)r.supports[n]=e[n],r.supports.everything=r.supports.everything&&r.supports[n],"flag"!==n&&(r.supports.everythingExceptFlag=r.supports.everythingExceptFlag&&r.supports[n]);var t;r.supports.everythingExceptFlag=r.supports.everythingExceptFlag&&!r.supports.flag,r.supports.everything||((t=r.source||{}).concatemoji?a(t.concatemoji):t.wpemoji&&t.twemoji&&(a(t.twemoji),a(t.wpemoji)))});
4352//# sourceURL=https://www.choosetosucceed.org/wp-includes/js/wp-emoji-loader.min.js</script>
4352 
4352<script id="wpconvert-video-lightbox">(function() {
4353        'use strict';
4354        
4355        function getEmbedUrl(rawUrl) {
4356            var url = (rawUrl || '').trim();
4357            if (!url || /\bexample\d*\b/i.test(url)) return '';
4358            
4359            if (url.indexOf('vimeo.com') !== -1) {
4360                var vm = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
4361                return vm ? 'https://player.vimeo.com/video/' + vm[1] + '?autoplay=1&title=0&byline=0&portrait=0&dnt=1' : '';
4362            }
4363            if (url.indexOf('youtube.com') !== -1 || url.indexOf('youtu.be') !== -1) {
4364                var yt = url.match(/youtu\.be\/([^?&\/]+)/) ||
4365                         url.match(/[?&]v=([^?&\/]+)/) ||
4366                         url.match(/youtube\.com\/embed\/([^?&\/]+)/);
4367                return yt ? 'https://www.youtube-nocookie.com/embed/' + yt[1] + '?autoplay=1&rel=0&modestbranding=1' : '';
4368            }
4369            return url;
4370        }
4371        
4372        function createModal() {
4373            if (document.getElementById('wpconvert-video-modal')) return;
4374            var overlay = document.createElement('div');
4375            overlay.id = 'wpconvert-video-modal';
4376            overlay.style.cssText = 'display:none;position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.85);backdrop-filter:blur(4px);align-items:center;justify-content:center;padding:2rem;';
4377            overlay.innerHTML = '<div style="position:relative;width:100%;max-width:960px;"><button id="wpconvert-video-close" style="position:absolute;top:-40px;right:0;background:none;border:none;color:#fff;font-size:28px;cursor:pointer;padding:4px 8px;z-index:1;" aria-label="Close video">&times;</button><div style="position:relative;width:100%;padding-top:56.25%;background:#000;border-radius:8px;overflow:hidden;"><iframe id="wpconvert-video-frame" style="position:absolute;top:0;left:0;width:100%;height:100%;border:none;" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture;fullscreen" allowfullscreen></iframe></div></div>';
4378            document.body.appendChild(overlay);
4379            
4380            var closeBtn = document.getElementById('wpconvert-video-close');
4381            closeBtn.addEventListener('click', closeModal);
4382            overlay.addEventListener('click', function(e) {
4383                if (e.target === overlay) closeModal();
4384            });
4385            document.addEventListener('keydown', function(e) {
4386                if (e.key === 'Escape') closeModal();
4387            });
4388        }
4389        
4390        function openModal(videoUrl) {
4391            var modal = document.getElementById('wpconvert-video-modal');
4392            var iframe = document.getElementById('wpconvert-video-frame');
4393            if (!modal || !iframe) return;
4394            var embedUrl = getEmbedUrl(videoUrl);
4395            if (!embedUrl) return;
4396            iframe.src = embedUrl;
4397            modal.style.display = 'flex';
4398            document.body.style.overflow = 'hidden';
4399        }
4400        
4401        function closeModal() {
4402            var modal = document.getElementById('wpconvert-video-modal');
4403            var iframe = document.getElementById('wpconvert-video-frame');
4404            if (!modal) return;
4405            modal.style.display = 'none';
4406            if (iframe) iframe.src = '';
4407            document.body.style.overflow = '';
4408        }
4409        
4410        function init() {
4411            if (window.location.search.indexOf('wpc_edit=1') !== -1) return;
4412            var cardsFound = 0;
4413            
4414            // Strategy 1: Cards with data-video-url (set during snapshot extraction)
4415            document.querySelectorAll('[data-video-url]').forEach(function(card) {
4416                var videoUrl = card.getAttribute('data-video-url');
4417                if (!videoUrl) return;
4418                card.style.cursor = 'pointer';
4419                card.addEventListener('click', function(e) {
4420                    e.preventDefault();
4421                    e.stopPropagation();
4422                    openModal(videoUrl);
4423                });
4424                cardsFound++;
4425            });
4426            
4427            // Strategy 2: Cards with play button SVGs but no data-vi
4427deo-url
4428            // Auto-detect video URLs from thumbnail image sources
4429            var playIcons = document.querySelectorAll('.lucide-play, [class*="lucide-play"]');
4430            playIcons.forEach(function(icon) {
4431                var card = icon.closest('[class*="cursor-pointer"]') ||
4432                           icon.closest('[class*="card"]') ||
4433                           icon.closest('[class*="group"]');
4434                if (!card || card.hasAttribute('data-video-url') || card.hasAttribute('data-wpconvert-video-bound')) return;
4435                
4436                var img = card.querySelector('img');
4437                if (!img) return;
4438                var src = img.getAttribute('src') || '';
4439                
4440                var videoUrl = '';
4441                // YouTube thumbnail -> YouTube embed
4442                var ytMatch = src.match(/img\.youtube\.com\/vi\/([a-zA-Z0-9_-]+)\//);
4443                if (ytMatch) {
4444                    videoUrl = 'https://www.youtube.com/watch?v=' + ytMatch[1];
4445                }
4446                // Vimeo CDN thumbnail — can't derive URL, skip
4447                
4448                if (videoUrl) {
4449                    card.setAttribute('data-wpconvert-video-bound', 'true');
4450                    card.addEventListener('click', function(e) {
4451                        e.preventDefault();
4452                        e.stopPropagation();
4453                        openModal(videoUrl);
4454                    });
4455                    cardsFound++;
4456                }
4457            });
4458            
4459            if (cardsFound > 0) {
4460                createModal();
4461                console.log('[WPConvert] Video lightbox initialized for ' + cardsFound + ' card(s)');
4462            }
4463        }
4464        
4465        if (document.readyState === 'loading') {
4466            document.addEventListener('DOMContentLoaded', init);
4467        } else {
4468            init();
4469        }
4470    })();</script>
4470 
4470<script>(function() {
4471        'use strict';
4472        
4473        function init() {
4474            // Find button groups that look like tab selectors
4475            // Pattern: multiple sibling buttons in a flex container with gap
4476            var containers = document.querySelectorAll('div[class*="flex"][class*="gap"]');
4477            var tabGroupsInitialized = 0;
4478            
4479            containers.forEach(function(container, containerIndex) {
4480                var buttons = container.querySelectorAll(':scope > button');
4481                
4482                // Need at least 2 buttons to be a tab selector
4483                if (buttons.length < 2) return;
4484                
4485                // Check if buttons have similar structure (tab selector pattern)
4486                // Only compare SIZE classes (w-, h-), ignore color/styling differences
4487                var hasSimilarButtons = true;
4488                var firstButtonSizeClasses = buttons[0].className.split(' ').filter(function(c) {
4489                    // Only check size classes - ignore border colors, bg colors, hover states
4490                    return /^(w-[0-9]+|h-[0-9]+)$/.test(c);
4491                }).sort().join(' ');
4492                
4493                buttons.forEach(function(btn) {
4494                    var btnSizeClasses = btn.className.split(' ').filter(function(c) {
4495                        return /^(w-[0-9]+|h-[0-9]+)$/.test(c);
4496                    }).sort().join(' ');
4497                    if (btnSizeClasses !== firstButtonSizeClasses) {
4498                        hasSimilarButtons = false;
4499                    }
4500                });
4501                
4502                // Also require that buttons have size classes (not just any flex container)
4503                if (!firstButtonSizeClasses || !hasSimilarButtons) return;
4504                
4505                // Skip if already handled (navigation buttons with < > arrows)
4506                var hasNavArrows = container.querySelector('svg[class*="chevron-left"], svg[class*="chevron-right"]');
4507                var isNavContainer = container.querySelectorAll(':scope > button').length === 2 && hasNavArrows;
4508                if (isNavContainer) return;
4509                
4510                // Find the active button (has gold/primary styling)
4511                var activeButton = null;
4512                var activeIndex = 0;
4513                buttons.forEach(function(btn, index) {
4514                    if (/border-gold|bg-gold|border-primary|bg-primary/.test(btn.className)) {
4515                        activeButton = btn;
4516                        activeIndex = index;
4517                    }
4518                });
4519                
4520                // Find associated content area
4521                // Look for text that shows selected item info
4522                // It could be: sibling of container, sibling of parent, or in ancestor section
4523                var infoText = null;
4524                var searchContext = container.parentElement;
4525                
4526                // Try parent's siblings first (common pattern)
4527                if (searchContext && searchContext.parentElement) {
4528                    var grandparent = searchContext.parentElement;
4529                    infoText = grandparent.querySelector('div[class*="text-center"] p[class*="text-gold"], div[class*="text-center"] p[class*="font-serif"]');
4530                }
4531                
4532                // If not found, try the whole section
4533                if (!infoText) {
4534                    var section = container.closest('section');
4535                    if (section) {
4536                        infoText = section.querySelector('div[class*="text-center"][class*="mt-"] p[class*="text-gold"], div[class*="text-center"][class*="mt-"] p[class*="font-serif"]');
4537                    }
4538                }
4539                
4540                // Mark as initialized
4541                container.setAttribute('data-wpconvert-tabs', 'true');
4542                
4543                // Store button labels for updating info text
4544                var buttonLabels = [];
4545                buttons.forEach(function(btn) {
4546                    buttonLabels.push(btn.textContent.trim());
4547                });
4548                
4549                // Add click handlers to each button
4550                buttons.forEach(function(btn, index) {
4551                    btn.addEventListener('click', function(e) {
4552                        e.preventDefault();
4553                        e.stopPropagation();
4554                        
4555                        // Update active styling on all buttons
4556                        buttons.forEach(function(b, i) {
4557                            if (i === index) {
4558                                // Make this button active - use RegExp constructor to avoid escape issues
4559                                b.className = b.className
4560                                    .replace(new RegExp('border-white/[0-9]+', 'g'), 'border-gold')
4561                                    .replace(new RegExp('hover:border-white/[0-9]+', 'g'), '');
4562                                if (b.className.indexOf('bg-gold') === -1) {
4563                                    b.className = b.className.replace('border-gold', 'border-gold bg-gold/20');
4564                                }
4565                            } else {
4566                                // Make this button inactive
4567                                b.className = b.className
4568                                    .replace(/border-gold/g, 'border-white/20')
4569                                    .replace(new RegExp('bg-gold/[0-9]+', 'g'), '')
4570                                    .replace(new RegExp(' +', 'g'), ' ')
4571                                    .trim();
4572                                if (b.className.indexOf('hover:border') === -1) {
4573                                    b.className += ' hover:border-white/40';
4574                                }
4575                            }
4576                        });
4577                        
4578                        // Update info text if found
4579                        if (infoText && buttonLabels[index]) {
4580                            infoText.textContent = buttonLabels[index];
4581                        }
4582                        
4583                        // Store active index
4584                        container.setAttribute('data-active-index', index);
4585                    });
4586                });
4587                
4588                tabGroupsInitialized++;
4589            });
4590            
4591            if (tabGroupsInitialized > 0) {
4592                console.log('WPConvert Tab Selector: Initialized ' + tabGroupsInitialized + ' tab group(s)');
4593            }
4594        }
4595        
4596        // Initialize when DOM is ready
4597        if (document.readyState === 'loading') {
4598            document.addEventListener('DOMContentLoaded', init);
4599        } else {
4600            init();
4601        }
4602        
4603    })();</script>
4603 
4603<script>(function() {
4604        'use strict';
4605        
4606        function init() {
4607            // Find all range inputs that might be comparison sliders
4608            var rangeInputs = document.querySelectorAll('input[type="range"]');
4609            var slidersInitialized = 0;
4610            
4611            rangeInputs.forEach(function(input) {
4612                // Look for comparison slider pattern:
4613                // - Range input inside a container with overlapping elements
4614                // - Sibling or nearby element with clip-path styling
4615                var container = input.closest('div[class*="relative"], div[class*="overflow"]');
4616                if (!container) return;
4617                
4618                // Find elements that use clip-path (the "after" reveal layer)
4619                var clipPathElements = container.querySelectorAll('[style*="clip-path"], [style*="clipPath"]');
4620                if (clipPathElements.length === 0) return;
4621                
4622                // Find the divider line (vertical bar that moves with slider)
4623                var dividerLine = container.querySelector('[class*="bg-gold"], [class*="bg-primary"], div[style*="left:"]');
4624                
4625                // Mark as initialized
4626                input.setAttribute('data-wpconvert-comparison', 'true');
4627                
4628                // Make input visible and interactive
4629                input.style.opacity = '0';
4630                input.style.cursor = 'ew-resize';
4631                input.style.zIndex = '20';
4632                
4633                // Add input handler
4634                input.addEventListener('input', function(e) {
4635                    var value = e.target.value;
4636                    
4637                    // Update clip-path on reveal elements
4638                    clipPathElements.forEach(function(el) {
4639                        // clip-path: inset(0 RIGHT% 0 0) - RIGHT is 100 - value
4640                        el.style.clipPath = 'inset(0 ' + (100 - value) + '% 0 0)';
4641                    });
4642                    
4643                    // Update divider line position
4644                    if (dividerLine) {
4645                        dividerLine.style.left = value + '%';
4646                    }
4647                });
4648                
4649                // Trigger initial positioning based on current value
4650                input.dispatchEvent(new Event('input'));
4651                
4652                slidersInitialized++;
4653            });
4654            
4655            if (slidersInitialized > 0) {
4656                console.log('WPConvert Comparison Slider: Initialized ' + slidersInitialized + ' slider(s)');
4657            }
4658        }
4659        
4660        // Initialize when DOM is ready
4661        if (document.readyState === 'loading') {
4662            document.addEventListener('DOMContentLoaded', init);
4663        } else {
4664            init();
4665        }
4666        
4667    })();</script>
4667 
4667<script>(function() {
4668        'use strict';
4669        
4670        // EC-ACCORDION-003: Find the content panel for an accordion button.
4671        // Handles two DOM patterns:
4672        // 1. Direct sibling: <button/> <div class="overflow-hidden">  (custom accordions)
4673        // 2. Header-wrapped: <h3><button/></h3> <div role="region">   (Radix UI / shadcn)
4674        //
4675        // EC-ACCORDION-012: An accordion content panel always comes AFTER its
4676        // trigger and is real content (not an img/picture/video-only wrapper).
4677        // Otherwise we mis-detect image-card layouts where the CTA button is
4678        // the LAST child of the card (MenuCard "+ Tilfoj"): the old
4679        // third-branch loop returned the image wrapper at the TOP of the
4680        // card and the init collapsed it to height:0.
4681        function isImageOnlyPanel(el) {
4682            if (!el || !el.children) return false;
4683            var hasText = (el.textContent || '').replace(/\s+/g, '').length > 0;
4684            if (hasText) return false;
4685            var kids = el.children;
4686            if (!kids.length) return false;
4687            for (var i = 0; i < kids.length; i++) {
4688                var tag = kids[i].tagName;
4689                if (tag !== 'IMG' && tag !== 'PICTURE' && tag !== 'VIDEO' && tag !== 'SOURCE') {
4690                    return false;
4691                }
4692            }
4693            return true;
4694        }
4695        function findContentPanel(btn) {
4696            var sibling = btn.nextElementSibling;
4697            if (sibling && sibling.tagName === 'DIV') {
4698                if (isImageOnlyPanel(sibling)) return null;
4699                return sibling;
4700            }
4701            
4702            var parent = btn.parentElement;
4703            if (parent && /^H[1-6]$/.test(parent.tagName)) {
4704                var parentSibling = parent.nextElementSibling;
4705                if (parentSibling && parentSibling.tagName === 'DIV') {
4706                    if (isImageOnlyPanel(parentSibling)) return null;
4707                    return parentSibling;
4708                }
4709            }
4710            // EC-ACCORDION-012: third branch -- button has no nextElementSibling.
4711            // The original intent (button is first child of wrapper, wrapper
4712            // contains a hidden div) is enforced by ONLY returning DIVs that
4713            // come AFTER the button in DOM order. Image-only DIVs are skipped.
4714            if (parent && !sibling) {
4715                var kids = parent.children;
4716                var btnIdx = -1;
4717                for (var k = 0; k < kids.length; k++) {
4718                    if (kids[k] === btn) { btnIdx = k; break; }
4719                }
4720                if (btnIdx >= 0) {
4721                    for (var j = btnIdx + 1; j < kids.length; j++) {
4722                        if (kids[j].tagName === 'DIV' && !isImageOnlyPanel(kids[j])) {
4723                            return kids[j];
4724                        }
4725                    }
4726                }
4727            }
4728            return null;
4729        }
4730        
4731        function init() {
4732            var buttons = document.querySelectorAll('button');
4733            var accordionsInitialized = 0;
4734            
4735            buttons.forEach(function(btn) {
4736                // EC-ACCORDION-012: Image-card CTA buttons (e.g. MenuCard
4737                // + Tilfoj) are explicitly stamped at build time as
4738                // non-accordion triggers. Honour the marker here so the
4739                // accordion init can never wire them up regardless of
4740                // what findContentPanel would otherwise return.
4741                if (btn.getAttribute('data-wpc-no-accordion-trigger') === '1') return;
4742
4743                // EC-ACCORDION-007: Skip buttons with aria-controls pointing to existing
4744                // Radix content. The jQuery accordion handler already manages these via
4745                // aria-controls → role="region". Attaching a second handler on the
4746                // injected data-wpc-faq-answer div would show duplicate answers.
4747                var ariaCtrl = btn.getAttribute('aria-controls');
4748                if (ariaCtrl && document.getElementById(ariaCtrl)) return;
4749                
4750                var sibling = findContentPanel(btn);
4751                if (!sibling) return;
4752                
4753                var siblingClasses = sibling.className || '';
4754                var siblingStyle = sibling.getAttribute('style') || '';
4755                var hasHidden = sibling.hasAttribute('hidden');
4756                var hasRoleRegion = sibling.getAttribute('role') === 'regi
4756on';
4757                
4758                // EC-ACCORDION-005: Also check first child for overflow-hidden (grid-rows pattern)
4759                var firstChild = sibling.firstElementChild;
4760                var childClasses = firstChild ? (firstChild.className || '') : '';
4761                
4762                // Detect accordion pattern:
4763                // 1. Sibling has overflow-hidden class (common accordion pattern)
4764                // 2. Sibling has height: 0 or height: auto in inline style
4765                // 3. Sibling has opacity: 0 or opacity: 1 in inline style
4766                // EC-ACCORDION-003: Also detect Radix UI pattern (role="region" + hidden + data-state)
4767                // EC-ACCORDION-005: Tailwind grid-rows-[0fr] pattern (sibling is grid, child has overflow-hidden)
4768                var isGridRowsAccordion = /grid-rows-\[0fr\]|grid-rows-\[1fr\]/.test(siblingClasses);
4769                var hasChildOverflowHidden = /overflow-hidden/.test(childClasses);
4770                
4771                var isFaqAnswer = sibling.hasAttribute('data-wpc-faq-answer');
4772                var isAccordion = /overflow-hidden|collapse/i.test(siblingClasses) ||
4773                                  /height:[ ]*(0|auto)|opacity:[ ]*(0|1)/i.test(siblingStyle) ||
4774                                  (hasRoleRegion && (hasHidden || sibling.hasAttribute('data-state'))) ||
4775                                  (isGridRowsAccordion && hasChildOverflowHidden) ||
4776                                  isFaqAnswer;
4777                
4778                if (!isAccordion) return;
4779                
4780                // Check current state
4781                var isExpanded = false;
4782                if (isGridRowsAccordion) {
4783                    isExpanded = /grid-rows-\[1fr\]/.test(siblingClasses);
4784                } else if (hasHidden || /display:[ ]*none/i.test(siblingStyle)) {
4785                    isExpanded = false;
4786                } else if (/height:[ ]*auto|opacity:[ ]*1/i.test(siblingStyle) && 
4787                           !/height:[ ]*0|opacity:[ ]*0/i.test(siblingStyle)) {
4788                    isExpanded = true;
4789                }
4790                
4791                btn.setAttribute('data-wpconvert-accordion', 'true');
4792                btn.setAttribute('data-expanded', isExpanded ? 'true' : 'false');
4793                sibling.setAttribute('data-wpconvert-accordion-content', 'true');
4794                
4795                // EC-ACCORDION-005: Grid-rows accordion uses grid-template-rows for animation
4796                if (isGridRowsAccordion) {
4797                    sibling.setAttribute('data-wpconvert-grid-accordion', 'true');
4798                    sibling.style.transition = 'grid-template-rows 0.3s ease';
4799                    if (!isExpanded) {
4800                        sibling.style.gridTemplateRows = '0fr';
4801                    } else {
4802                        sibling.style.gridTemplateRows = '1fr';
4803                    }
4804                } else {
4805                    // EC-ACCORDION-003: Remove hidden attribute and display:none so CSS can control visibility.
4806                    // The accordion starts collapsed via height:0 + opacity:0 instead.
4807                    sibling.removeAttribute('hidden');
4808                    sibling.style.display = '';
4809                    
4810                    sibling.style.overflow = 'hidden';
4811                    sibling.style.transition = 'height 0.3s ease, opacity 0.3s ease';
4812                    
4813                    // EC-ACCORDION-004: Tailwind max-h-0 class sets max-height:0px which
4814                    // blocks expansion regardless of inline height. Override with a
4815                    // large value instead of 'none' — some browsers treat 'none' (the
4816                    // initial value) as removing the inline declaration, letting the
4817                    // class rule win.
4818                    if (/max-h-/.test(siblingClasses)) {
4819                        sibling.style.maxHeight = '9999px';
4820                    }
4821                    
4822                    if (!isExpanded) {
4823                        sibling.style.height = '0px';
4824                        sibling.style.opacity = '0';
4825                    } else {
4826                        sibling.style.height = 'auto';
4827                        sibling.style.opacity = '1';
4828                    }
4829                }
4830                
4831                btn.addEventListener('click', function(e) {
4832                    e.preventDefault();
4833                    
4834                    var currentlyExpanded = btn.getAttribute('data-expanded') === 'true';
4835                    var content = findContentPanel(btn);
4836                    if (!content) return;
4837                    
4838                    // EC-ACCORDION-005: Grid-rows toggle
4839                    if (content.hasAttribute('data-wpconvert-grid-accordion')) {
4840                        if (currentlyExpanded) {
4841                            content.style.gridTemplateRows = '0fr';
4842                            btn.setAttribute('data-expanded', 'false');
4843                        } else {
4844                            content.style.gridTemplateRows = '1fr';
4845                            btn.setAttribute('data-expanded', 'true');
4846                        }
4847                    } else if (currentlyExpanded) {
4848                        var currentHeight = content.scrollHeight || content.offsetHeight;
4849                        content.style.height = currentHeight + 'px';
4850                        content.offsetHeight;
4851                        content.style.height = '0px';
4852                        content.style.opacity = '0';
4853                        btn.setAttribute('data-expanded', 'false');
4854                    } else {
4855                        // EC-ACCORDION-004: Robust height measurement.
4856                        // Some browsers return scrollHeight=0 when height:0+overflow:hidden.
4857                        // Temporarily reveal to measure, then animate from 0.
4858                        var targetHeight = content.scrollHeight;
4859                        if (!targetHeight) {
4860                            content.style.transition = 'none';
4861                            content.style.height = 'auto';
4862                            content.style.maxHeight = '9999px';
4863                            content.style.opacity = '0';
4864                            targetHeight = content.scrollHeight || content.offsetHeight;
4865                            content.style.height = '0px';
4866                            content.offsetHeight; // force reflow
4867                            content.style.transition = 'height 0.3s ease, opacity 0.3s ease';
4868                        }
4869                        if (targetHeight > 0) {
4870                            content.style.height = targetHeight + 'px';
4871                        } else {
4872                            content.style.height = 'auto';
4873                        }
4874                        content.style.opacity = '1';
4875                        btn.setAttribute('data-expanded', 'true');
4876                        
4877                        setTimeout(function() {
4878                            if (btn.getAttribute('data-expanded') === 'true') {
4879                                content.style.height = 'auto';
4880                            }
4881                        }, 300);
4882                    }
4883                    
4884                    var plusIcon = btn.querySelector('.lucide-plus, [class*="plus"]');
4885                    var minusIcon = btn.querySelector('.lucide-minus, [class*="minus"]');
4886                    
4887                    if (plusIcon && minusIcon) {
4888                        if (currentlyExpanded) {
4889                            plusIcon.style.display = '';
4890                            minusIcon.style.display = 'none';
4891                        } else {
4892                            plusIcon.style.display = 'none';
4893                            minusIcon.style.display = '';
4894                        }
4895                    }
4896                    
4897                    var chevron = btn.querySelector('.lucide-chevron-down, .lucide-chevron-up, [class*="chevron"]');
4898                    if (chevron) {
4899                        chevron.style.transition = 'transform 0.3s ease';
4900                        chevron.style.transform = currentlyExpanded ? 'rotate(0deg)' : 'rotate(180deg)';
4901                    }
4902                });
4903                
4904                var plusIcon = btn.querySelector('.lucide-plus, [class*="plus"]');
4905                var minusIcon = btn.querySelector('.lucide-minus, [class*="minus"]');
4906                if (plusIcon && minusIcon) {
4907                    if (isExpanded) {
4908                        plusIcon.style.display = 'none';
4909                        minusIcon.style.display = '';
4910                    } else {
4911                        plusIcon.style.display = '';
4912                        minusIcon.style.display = 'none';
4913                    }
4914                }
4915                
4916                accordionsInitialized++;
4917            });
4918            
4919            if (accordionsInitialized > 0) {
4920                console.log('WPConvert Accordion: Initialized ' + accordionsInitialized + ' accordion(s)');
4921            }
4922            
4923            // EC-ACCORDION-007: Enhance <details>/<summary> elements for consistent FAQ styling
4924            // Sub-pages may have FAQ sections converted to native <details>/<summary>.
4925            // Add animated +/- icon toggle and smooth open/close transitions.
4926            var detailsEnhanced = 0;
4927            document.querySelectorAll('details').forEach(function(det) {
4928                if (det.hasAttribute('data-wpc-enhanced')) return;
4929                det.setAttribute('data-wpc-enhanced', 'true');
4930                var summary = det.querySelector('summary');
4931                if (!summary) return;
4932                // Add toggle icon if not already present
4933                var toggleSpan = summary.querySelector('.wpc-toggle-icon');
4934                if (!toggleSpan) {
4935                    var existingIcon = summary.querySelector('span:last-child');
4936                    if (existingIcon && /^[+-×]$/.test(existingIcon.textContent.trim())) {
4937                        toggleSpan = existingIcon;
4938                        toggleSpan.classList.add('wpc-toggle-icon');
4939                    } else {
4940                        toggleSpan = document.createElement('span');
4941                        toggleSpan.className = 'wpc-toggle-icon';
4942                        toggleSpan.textContent = '+';
4943                        toggleSpan.style.cssText = 'margin-left:auto;transition:transform 0.3s ease;font-size:1.25em;';
4944                        summary.appendChild(toggleSpan);
4945                    }
4946                }
4947                function updateIcon() {
4948                    if (det.open) {
4949                        toggleSpan.textContent = toggleSpan.textContent === '+' ? '−' : toggleSpan.textContent;
4950                        toggleSpan.style.transform = 'rotate(45deg)';
4951                    } else {
4952                        toggleSpan.textContent = toggleSpan.textContent === '−' ? '+' : toggleSpan.textContent;
4953                        toggleSpan.style.transform = 'rotate(0deg)';
4954                    }
4955                }
4956                det.addEventListener('toggle', updateIcon);
4957                updateIcon();
4958                detailsEnhanced++;
4959            });
4960            if (detailsEnhanced > 0) {
4961                console.log('WPConvert Accordion: Enhanced ' + detailsEnhanced + ' <details> element(s)');
4962            }
4963        }
4964        
4965        // Initialize when DOM is ready
4966        if (document.readyState === 'loading') {
4967            document.addEventListener('DOMContentLoaded', init);
4968        } else {
4969            init();
4970        }
4971        
4972    })();</script>
4972 
4972<script id="wpconvert-smooth-scroll">(function() {
4973        'use strict';
4974        
4975        // Configuration
4976        const CONFIG = {
4977            offset: 80,              // Offset from top (accounts for sticky headers)
4978            duration: 800,           // Scroll duration in ms
4979            easing: 'easeInOutCubic' // Easing function
4980        };
4981        
4982        // Easing functions
4983        const easings = {
4984            linear: function(t) { return t; },
4985            easeInOutCubic: function(t) { 
4986                return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; 
4987            },
4988            easeInOutQuad: function(t) {
4989                return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
4990            }
4991        };
4992        
4993        /**
4994         * Smooth scroll to target element
4995         */
4996        function smoothScrollTo(target, duration, offset, easing) {
4997            const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - offset;
4998            const startPosition = window.pageYOffset;
4999            const distance = targetPosition - startPosition;
5000            const easingFunction = easings[easing] || easings.easeInOutCubic;
5001            let startTime = null;
5002            
5003            function animation(currentTime) {
5004                if (startTime === null) startTime = currentTime;
5005                const timeElapsed = currentTime - startTime;
5006                const progress = Math.min(timeElapsed / duration, 1);
5007                const ease = easingFunction(progress);
5008                
5009                window.scrollTo(0, startPosition + distance * ease);
5010                
5011                if (timeElapsed < duration) {
5012                    requestAnimationFrame(animation);
5013                } else {
5014                    // Update URL hash without jumping
5015                    if (target.id && window.history && window.history.pushState) {
5016                        window.history.pushState(null, null, '#' + target.id);
5017                    }
5018                }
5019            }
5020            
5021            requestAnimationFrame(animation);
5022        }
5023        
5024        /**
5025         * Calculate header offset dynamically
5026         * Accounts for sticky/fixed headers
5027         */
5028        function getHeaderOffset() {
5029            const fixedElements = document.querySelectorAll('header, nav, [class*="sticky"], [class*="fixed"]');
5030            let maxHeight = CONFIG.offset;
5031            
5032            fixedElements.forEach(function(el) {
5033                const styles = window.getComputedStyle(el);
5034                const position = styles.position;
5035                
5036                if (position === 'fixed' || position === 'sticky') {
5037                    const rect = el.getBoundingClientRect();
5038                    // Only consider elements at top of page
5039                    if (rect.top < 100) {
5040                        maxHeight = Math.max(maxHeight, rect.height + 20);
5041                    }
5042                }
5043            });
5044            
5045            return maxHeight;
5046        }
5047        
5048        /**
5049         * Find target element by ID or data attribute
5050         * Handles cases where IDs were modified during conversion
5051         */
5052        function findTarget(hash) {
5053            // Remove # from hash
5054            const id = hash.replace('#', '');
5055            if (!id) return null;
5056            
5057            // Try direct ID match
5058            let target = document.getElementById(id);
5059            if (target) return target;
5060            
5061            // Try case-insensitive match (IDs might have been lowercased)
5062            const elements = document.querySelectorAll('[id]');
5063            for (let i = 0; i < elements.length; i++) {
5064                if (elements[i].id.toLowerCase() === id.toLowerCase()) {
5065                    return elements[i];
5066                }
5067            }
5068            
5069            // Try data-section attribute (common in converted SPAs)
5070            target = document.querySelector('[data-section="' + id + '"]');
5071            if (target) return target;
5072            
5073            // Try finding by heading text content (last resort)
5074            const headings = document.querySelectorAll('h1, h2, h3');
5075            const searchText = id.replace(/-/g, ' ').toLowerCase();
5076            for (let i = 0; i < headings.length; i++) {
5077                const headingText = headings[i].textContent.toLowerCase();
5078                if (headingText.includes(searchText)) {
5079                    return headings[i];
5080                }
5081            }
5082            
5083            return null;
5084        }
5085        
5086        /**
5087         * Handle anchor link clicks
5088         */
5089        function handleAnchorClick(e) {
5090            // EC-EDITOR-005: Skip when WPConvert editor is active to avoid scroll conflict
5091            if (window.location.search.indexOf('wpc_edit=1') !== -1) return;
5092
5093            const href = this.getAttribute('href');
5094            
5095            // Only handle anchor links (starting with #)
5096            if (!href || !href.startsWith('#')) return;
5097            
5098            // Skip empty hashes
5099            if (href === '#' || href === '#!') return;
5100            
5101            const target = findTarget(href);
5102            
5103            if (target) {
5104                e.preventDefault();
5105                
5106                // Get dynamic header offset
5107                const offset = getHeaderOffset();
5108                
5109                // Smooth scroll to target
5110                smoothScrollTo(target, CONFIG.duration, offset, CONFIG.easing);
5111                
5112                // Update focus for accessibility
5113                target.setAttribute('tabindex', '-1');
5114                target.focus();
5115            } else {
5116                // Target not found - log warning but don't break
5117                console.warn('WPConvert Smooth Scroll: Target not found for', href);
5118            }
5119        }
5120        
5121        /**
5122         * Initialize smooth scroll on page load
5123         */
5124        function init() {
5125            // Handle all anchor links
5126            const anchorLinks = document.querySelectorAll('a[href^="#"]');
5127            anchorLinks.forEach(function(link) {
5128                link.addEventListener('click', handleAnchorClick);
5129            });
5130            
5131            // CONSERVATIVE FALLBACK: Handle buttons with explicit data attributes
5132            // Only activates if button has data-scroll-to or similar attributes
5133            const scrollButtons = document.querySelectorAll('button[data-scroll-to], button[data-anchor]');
5134            scrollButtons.forEach(function(btn) {
5135                const scrollTarget = btn.getAttribute('data-scroll-to') || btn.getAttribute('data-anchor');
5136                if (scrollTarget) {
5137                    btn.addEventListener('click', function(e) {
5138                        const targetId = scrollTarget.replace('#', '');
5139                        const target = document.getElementById(targetId);
5140                        if (target) {
5141                            e.preventDefault();
5142                            const offset = getHeaderOffset();
5143                            smoothScrollTo(target, CONFIG.duration, offset, CONFIG.easing);
5144                            console.log('WPConvert Smooth Scroll: Button scrolled to #' + targetId);
5145                        }
5146                    });
5147                }
5148            });
5149            
5150            // Handle hash in URL on page load
5151            if (window.location.hash) {
5152                // Small delay to ensure page is fully loaded
5153                setTimeout(function() {
5154                    const target = findTarget(window.location.hash);
5155                    if (target) {
5156                        const offset = getHeaderOffset();
5157                        smoothScrollTo(target, CONFIG.duration, offset, CONFIG.easing);
5158                    }
5159                }, 100);
5160            }
5161        }
5162        
5163        // Initialize when DOM is ready
5164        if (document.readyState === 'loading') {
5165            document.addEventListener('DOMContentLoaded', init);
5166        } else {
5167            init();
5168        }
5169        
5170    })();</script>
5170 <style id="wpconvert-carousel-visibility-footer">.scroll-fade-up:has([aria-roledescription="carousel"]),
5171    .scroll-fade-up:has([data-wpconvert-carousel]),
5172    .scroll-fade-up:has([data-wpconvert-blog-filter-bar]) {
5173      opacity: 1 !important;
5174      transform: none !important;
5175      visibility: visible !important;
5176    }
5177    [role="region"][aria-roledescription="carousel"],
5178    [data-wpconvert-carousel="aria"] {
5179      opacity: 1 !important;
5180      visibility: visible !important;
5181    }</style> 
5181<script defer src="https://www.choosetosucceed.org/wp-content/cache/autoptimize/js/autoptimize_e2784d36383430c65bb2b3325d08b990.js"></script>
5181</body></html>

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