# king-addons/51.1.78/includes/widgets/Styled_Text_Builder/script.js

King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder, version 51.1.78. 716 lines.

- Page: https://pluginprobe.com/plugins/king-addons/51.1.78/code/includes/widgets/Styled_Text_Builder/script.js
- Raw: https://pluginprobe.com/plugins/king-addons/51.1.78/raw/includes/widgets/Styled_Text_Builder/script.js
- Modified: 2026-08-25T03:27:10+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/king-addons/51.1.78/code/includes/widgets/Styled_Text_Builder/script.js#L10-L20`.

```javascript
"use strict";

(function ($) {
    // In Elementor the preview iframe can be narrow even on desktop.
    // Detect "mobile" by pointer/hover capabilities, not by width.
    const isTouchLike = () => window.matchMedia("(hover: none), (pointer: coarse)").matches;

    const clamp = (val, min, max) => Math.max(min, Math.min(max, val));

    const getCssVar = (el, name, fallback) => {
        const value = window.getComputedStyle(el).getPropertyValue(name).trim();
        return value || fallback;
    };
 
    const getNumberCssVar = (el, name, fallback) => {
        const raw = getCssVar(el, name, "");
        const num = parseFloat(raw);
        return Number.isFinite(num) ? num : fallback;
    };

    const setupSpoilerCanvas = ($item, $inner) => {
        if ($item.data("kngSpoilerInit")) return;
        $item.data("kngSpoilerInit", true);

        const innerEl = $inner.get(0);
        const textEl = $inner.find("span").first().get(0);
        if (!innerEl || !textEl) return;

        const canvas = document.createElement("canvas");
        canvas.className = "king-addons-spoiler-canvas";
        innerEl.appendChild(canvas);
        const ctx = canvas.getContext("2d");
        if (!ctx) return;

        const state = {
            dpr: Math.max(1, Math.min(2, window.devicePixelRatio || 1)),
            w: 0,
            h: 0,
            particles: [],
            revealPoints: [],
            rafId: 0,
            start: performance.now(),
            last: performance.now(),
            running: false,
            bounds: null,
            cloud: null,
            region: null,
            sprites: null,
            spriteKey: "",
            edgeFadeStart: 0.65,
            edgeFadeEnd: 2.05,
        };

        const getText = () => ($(textEl).text() || "").trim();

        const parseColor = (color) => {
            // Supports: #rgb, #rrggbb, rgb(), rgba(). Falls back to medium gray.
            const fallback = { r: 156, g: 163, b: 175 };
            if (!color) return fallback;
            const c = color.toString().trim();
            if (c.startsWith("#")) {
                const hex = c.slice(1);
                if (hex.length === 3) {
                    const r = parseInt(hex[0] + hex[0], 16);
                    const g = parseInt(hex[1] + hex[1], 16);
                    const b = parseInt(hex[2] + hex[2], 16);
                    return { r, g, b };
                }
                if (hex.length === 6) {
                    const r = parseInt(hex.slice(0, 2), 16);
                    const g = parseInt(hex.slice(2, 4), 16);
                    const b = parseInt(hex.slice(4, 6), 16);
                    return { r, g, b };
                }
                return fallback;
            }
            const m = c.match(/rgba?\(([^)]+)\)/i);
            if (m) {
                const parts = m[1].split(",").map((p) => p.trim());
                const r = parseInt(parts[0], 10);
                const g = parseInt(parts[1], 10);
                const b = parseInt(parts[2], 10);
                if ([r, g, b].every((n) => Number.isFinite(n))) {
                    return { r, g, b };
                }
            }
            return fallback;
        };

        const rebuildParticles = () => {
            const text = getText();
            state.particles = [];
            state.cloud = null;
            state.region = null;
            if (!text || state.w <= 1 || state.h <= 1) return;

            // iMessage Invisible Ink is not per-letter; it's a tight rounded-rect over the text box.
            const computed = window.getComputedStyle($item.get(0));
            const font = computed.font || `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;

            // Measure using an offscreen context.
            const mcanvas = document.createElement("canvas");
            const mctx = mcanvas.getContext("2d");
            if (!mctx) return;
            mctx.font = font;

            const fontSize = parseFloat(computed.fontSize) || 16;
            const lineHeightRaw = computed.lineHeight;
            const lineHeight = Number.isFinite(parseFloat(lineHeightRaw)) ? parseFloat(lineHeightRaw) : fontSize * 1.25;

            // Region from actual laid-out text box.
            const innerRect = innerEl.getBoundingClientRect();
            const spanRect = textEl.getBoundingClientRect();
            const relX = spanRect.left - innerRect.left;
            const relY = spanRect.top - innerRect.top;
            const relW = spanRect.width;
            const relH = spanRect.height;

            const padX = Math.max(10, relW * 0.04, fontSize * 0.7);
            const padY = Math.max(6, relH * 0.18, fontSize * 0.35);
            const rx = relW / 2;
            const corner = clamp(lineHeight * 0.55, 10, Math.max(10, (relH + padY * 2) / 2));

            const region = {
                x: clamp(relX - padX, 0, Math.max(0, state.w - 1)),
                y: clamp(relY - padY, 0, Math.max(0, state.h - 1)),
                w: Math.max(1, relW + padX * 2),
                h: Math.max(1, relH + padY * 2),
                r: corner,
                feather: Math.max(8, lineHeight * 0.28),
            };
            // Clamp region size inside canvas.
            if (region.x + region.w > state.w) region.w = Math.max(1, state.w - region.x);
            if (region.y + region.h > state.h) region.h = Math.max(1, state.h - region.y);
            state.region = region;

            // Target count based on region area.
            const maxParticles = 2200;
            const minParticles = 520;
            const area = region.w * region.h;
            const targetTotal = clamp(Math.round(area / 22), minParticles, maxParticles);

            const sdfRoundRect = (px, py, rr) => {
                const cx = rr.x + rr.w / 2;
                const cy = rr.y + rr.h / 2;
                const hx = rr.w / 2 - rr.r;
                const hy = rr.h / 2 - rr.r;
                const qx = Math.abs(px - cx) - hx;
                const qy = Math.abs(py - cy) - hy;
                const ax = Math.max(qx, 0);
                const ay = Math.max(qy, 0);
                return Math.hypot(ax, ay) + Math.min(Math.max(qx, qy), 0) - rr.r;
            };

            const samplePoint = () => {
                const margin = region.feather * 1.15;
                for (let k = 0; k < 14; k += 1) {
                    const x = region.x + (Math.random() * (region.w + margin * 2) - margin);
                    const y = region.y + (Math.random() * (region.h + margin * 2) - margin);
                    if (sdfRoundRect(x, y, region) <= margin) return { x, y };
                }
                return { x: region.x + region.w / 2, y: region.y + region.h / 2 };
            };

            for (let i = 0; i < targetTotal; i += 1) {
                const pt = samplePoint();
                const speed = 7 + Math.random() * 10;
                const dir = Math.random() * Math.PI * 2;
                const sparkle = Math.random() < 0.06;
                const size = sparkle ? (0.90 + Math.random() * 0.35) : (0.55 + Math.random() * 0.28);
                const tint = sparkle ? (0.65 + Math.random() * 0.25) : (0.22 + Math.random() * 0.22);
                const kind = sparkle ? "spark" : (Math.random() < 0.55 ? "dust" : "dust2");
                state.particles.push({
                    x: pt.x,
                    y: pt.y,
                    vx: Math.cos(dir) * speed,
                    vy: Math.sin(dir) * speed,
                    baseSpeed: speed,
                    alpha: 1,
                    size,
                    sparkle,
                    tint,
                    kind,
                    phase: Math.random() * Math.PI * 2,
                    twinkleSpeed: 0.6 + Math.random() * 1.0,
                    // store region ref values for cheaper access
                    rx: region.x,
                    ry: region.y,
                    rw: region.w,
                    rh: region.h,
                    rr: region.r,
                    rf: region.feather,
                });
            }
        };

        const resize = () => {
            const rect = innerEl.getBoundingClientRect();
            const w = Math.max(1, rect.width);
            const h = Math.max(1, rect.height);

            // Avoid rebuilding on tiny sub-pixel changes.
            if (Math.abs(w - state.w) < 0.5 && Math.abs(h - state.h) < 0.5) return;

            state.w = w;
            state.h = h;
            canvas.width = Math.ceil(w * state.dpr);
            canvas.height = Math.ceil(h * state.dpr);
            canvas.style.width = `${w}px`;
            canvas.style.height = `${h}px`;
            ctx.setTransform(state.dpr, 0, 0, state.dpr, 0, 0);

            rebuildParticles();
        };

        const addRevealPoint = (x, y) => {
            state.revealPoints.push({ x, y, t: performance.now() });
            if (state.revealPoints.length > 30) {
                state.revealPoints.shift();
            }
        };

        const setSpotlight = (clientX, clientY, enabled) => {
            if (!enabled) {
                innerEl.style.setProperty("--kng-spoiler-spot-r", "0px");
                return;
            }

            const rect = textEl.getBoundingClientRect();
            const x = clientX - rect.left;
            const y = clientY - rect.top;
            const r = 34;

            innerEl.style.setProperty("--kng-spoiler-spot-x", `${x}px`);
            innerEl.style.setProperty("--kng-spoiler-spot-y", `${y}px`);
            innerEl.style.setProperty("--kng-spoiler-spot-r", `${r}px`);
        };

        const clearRevealPoints = () => {
            state.revealPoints = [];
            // Restore particles quickly.
            state.particles.forEach((p) => {
                p.alpha = 1;
                // Keep their motion; just restore opacity.
            });
        };

        const draw = () => {
            if ($inner.hasClass("is-revealed")) {
                state.running = false;
                state.rafId = 0;
                return;
            }

            const now = performance.now();
            const time = (now - state.start) / 1000;
            const dt = clamp((now - state.last) / 1000, 0, 0.05);
            state.last = now;

            // Decay reveal points.
            const ttl = 1200;
            state.revealPoints = state.revealPoints.filter((p) => now - p.t < ttl);

            resize();
            if (!state.w || !state.h) {
                state.rafId = requestAnimationFrame(draw);
                return;
            }

            const text = getText();
            const computed = window.getComputedStyle($item.get(0));
            const font = computed.font || `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;
            const textColor = computed.color || "#000";

            const spoilerColorRaw = getCssVar($item.get(0), "--kng-spoiler-color", "#9ca3af");
            const spoilerRgb = parseColor(spoilerColorRaw);
            const spoilerOpacity = clamp(getNumberCssVar($item.get(0), "--kng-spoiler-opacity", 1), 0, 1);

            ctx.clearRect(0, 0, state.w, state.h);

            const radius = 26;
            const fadeSpeed = 0.12;

            const smoothStep = (a, b, x) => {
                const t = clamp((x - a) / (b - a), 0, 1);
                return t * t * (3 - 2 * t);
            };

            const mixToWhite = (rgb, t) => {
                const tt = clamp(t, 0, 1);
                const r = Math.round(rgb.r + (255 - rgb.r) * tt);
                const g = Math.round(rgb.g + (255 - rgb.g) * tt);
                const b = Math.round(rgb.b + (255 - rgb.b) * tt);
                return { r, g, b };
            };

            const snap = (v) => {
                // Snap to device pixels for crisp rendering.
                const dpr = state.dpr || 1;
                return Math.round(v * dpr) / dpr;
            };

            const ensureSprites = (rgb) => {
                const key = `${rgb.r},${rgb.g},${rgb.b}`;
                if (state.sprites && state.spriteKey === key) return;

                const makeSprite = (radiusPx, tintToWhite) => {
                    const size = Math.ceil(radiusPx * 2 + 4);
                    const c = document.createElement("canvas");
                    c.width = size;
                    c.height = size;
                    const cctx = c.getContext("2d");
                    if (!cctx) return c;

                    // Keep color visible: only tiny whitening.
                    const col = mixToWhite(rgb, tintToWhite);
                    const cx = Math.floor(size / 2);
                    const cy = Math.floor(size / 2);

                    cctx.clearRect(0, 0, size, size);
                    cctx.imageSmoothingEnabled = false;

                    // Hard core (no AA): 1–2 device-independent pixels.
                    const core = radiusPx >= 1.05 ? 2 : 1;
                    cctx.globalAlpha = 1;
                    cctx.fillStyle = `rgba(${col.r}, ${col.g}, ${col.b}, 1)`;
                    cctx.fillRect(cx - Math.floor(core / 2), cy - Math.floor(core / 2), core, core);

                    // Minimal halo (very subtle) so it doesn't look blurry.
                    const grad = cctx.createRadialGradient(cx, cy, 0, cx, cy, radiusPx + 0.8);
                    grad.addColorStop(0, `rgba(${col.r}, ${col.g}, ${col.b}, 0.18)`);
                    grad.addColorStop(1, `rgba(${col.r}, ${col.g}, ${col.b}, 0)`);
                    cctx.fillStyle = grad;
                    cctx.beginPath();
                    cctx.arc(cx + 0.5, cy + 0.5, radiusPx + 0.8, 0, Math.PI * 2);
                    cctx.fill();
                    return c;
                };

                // Mostly small dust, rare brighter sparkles.
                state.sprites = {
                    dust: makeSprite(0.72, 0.06),
                    dust2: makeSprite(0.92, 0.08),
                    spark: makeSprite(1.18, 0.14),
                };
                state.spriteKey = key;
            };

            const sdfRoundRectFast = (px, py, x, y, w, h, r) => {
                const cx = x + w / 2;
                const cy = y + h / 2;
                const hx = w / 2 - r;
                const hy = h / 2 - r;
                const qx = Math.abs(px - cx) - hx;
                const qy = Math.abs(py - cy) - hy;
                const ax = Math.max(qx, 0);
                const ay = Math.max(qy, 0);
                return Math.hypot(ax, ay) + Math.min(Math.max(qx, qy), 0) - r;
            };

            const edgeFactorAt = (p) => {
                if (typeof p.cx !== "number" || typeof p.cy !== "number" || typeof p.rx !== "number" || typeof p.ry !== "number") {
                    return 1;
                }
                const nx = (p.x - p.cx) / p.rx;
                const ny = (p.y - p.cy) / p.ry;
                const r = Math.sqrt(nx * nx + ny * ny) + (typeof p.edgeJitter === "number" ? p.edgeJitter : 0);

                // Feathered edge so the cloud doesn't end with a hard oval.
                // r <= 1.0  -> fully visible
                // 1.0 < r < 1.55 -> fade out
                // r >= 1.55 -> fully invisible (and we respawn)
                const fadeStart = state.edgeFadeStart || 1.0;
                const fadeEnd = state.edgeFadeEnd || 1.55;
                if (r <= fadeStart) return 1;
                if (r >= fadeEnd) return 0;
                const t = clamp((r - fadeStart) / (fadeEnd - fadeStart), 0, 1);
                const s = t * t * (3 - 2 * t);
                return 1 - s;
            };

            const shouldRespawn = (p) => {
                if (typeof p.cx !== "number" || typeof p.cy !== "number" || typeof p.rx !== "number" || typeof p.ry !== "number") {
                    return false;
                }
                const nx = (p.x - p.cx) / p.rx;
                const ny = (p.y - p.cy) / p.ry;
                const r = Math.sqrt(nx * nx + ny * ny);
                const fadeEnd = state.edgeFadeEnd || 1.55;
                return r > fadeEnd + 0.55;
            };

            const respawn = (p) => {
                if (!state.region) {
                    return;
                }
                const margin = state.region.feather * 1.15;
                for (let k = 0; k < 12; k += 1) {
                    const x = state.region.x + (Math.random() * (state.region.w + margin * 2) - margin);
                    const y = state.region.y + (Math.random() * (state.region.h + margin * 2) - margin);
                    if (sdfRoundRectFast(x, y, state.region.x, state.region.y, state.region.w, state.region.h, state.region.r) <= margin) {
                        p.x = x;
                        p.y = y;
                        break;
                    }
                }

                const speed = typeof p.baseSpeed === "number" ? p.baseSpeed : (7 + Math.random() * 10);
                const dir = Math.random() * Math.PI * 2;
                p.vx = Math.cos(dir) * speed;
                p.vy = Math.sin(dir) * speed;
            };

            ensureSprites(spoilerRgb);

            // Preserve color (lighter tends to wash into white).
            ctx.globalCompositeOperation = "source-over";
            ctx.imageSmoothingEnabled = false;

            for (let i = 0; i < state.particles.length; i += 1) {
                const p = state.particles[i];

                // Natural iMessage-like shimmer: gentle drift + tiny curl.
                if (typeof p.vx === "number" && typeof p.vy === "number") {
                    const phase = (p.phase || 0);
                    const ax = Math.sin(p.y * 0.045 + time * 0.85 + phase) * 3.2;
                    const ay = Math.cos(p.x * 0.045 - time * 0.80 + phase) * 3.2;
                    p.vx += ax * dt;
                    p.vy += ay * dt;

                    const base = typeof p.baseSpeed === "number" ? p.baseSpeed : 14;
                    const maxV = base * 1.18;
                    const v = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
                    if (v > maxV) {
                        const k = maxV / (v || 1);
                        p.vx *= k;
                        p.vy *= k;
                    }

                    p.x += p.vx * dt;
                    p.y += p.vy * dt;
                }

                if (!state.region) continue;
                // No hard boundary: fade via SDF with a bit of noise.
                const sdf = sdfRoundRectFast(p.x, p.y, state.region.x, state.region.y, state.region.w, state.region.h, state.region.r);
                // Irregular edge (not a visible geometric boundary)
                const edgeNoise = (Math.sin(p.x * 0.11 + time * 0.8 + (p.phase || 0)) + Math.cos(p.y * 0.13 - time * 0.7 + (p.phase || 0))) * 0.5;
                const feather = state.region.feather;
                const edge = 1 - smoothStep(-feather, 0, sdf + edgeNoise * feather * 0.18);
                if (edge <= 0.01) {
                    // if far outside, respawn; if near outside, just drift back.
                    if (sdf > feather * 1.8) respawn(p);
                    continue;
                }

                // If slightly outside, gently pull back toward center (no bounce).
                if (sdf > 0.6) {
                    const cx = state.region.x + state.region.w / 2;
                    const cy = state.region.y + state.region.h / 2;
                    p.x += (cx - p.x) * 0.02;
                    p.y += (cy - p.y) * 0.02;
                    p.vx *= 0.98;
                    p.vy *= 0.98;
                }

                // Soft wrap/respawn so the cloud keeps moving without visible "bounce".
                // Only respawn when far outside; near-edge particles fade out instead.
                if (shouldRespawn(p)) {
                    respawn(p);
                }

                // Reveal around cursor/touch movement.
                let shouldFade = false;
                for (let k = 0; k < state.revealPoints.length; k += 1) {
                    const rp = state.revealPoints[k];
                    const dx = p.x - rp.x;
                    const dy = p.y - rp.y;
                    if (dx * dx + dy * dy < radius * radius) {
                        shouldFade = true;
                        break;
                    }
                }

                if (shouldFade) {
                    p.alpha = Math.max(0, p.alpha - fadeSpeed);
                } else {
                    p.alpha = Math.min(1, p.alpha + fadeSpeed * 0.35);
                }

                const tw = 0.94 + 0.06 * Math.sin(time * (p.twinkleSpeed || 1.0) + (p.phase || 0));
                // Boost a bit so color reads.
                const baseAlpha = clamp(spoilerOpacity * p.alpha * edge * tw * 1.48, 0, 1);
                if (baseAlpha <= 0.01) continue;

                const size = typeof p.size === "number" ? p.size : 0.8;
                const kind = (p.kind || (p.sparkle ? "spark" : "dust"));
                const sprite = kind === "spark" ? state.sprites.spark : (kind === "dust2" ? state.sprites.dust2 : state.sprites.dust);
                const sw = sprite.width;
                const sh = sprite.height;
                // Limit scaling to avoid blur and keep pointy look.
                const scale = clamp(size / 0.8, 0.9, 1.15);
                const dw = Math.max(1, Math.round(sw * scale));
                const dh = Math.max(1, Math.round(sh * scale));

                const tintBoost = typeof p.tint === "number" ? (1 + p.tint * 0.82) : 1;
                ctx.globalAlpha = clamp(baseAlpha * tintBoost * (kind === "spark" ? 1 : 1), 0, 1);
                // Snap to device pixel grid for crispness.
                const dx = snap(p.x - dw / 2);
                const dy = snap(p.y - dh / 2);
                ctx.drawImage(sprite, dx, dy, dw, dh);
            }

            ctx.globalCompositeOperation = "source-over";

            ctx.globalAlpha = 1;
            state.rafId = requestAnimationFrame(draw);
        };

        const start = () => {
            if (state.running) return;
            state.running = true;
            state.start = performance.now();
            state.rafId = requestAnimationFrame(draw);
        };

        const stopAndRevealAll = () => {
            $inner.addClass("is-revealed");
            setSpotlight(0, 0, false);
            clearRevealPoints();
            if (state.rafId) {
                cancelAnimationFrame(state.rafId);
                state.rafId = 0;
            }
            state.running = false;
        };

        const hideAll = () => {
            $inner.removeClass("is-revealed");
            setSpotlight(0, 0, false);
            clearRevealPoints();
            start();
        };

        // Initial sizing & animation.
        resize();
        start();

        // Use triggers from widget settings.
        const triggerDesktop = $item.data("spoiler-desktop") || "hover";
        const triggerMobile = $item.data("spoiler-mobile") || "tap";

        const attachHoverInk = () => {
            $item.on("mousemove", (e) => {
                const rect = innerEl.getBoundingClientRect();
                addRevealPoint(e.clientX - rect.left, e.clientY - rect.top);
                setSpotlight(e.clientX, e.clientY, true);
            });
            $item.on("mouseleave", () => {
                setSpotlight(0, 0, false);
                clearRevealPoints();
            });
        };

        const attachTouchInk = () => {
            $item.on("touchstart", (e) => {
                const touch = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0];
                if (touch) setSpotlight(touch.clientX, touch.clientY, true);
            });
            $item.on("touchmove", (e) => {
                const touch = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0];
                if (!touch) return;
                const rect = innerEl.getBoundingClientRect();
                addRevealPoint(touch.clientX - rect.left, touch.clientY - rect.top);
                setSpotlight(touch.clientX, touch.clientY, true);
            });
            $item.on("touchend", () => {
                setSpotlight(0, 0, false);
                // On mobile iOS-like behavior: reset when touch ends.
                clearRevealPoints();
            });
        };

        const attachClickRemove = () => {
            $item.on("click", (e) => {
                e.preventDefault();
                if ($inner.hasClass("is-revealed")) {
                    hideAll();
                } else {
                    stopAndRevealAll();
                }
            });
        };

        const attachClickToggle = () => {
            $item.on("click", (e) => {
                e.preventDefault();
                if ($inner.hasClass("is-revealed")) {
                    hideAll();
                } else {
                    stopAndRevealAll();
                }
            });
        };

        if (isTouchLike()) {
            if (triggerMobile === "hover") {
                attachTouchInk();
            } else if (triggerMobile === "hover-click") {
                attachTouchInk();
                attachClickRemove();
            } else {
                attachClickToggle();
            }
        } else {
            if (triggerDesktop === "click") {
                attachClickToggle();
            } else if (triggerDesktop === "hover-click") {
                attachHoverInk();
                attachClickRemove();
            } else {
                attachHoverInk();
            }
        }

        // Keep canvas in sync with layout changes.
        if (window.ResizeObserver) {
            const ro = new ResizeObserver(() => {
                resize();
            });
            ro.observe(innerEl);
        } else {
            $(window).on("resize", () => resize());
        }
    };

    const bindSpoiler = ($item) => {
        const $inner = $item.find(".king-addons-styled-text-inner").first();
        if (!$inner.length) return;
        setupSpoilerCanvas($item, $inner);
    };

    const startTyping = ($item) => {
        const $inner = $item.find(".king-addons-styled-text-inner span").first();
        if (!$inner.length) return;

        const fullText = $inner.text();
        const speed = parseInt($item.data("typing-speed"), 10) || 80;
        const delay = parseInt($item.data("typing-delay"), 10) || 1200;
        const loop = $item.data("typing-loop") === "yes";
        const cursorChar = ($item.data("typing-cursor") || "|").toString();

        const $cursor = $('<span class="king-addons-styled-text__cursor"></span>').text(cursorChar);
        $inner.after($cursor);

        let pos = 0;
        let isTyping = false;

        const typeOnce = () => {
            if (isTyping) return;
            isTyping = true;
            $inner.text("");
            pos = 0;

            const step = () => {
                if (pos <= fullText.length) {
                    $inner.text(fullText.substring(0, pos));
                    pos += 1;
                    setTimeout(step, speed);
                } else {
                    isTyping = false;
                    if (loop) {
                        setTimeout(typeOnce, delay);
                    }
                }
            };

            step();
        };

        typeOnce();
    };

    const initStyledText = ($scope) => {
        const $items = $scope.find('.king-addons-styled-text[data-effect="spoiler"]');
        if ($items.length) {
            $items.each(function () {
                bindSpoiler($(this));
            });
        }

        const $typingItems = $scope.find('.king-addons-styled-text[data-effect="typing"]');
        if ($typingItems.length) {
            $typingItems.each(function () {
                startTyping($(this));
            });
        }
    };

    $(window).on("elementor/frontend/init", function () {
        elementorFrontend.hooks.addAction(
            "frontend/element_ready/king-addons-styled-text-builder.default",
            function ($scope) {
                initStyledText($scope);
            }
        );
    });
})(jQuery);








```
