# blockenberg/2.0.14/blocks/handwriting-text/frontend.js

Blockenberg — 600+ Advanced Gutenberg Blocks &amp; AI Agent for WordPress Block Editor, version 2.0.14. 197 lines.

- Page: https://pluginprobe.com/plugins/blockenberg/2.0.14/code/blocks/handwriting-text/frontend.js
- Raw: https://pluginprobe.com/plugins/blockenberg/2.0.14/raw/blocks/handwriting-text/frontend.js
- Modified: 2026-10-11T14:39:22+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/blockenberg/2.0.14/code/blocks/handwriting-text/frontend.js#L10-L20`.

```javascript
(function () {
    'use strict';

    var svgNS = 'http://www.w3.org/2000/svg';
    var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    document.querySelectorAll('.bkbg-hw-app').forEach(function (app) {
        var opts = {};
        try { opts = JSON.parse(app.dataset.opts || '{}'); } catch (e) { }

        var text         = opts.text        || '';
        var fontFamily   = opts.fontFamily  || "'Dancing Script', cursive";
        var fontSize     = opts.fontSize    || 72;
        var fontWeight   = opts.fontWeight  || '700';
        var svgWidth     = opts.svgWidth    || 900;
        var svgHeight    = opts.svgHeight   || 140;
        var textX        = opts.textX       || 50;
        var textY        = opts.textY       || 110;
        var textAnchor   = opts.textAnchor  || 'middle';
        var strokeWidth  = opts.strokeWidth || 2;
        var duration     = opts.duration    || 3;
        var delay        = opts.delay       || 0.3;
        var trigger      = opts.trigger     || 'scroll';
        var fillOnComplete = opts.fillOnComplete !== false;
        var fillDuration = opts.fillDuration || 0.8;
        var repeatAnim   = opts.repeatAnimation || false;
        var easing       = opts.easing      || 'ease-in-out';
        var textColor    = opts.textColor   || '#0f172a';
        var strokeColor  = opts.strokeColor || '#6366f1';
        var align2       = opts.align2      || 'center';
        var sectionBg    = opts.sectionBg   || '';
        var showUnderline  = opts.showUnderline || false;
        var underlineColor = opts.underlineColor || '#6366f1';
        var underlineWidth = opts.underlineWidth || 4;
        var underlineOffsetY = opts.underlineOffsetY || 12;
        var underlineDuration = opts.underlineDuration || 0.6;

        if (sectionBg) app.style.background = sectionBg;

        /* typography CSS vars */
        if (window.typoCssVarsForEl) window.typoCssVarsForEl(opts.typoText, '--bkbg-hw-tt-', app);

        // Build SVG
        var svg = document.createElementNS(svgNS, 'svg');
        svg.setAttribute('viewBox', '0 0 ' + svgWidth + ' ' + svgHeight);
        svg.setAttribute('width', '100%');
        svg.style.cssText = 'display:block;overflow:visible;max-width:' + svgWidth + 'px;' +
            'margin:' + (align2 === 'center' ? '0 auto' : align2 === 'right' ? '0 0 0 auto' : '0');

        // Text element
        var textEl = document.createElementNS(svgNS, 'text');
        textEl.className.baseVal = 'bkbg-hw-text';
        textEl.setAttribute('x', textX + '%');
        textEl.setAttribute('y', textY);
        textEl.setAttribute('text-anchor', textAnchor);
        textEl.setAttribute('stroke', strokeColor);
        textEl.setAttribute('stroke-width', strokeWidth);
        textEl.textContent = text;

        svg.appendChild(textEl);

        var wrap = document.createElement('div');
        wrap.className = 'bkbg-hw-wrap';
        wrap.style.textAlign = align2;
        wrap.appendChild(svg);
        app.appendChild(wrap);

        // Need text in DOM before measuring
        // Use requestAnimationFrame to let browser render
        requestAnimationFrame(function () {
            var len = 5000; // large fallback
            try { len = textEl.getComputedTextLength() || 5000; } catch (e) { }
            len = Math.max(len, textEl.getTotalLength ? textEl.getTotalLength() : len);

            // Also build underline path if requested
            var ulEl = null;
            var ulLen = 0;
            if (showUnderline) {
                // Get bounding box to draw underline across actual text width
                var bbox = { x: 0, width: svgWidth };
                try { bbox = textEl.getBBox(); } catch (e) { }
                var ulY  = parseFloat(textY) + underlineOffsetY;
                var ulX1 = bbox.x;
                var ulX2 = bbox.x + bbox.width;

                ulEl = document.createElementNS(svgNS, 'path');
                ulEl.className.baseVal = 'bkbg-hw-underline';
                ulEl.setAttribute('d', 'M ' + ulX1 + ' ' + ulY + ' L ' + ulX2 + ' ' + ulY);
                ulEl.setAttribute('stroke', underlineColor);
                ulEl.setAttribute('stroke-width', underlineWidth);
                ulEl.setAttribute('stroke-linecap', 'round');
                svg.appendChild(ulEl);

                ulLen = (ulX2 - ulX1) || svgWidth;
            }

            function setupInitialState() {
                if (reducedMotion) {
                    // Show immediately
                    textEl.setAttribute('fill', textColor);
                    textEl.setAttribute('stroke', 'none');
                    if (ulEl) {
                        ulEl.style.strokeDasharray = '';
                        ulEl.style.strokeDashoffset = '';
                    }
                    return;
                }

                // Set dash = full length = hidden
                textEl.style.strokeDasharray = len;
                textEl.style.strokeDashoffset = len;
                textEl.setAttribute('fill', 'transparent');
                textEl.style.transition = 'none';

                if (ulEl) {
                    ulEl.style.strokeDasharray = ulLen;
                    ulEl.style.strokeDashoffset = ulLen;
                    ulEl.style.transition = 'none';
                }
            }

            function startAnimation() {
                requestAnimationFrame(function () {
                    requestAnimationFrame(function () {
                        // Draw stroke
                        textEl.style.transition = 'stroke-dashoffset ' + duration + 's ' + easing + ' ' + delay + 's';
                        textEl.style.strokeDashoffset = 0;

                        // Underline draws after text
                        if (ulEl) {
                            var ulDelay = delay + duration * 0.9;
                            ulEl.style.transition = 'stroke-dashoffset ' + underlineDuration + 's ease-out ' + ulDelay + 's';
                            ulEl.style.strokeDashoffset = 0;
                        }

                        // Fill text after stroke completes
                        if (fillOnComplete) {
                            var fillDelay = delay + duration;
                            setTimeout(function () {
                                textEl.classList.add('bkbg-hw-filled');
                                textEl.style.fill = textColor;
                                textEl.style.fillOpacity = '1';
                                textEl.style.transitionProperty = 'fill';
                                textEl.style.transitionDuration = fillDuration + 's';
                                textEl.style.transitionTimingFunction = 'ease-in-out';
                            }, fillDelay * 1000);
                        }
                    });
                });
            }

            function resetAnimation() {
                textEl.style.transition = 'none';
                textEl.style.strokeDashoffset = len;
                textEl.setAttribute('fill', 'transparent');
                textEl.classList.remove('bkbg-hw-filled');
                if (ulEl) {
                    ulEl.style.transition = 'none';
                    ulEl.style.strokeDashoffset = ulLen;
                }
            }

            setupInitialState();

            if (reducedMotion) return; // Don't animate for reduced motion users

            if (trigger === 'load') {
                startAnimation();
            } else if (trigger === 'click') {
                app.style.cursor = 'pointer';
                var played = false;
                app.addEventListener('click', function () {
                    if (played) resetAnimation();
                    setTimeout(startAnimation, played ? 50 : 0);
                    played = true;
                });
            } else {
                // scroll (IntersectionObserver)
                var played2 = false;
                var io = new IntersectionObserver(function (entries) {
                    entries.forEach(function (entry) {
                        if (entry.isIntersecting && !played2) {
                            played2 = true;
                            startAnimation();
                            if (!repeatAnim) io.unobserve(app);
                        } else if (!entry.isIntersecting && repeatAnim && played2) {
                            played2 = false;
                            resetAnimation();
                        }
                    });
                }, { threshold: 0.3 });
                io.observe(app);
            }
        });
    });
})();

```
