PageSourceSearch

https://seovaultai.com/assets/js/site.js?v=20260805-04

js seovaultai.com collected 2026-10-01 10:39:19 UTC 27,493 bytes, 742 lines download raw bytes

1(function () {
2    function onReady(callback) {
3        var called = false;
4
5        function runOnce() {
6            if (called) {
7                return;
8            }
9            called = true;
10            callback();
11        }
12
13        if (document.readyState === "loading") {
14            document.addEventListener("DOMContentLoaded", runOnce);
15            window.setTimeout(runOnce, 0);
16            return;
17        }
18        runOnce();
19    }
20
21    function getTheme() {
22        try {
23            var stored = window.localStorage.getItem("seovault-theme");
24            if (stored === "light" || stored === "dark") {
25                return stored;
26            }
27        } catch (error) {
28            // Storage can be unavailable in privacy-restricted browser contexts.
29        }
30        return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
31    }
32
33    function setTheme(theme) {
34        document.documentElement.setAttribute("data-theme", theme);
35        try {
36            window.localStorage.setItem("seovault-theme", theme);
37        } catch (error) {
38            // Keep the in-memory theme working when storage is unavailable.
39        }
40        var themeColor = document.querySelector('meta[name="theme-color"]');
41        if (themeColor) {
42            themeColor.setAttribute("content", theme === "light" ? "#f3f5f9" : "#07111f");
43        }
44    }
45
46    function toggleTheme() {
47        var current = document.documentElement.getAttribute("data-theme") || "dark";
48        setTheme(current === "dark" ? "light" : "dark");
49    }
50
51    function applyEnvironmentLinks() {
52        var config = window.SeoVaultSiteConfig;
53        if (!config) {
54            return;
55        }
56
57        var linkMap = {
58            "start-free": config.startFreeUrl,
59            login: config.loginUrl,
60            blog: config.blogUrl,
61            app: config.appOrigin,
62            pricing: config.pricingUrl,
63            features: config.featuresUrl,
64            billing: config.billingUrl
65        };
66
67        Object.keys(linkMap).forEach(function (key) {
68            var nodes = document.querySelectorAll('[data-link="' + key + '"]');
69            nodes.forEach(function (node) {
70                node.setAttribute("href", linkMap[key]);
71            });
72        });
73
74        document.querySelectorAll("[data-env-text]").forEach(function (node) {
75            node.textContent = config.environmentLabel;
76        });
77
78        if (config.environment !== "prod") {
79            var badge = document.querySelector("[data-env-badge]");
80            if (badge) {
81                badge.hidden = false;
82                badge.textContent = config.environment === "dev" ? "Dev preview" : "Local preview";
83            }
84        }
85    }
86
87    function trackMarketingEvent(eventName, payload) {
88        try {
89            var consent = window.CookieConsent;
90            if (consent && typeof consent.acceptedCategory === "function" &&
91                !consent.acceptedCategory("analytics")) {
92                return false;
93            }
94
95            if (typeof window.gtag === "function") {
96                window.gtag("event", eventName, payload || {});
97                return true;
98            }
99
100            if (Array.isArray(window.dataLayer)) {
101                window.dataLayer.push(Object.assign({ event: eventName }, payload || {}));
102                return true;
103            }
104        } catch (error) {
105            // Analytics must never interrupt the marketing page.
106        }
107
108        return false;
109    }
110
111    function initPluginProAnalytics() {
112        if (document.body.getAttribute("data-page") !== "pricing-plugin-pro") {
113            return;
114        }
115
116        var tracked = false;
117        var payload = {
118            surface: "marketing_pricing",
119            page_path: window.location.pathname
120        };
121
122        function trackPricingViewed() {
123            if (tracked || !trackMarketingEvent("plugin_pro_pricing_viewed", payload)) {
124                return;
125            }
126            tracked = true;
127        }
128
129        trackPricingViewed();
130        window.addEventListener("cc:onConsent", trackPricingViewed);
131        window.addEventListener("cc:onChange", trackPricingViewed);
132        document.addEventListener("cc:onConsent", trackPricingViewed);
133        document.addEventListener("cc:onChange", trackPricingViewed);
134    }
135
136    function applyCanonicalAndRobots() {
137        var config = window.SeoVaultSiteConfig;
138        if (!config) {
139            return;
140        }
141
142        var canonical = document.querySelector('link[rel="canonical"]');
143        if (canonical) {
144            canonical.setAttribute("href", config.siteOrigin + window.location.pathname);
145        }
146
147        var ogUrl = document.querySelector('meta[property="og:url"]');
148        if (ogUrl) {
149            ogUrl.setAttribute("content", config.siteOrigin + window.location.pathname);
150        }
151
152        var robots = document.querySelector('meta[name="robots"]');
153        if (!robots) {
154            robots = document.createElement("meta");
155            robots.setAttribute("name", "robots");
156            document.head.appendChild(robots);
157        }
158
159        if (config.environment === "prod") {
160            robots.setAttribute("content", "index,follow");
161        } else {
162            robots.setAttribute("content", "noindex,nofollow");
163        }
164    }
165
166    function initMenu() {
167        var toggle = document.querySelector("[data-menu-toggle]");
168        var panel = document.querySelector("[data-mobile-panel]");
169        if (!toggle || !panel) {
170            return;
171        }
172
173        toggle.addEventListener("click", function () {
174            var open = panel.classList.toggle("is-open");
175            toggle.classList.toggle("is-open", open);
176            toggle.setAttribute("aria-expanded", open ? "true" : "false");
177        });
178
179        panel.querySelectorAll("a").forEach(function (link) {
180            link.addEventListener("click", function () {
181                panel.classList.remove("is-open");
182                toggle.classList.remove("is-open");
183                toggle.setAttribute("aria-expanded", "false");
184            });
185        });
186    }
187
188    function initSeoMockupSwitcher() {
189        var root = document.querySelector("[data-seo-mockup]");
190        if (!root) {
191            return;
192        }
193
194        var mockup = root.closest(".wp-mockup") || root;
195        var triggers = Array.prototype.slice.call(mockup.querySelectorAll("[data-mockup-view-trigger]"));
196        var panels = Array.prototype.slice.call(root.querySelectorAll("[data-mockup-view-panel]"));
197        if (!triggers.length || !panels.length) {
198            return;
199        }
200
201        var current = root.getAttribute("data-mockup-view") || "general";
202        var timer = null;
203
204        function scheduleNext() {
205            if (timer) {
206                window.clearTimeout(timer);
207            }
208            timer = window.setTimeout(function () {
209                activate(current === "general" ? "seo-analyzer" : "general");
210            }, 5000);
211        }
212
213        function activate(view) {
214            var found = false;
215
216            triggers.forEach(function (trigger) {
217                var active = trigger.getAttribute("data-mockup-view-trigger") === view;
218                trigger.classList.toggle("current", active);
219                trigger.setAttribute("aria-pressed", active ? "true" : "false");
220            });
221
222            panels.forEach(function (panel) {
223                var active = panel.getAttribute("data-mockup-view-panel") === view;
224                panel.classList.toggle("is-active", active);
225                panel.setAttribute("aria-hidden", active ? "false" : "true");
226                if (active) {
227                    found = true;
228                }
229            });
230
231            if (!found) {
232                return;
233            }
234
235            current = view;
236            root.setAttribute("data-mockup-view", view);
237            root.scrollTop = 0;
238            scheduleNext();
239        }
240
241        triggers.forEach(function (trigger) {
242            trigger.addEventListener("click", function () {
243                activate(trigger.getAttribute("data-mockup-view-trigger"));
244            });
245        });
246
247        activate(current);
248    }
249
250    function initReveal() {
251        var items = document.querySelectorAll(".reveal");
252        if (!items.length) {
253            return;
254        }
255
256        var observer = new IntersectionObserver(function (entries) {
257            entries.forEach(function (entry) {
258                if (entry.isIntersecting) {
259                    entry.target.classList.add("is-visible");
260                    observer.unobserve(entry.target);
261                }
262            });
263        }, { threshold: 0.18 });
264
265        items.forEach(function (item) {
266            observer.observe(item);
267        });
268    }
269
270    function initCounters() {
271        var counters = document.querySelectorAll("[data-counter]");
272        if (!counters.length) {
273            return;
274        }
275
276        var observer = new IntersectionObserver(function (entries) {
277            entries.forEach(function (entry) {
278                if (!entry.isIntersecting) {
279                    return;
280                }
281
282                var element = entry.target;
283                var target = parseInt(element.getAttribute("data-counter"), 10);
284                var current = 0;
285                var duration = 1100;
286                var startTime = null;
287
288                function step(timestamp) {
289                    if (!startTime) {
290                        startTime = timestamp;
291                    }
292                    var progress = Math.min((timestamp - startTime) / duration, 1);
293                    current = Math.floor(target * progress);
294                    element.textContent = current;
295                    if (progress < 1) {
296                        window.requestAnimationFrame(step);
297                    } else {
298                        element.textContent = target;
299                    }
300                }
301
302                window.requestAnimationFrame(step);
303                observer.unobserve(element);
304            });
305        }, { threshold: 0.5 });
306
307        counters.forEach(function (counter) {
308            observer.observe(counter);
309        });
310    }
311
312    function renderPricingCards() {
313        var containers = document.querySelectorAll("[data-pricing-grid]");
314        var data = window.SeoVaultPricingData;
315        if (!containers.length || !data) {
316            return;
317        }
318
319        var plans = data.plans;
320
321        containers.forEach(function (container) {
322            container.innerHTML = plans
323                .map(function (plan) {
324                    var list = plan.features.map(function (feature) {
325                        return "<li>" + feature + "</li>";
326                    }).join("");
327
328                    var meta = plan.meta.map(function (item) {
329                        return '<span class="meta-chip">' + item + "</span>";
330                    }).join("");
331
332                    var priceMarkup = '';
333
334                    if (plan.regularPrice) {
335                        priceMarkup = '' +
336                            '<div class="price-sale">' +
337                                '<div class="price-regular">' +
338                                    '<span>Regular price</span>' +
339                                    '<s>' + plan.regularPrice + '/mo</s>' +
340                                '</div>' +
341                                '<div class="price-row"><strong>' + plan.price + '/mo</strong></div>' +
342                                '<span class="price-launch-label">Launch price</span>' +
343                            '</div>';
344                    } else {
345                        priceMarkup = '<div class="price-row"><strong>' + plan.price + "</strong><span>" + plan.period + "</span></div>";
346                    }
347
348                    return '' +
349                        '<article class="pricing-card ' + (plan.badge ? "featured" : "") + '">' +
350                            '<div class="pricing-card-head">' +
351                                (plan.badge ? '<span class="pricing-badge">' + plan.badge + "</span>" : "") +
352                                "<h3>" + plan.name + "</h3>" +
353                                "<p>" + plan.tagline + "</p>" +
354                            "</div>" +
355                            priceMarkup +
356                            '<div class="pricing-meta">' + meta + "</div>" +
357                            '<ul class="pricing-list">' + list + "</ul>" +
358                            '<a class="button ' + (plan.badge ? "primary" : "secondary") + '" data-link="' + plan.ctaKey + '" href="#">' + plan.ctaLabel + "</a>" +
359                        "</article>";
360                })
361                .join("");
362        });
363
364        applyEnvironmentLinks();
365    }
366
367    function renderPluginProPricingCards() {
368        var containers = document.querySelectorAll("[data-plugin-pro-pricing-grid]");
369        var data = window.SeoVaultPluginProPricingData;
370        if (!containers.length || !data || !data.plans) {
371            return;
372        }
373
374        containers.forEach(function (container) {
375            container.innerHTML = data.plans
376                .map(function (plan) {
377                    var list = (plan.features || []).map(function (feature) {
378                        return "<li>" + feature + "</li>";
379                    }).join("");
380
381                    var meta = (plan.meta || []).map(function (item) {
382                        return '<span class="meta-chip">' + item + "</span>";
383                    }).join("");
384
385                    var buttonClass = plan.badge ? "primary" : "secondary";
386                    var priceMarkup = plan.renewalPrice
387                        ? '<div class="price-sale">' +
388                            '<div class="price-row"><strong>' + plan.price + "</strong>" +
389                                (plan.period ? "<span>" + plan.period + "</span>" : "") +
390                            "</div>" +
391                            '<div class="price-renewal"><span>Then renews at</span><strong>' + plan.renewalPrice + "</strong></div>" +
392                            '<span class="price-launch-label">Founding price · first payment only</span>' +
393                          "</div>"
394                        : '<div class="price-row"><strong>' + plan.price + "</strong>" +
395                            (plan.period ? "<span>" + plan.period + "</span>" : "") +
396                          "</div>";
397                    var cta = plan.checkoutAvailable && plan.ctaHref
398                        ? '<a class="button ' + buttonClass + '" data-plugin-pro-plan-cta="' + plan.id + '" href="' + plan.ctaHref + '">' +
399                            plan.ctaLabel +
400                          "</a>"
401                        : '<button class="button ' + buttonClass + '" type="button" disabled aria-disabled="true" data-plugin-pro-plan-cta="' + plan.id + '">' +
402                            plan.ctaLabel +
403                          "</button>";
404
405                    return '' +
406                        '<article class="pricing-card ' + (plan.badge ? "featured" : "") + '" data-plugin-pro-plan="' + plan.id + '">' +
407                            '<div class="pricing-card-head">' +
408                                (plan.badge ? '<span class="pricing-badge">' + plan.badge + "</span>" : "") +
409                                "<h3>" + plan.name + "</h3>" +
410                                "<p>" + plan.tagline + "</p>" +
411                            "</div>" +
412                            priceMarkup +
413                            '<div class="pricing-meta">' + meta + "</div>" +
414                            '<ul class="pricing-list">
414' + list + "</ul>" +
415                            cta +
416                        "</article>";
417                })
418                .join("");
419        });
420    }
421
422    function renderComparisonTable() {
423        var container = document.querySelector("[data-pricing-table]");
424        var notesContainer = document.querySelector("[data-pricing-notes]");
425        var data = window.SeoVaultPricingData;
426        if (!data) {
427            return;
428        }
429
430        if (container) {
431            var header = data.plans.map(function (plan) {
432                return "<th>" + plan.name + "</th>";
433            }).join("");
434
435            var rows = data.comparisonRows.map(function (row) {
436                var values = row.values.map(function (value) {
437                    return "<td>" + value + "</td>";
438                }).join("");
439
440                return "<tr><td>" + row.label + "</td>" + values + "</tr>";
441            }).join("");
442
443            container.innerHTML = '' +
444                '<table class="comparison-table">' +
445                    "<thead><tr><th>Plan feature</th>" + header + "</tr></thead>" +
446                    "<tbody>" + rows + "</tbody>" +
447                "</table>";
448        }
449
450        if (notesContainer) {
451            notesContainer.innerHTML = window.SeoVaultPricingData.notes
452                .map(function (note) {
453                    return "<li>" + note + "</li>";
454                })
455                .join("");
456        }
457    }
458
459    function renderVisuals() {
460        var hero = document.querySelector('[data-visual="hero"]');
461        if (hero && window.SeoVaultVisuals) {
462            hero.innerHTML = window.SeoVaultVisuals.heroVisual();
463        }
464    }
465
466    function initGauge() {
467        var gauge = document.querySelector('[data-gauge-target]');
468        if (!gauge) return;
469
470        var numberEl = gauge.querySelector('[data-gauge-number]');
471        var target = parseInt(gauge.getAttribute('data-gauge-target'), 10);
472        var peak = 100;
473        var duration1 = 2800;
474        var duration2 = 1600;
475        var startTime = null;
476        var phase = 1;
477
478        function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }
479        function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
480
481        function setGauge(v) {
482            gauge.style.setProperty('--value', v);
483            if (numberEl) numberEl.textContent = Math.round(v);
484        }
485
486        function tick(now) {
487            if (!startTime) startTime = now;
488            if (phase === 1) {
489                var t = Math.min((now - startTime) / duration1, 1);
490                setGauge(easeOutCubic(t) * peak);
491                if (t >= 1) { phase = 2; startTime = now; }
492            } else {
493                var t2 = Math.min((now - startTime) / duration2, 1);
494                var eased = easeInOutCubic(t2);
495                setGauge(peak - eased * (peak - target));
496                if (t2 >= 1) { setGauge(target); return; }
497            }
498            requestAnimationFrame(tick);
499        }
500
501        var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
502        if (reduceMotion) {
503            setGauge(target);
504            return;
505        }
506
507        // Only start the animation when the gauge scrolls into view
508        var observer = new IntersectionObserver(function (entries) {
509            entries.forEach(function (entry) {
510                if (entry.isIntersecting) {
511                    observer.unobserve(gauge);
512                    startTime = null;
513                    phase = 1;
514                    requestAnimationFrame(tick);
515                }
516            });
517        }, { threshold: 0.4 });
518
519        observer.observe(gauge);
520    }
521
522    function initFaq() {
523        document.querySelectorAll(".faq-item").forEach(function (item) {
524            var trigger = item.querySelector(".faq-trigger");
525            var panel = item.querySelector(".faq-panel");
526            if (!trigger || !panel) {
527                return;
528            }
529
530            trigger.addEventListener("click", function () {
531                var expanded = trigger.getAttribute("aria-expanded") === "true";
532                trigger.setAttribute("aria-expanded", expanded ? "false" : "true");
533                panel.classList.toggle("is-open", !expanded);
534            });
535        });
536    }
537
538    function initLightbox() {
539        var triggers = document.querySelectorAll(".lightbox-trigger");
540        if (!triggers.length) {
541            return;
542        }
543
544        var overlay = document.createElement("div");
545        overlay.className = "lightbox-overlay";
546        overlay.setAttribute("role", "dialog");
547        overlay.setAttribute("aria-modal", "true");
548        overlay.setAttribute("aria-label", "Image preview");
549
550        var img = document.createElement("img");
551        overlay.appendChild(img);
552
553        var closeBtn = document.createElement("button");
554        closeBtn.className = "lightbox-close";
555        closeBtn.type = "button";
556        closeBtn.setAttribute("aria-label", "Close image preview");
557        closeBtn.textContent = "\u00d7";
558        overlay.appendChild(closeBtn);
559
560        document.body.appendChild(overlay);
561
562        function open(src, alt) {
563            img.src = src;
564            img.alt = alt || "";
565            overlay.classList.add("is-open");
566            document.body.style.overflow = "hidden";
567        }
568
569        function close() {
570            overlay.classList.remove("is-open");
571            document.body.style.overflow = "";
572        }
573
574        triggers.forEach(function (trigger) {
575            trigger.addEventListener("click", function (e) {
576                e.preventDefault();
577                var triggerImg = trigger.querySelector("img");
578                open(trigger.href, triggerImg ? triggerImg.alt : "");
579            });
580        });
581
582        closeBtn.addEventListener("click", close);
583        overlay.addEventListener("click", function (e) {
584            if (e.target !== img) {
585                close();
586            }
587        });
588        document.addEventListener("keydown", function (e) {
589            if (e.key === "Escape" && overlay.classList.contains("is-open")) {
590                close();
591            }
592        });
593    }
594
595    function initLanguageMarquee() {
596        var track = document.querySelector("[data-lang-marquee]");
597        var inner = document.querySelector("[data-lang-marquee-inner]");
598        if (!track || !inner) {
599            return;
600        }
601
602        var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
603        if (reduceMotion) {
604            return;
605        }
606
607        var sourceItems = Array.prototype.slice.call(inner.querySelectorAll("[data-lang-item]"));
608        if (!sourceItems.length) {
609            return;
610        }
611
612        var gap = 30;
613
614        function getItemTotalWidth(item) {
615            var style = window.getComputedStyle(item);
616            var marginLeft = parseFloat(style.marginLeft) || 0;
617            var marginRight = parseFloat(style.marginRight) || 0;
618            return item.offsetWidth + marginLeft + marginRight;
619        }
620
621        var loopWidth = 0;
622        sourceItems.forEach(function (item) {
623            loopWidth += getItemTotalWidth(item);
624        });
625        loopWidth += gap * sourceItems.length;
626
627        if (loopWidth <= 0) {
628            return;
629        }
630
631        while (inner.children.length > sourceItems.length) {
632            inner.removeChild(inner.lastElementChild);
633        }
634
635        var needed = Math.ceil((track.offsetWidth + loopWidth) / loopWidth) + 1;
636        for (var i = 0; i < needed; i++) {
637            sourceItems.forEach(function (item) {
638                inner.appendChild(item.cloneNode(true));
639            });
640        }
641
642        var offset = 0;
643        var speed = window.innerWidth <= 820 ? 0.20 : 0.55;
644        var paused = false;
645        var animationId = null;
646        var lastTime = null;
647
648        function step(timestamp) {
649            if (lastTime === null) {
650                lastTime = timestamp;
651            }
652
653            var delta = timestamp - lastTime;
654            lastTime = timestamp;
655
656            if (!paused) {
657                offset += speed * delta;
658                if (offset >= loopWidth) {
659                    offset -= loopWidth;
660                }
661                inner.style.transform = "translate3d(" + (-offset) + "px, 0, 0)";
662            }
663
664            animationId = window.requestAnimationFrame(step);
665        }
666
667        track.addEventListener("mouseenter", function () {
668            paused = true;
669        });
670
671        track.addEventListener("mouseleave", function () {
672            paused = false;
673        });
674
675        if (animationId) {
676            window.cancelAnimationFrame(animationId);
677        }
678        animationId = window.requestAnimationFrame(step);
679    }
680
681    function initTypewriter() {
682        var el = document.getElementById('hero-typewriter');
683        if (!el || typeof Typed === 'undefined') return;
684
685        new Typed('#hero-typewriter', {
686            strings: [
687                'Publish faster in WordPress — with AI help, not plugin bloat.',
688                'More than an AI writer — your full WordPress publishing workflow.'
689            ],
690            typeSpeed: 40,
691            backSpeed: 18,
692            backDelay: 2800,
693            startDelay: 400,
694            loop: true,
695            smartBackspace: false,
696            showCursor: true,
697            cursorChar: '|'
698        });
699    }
700
701    function runSiteEnhancement(label, callback) {
702        try {
703            callback();
704        } catch (error) {
705            console.error("SEOVault site enhancement failed: " + label, error);
706        }
707    }
708
709    var siteInitialized = false;
710
711    function initializeSite() {
712        if (siteInitialized) {
713            return;
714        }
715        siteInitialized = true;
716
717        runSiteEnhancement("theme", function () {
718            setTheme(getTheme());
719            document.querySelectorAll("[data-theme-toggle]").forEach(function (button) {
720                button.addEventListener("click", toggleTheme);
721            });
722        });
723        runSiteEnhancement("environment links", applyEnvironmentLinks);
724        runSiteEnhancement("plugin pro analytics", initPluginProAnalytics);
725        runSiteEnhancement("canonical and robots", applyCanonicalAndRobots);
726        runSiteEnhancement("visuals", renderVisuals);
727        runSiteEnhancement("gauge", initGauge);
728        runSiteEnhancement("pricing cards", renderPricingCards);
729        runSiteEnhancement("plugin pro pricing cards", renderPluginProPricingCards);
730        runSiteEnhancement("comparison table", renderComparisonTable);
731        runSiteEnhancement("menu", initMenu);
732        runSiteEnhancement("SEO mockup switcher", initSeoMockupSwitcher);
733        runSiteEnhancement("reveal animations", initReveal);
734        runSiteEnhancement("counters", initCounters);
735        runSiteEnhancement("FAQ", initFaq);
736        runSiteEnhancement("lightbox", initLightbox);
737        runSiteEnhancement("language marquee", initLanguageMarquee);
738        runSiteEnhancement("typewriter", initTypewriter);
739    }
740
741    window.setTimeout(initializeSite, 50);
742})();

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.