
(function() {
'use strict';

if (typeof THREE === 'undefined') return;

var instances = {};

function setAncestorOverflow(el, visible) {
    var val = visible ? 'visible' : '';
    var node = el.parentElement;
    while (node && node !== document.body) {
        if (visible) node.style.overflow = 'visible';
        else node.style.overflow = '';
        node = node.parentElement;
    }
}

var _SHARED_VERT = [
    'varying vec2 vUv;',
    'void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}'
].join('\n');

var HBLUR_FRAG = [
    'precision mediump float;',
    'uniform sampler2D map;',
    'uniform float u_blurBase;',
    'uniform float u_blurEdge;',
    'uniform float u_texW;',
    'varying vec2 vUv;',
    'void main(){',
    '  float sigma=abs(u_blurBase+(vUv.x-0.5)*u_blurEdge);',
    '  if(sigma<0.5){gl_FragColor=texture2D(map,vUv);return;}',
    '  float px=2.0/max(u_texW,1.0);',
    '  float s2=max(sigma*sigma,0.25);',
    '  float w1=exp(-2.0/s2),w2=exp(-8.0/s2),w3=exp(-18.0/s2),w4=exp(-32.0/s2),w5=exp(-50.0/s2),w6=exp(-72.0/s2);',
    '  float wt=1.0+2.0*(w1+w2+w3+w4+w5+w6);',
    '  vec2 lo=vec2(0.001),hi=vec2(0.999);',
    '  vec4 c=texture2D(map,vUv);',
    '  c+=(texture2D(map,clamp(vUv+vec2(px,0.0),lo,hi))+texture2D(map,clamp(vUv-vec2(px,0.0),lo,hi)))*w1;',
    '  c+=(texture2D(map,clamp(vUv+vec2(2.0*px,0.0),lo,hi))+texture2D(map,clamp(vUv-vec2(2.0*px,0.0),lo,hi)))*w2;',
    '  c+=(texture2D(map,clamp(vUv+vec2(3.0*px,0.0),lo,hi))+texture2D(map,clamp(vUv-vec2(3.0*px,0.0),lo,hi)))*w3;',
    '  c+=(texture2D(map,clamp(vUv+vec2(4.0*px,0.0),lo,hi))+texture2D(map,clamp(vUv-vec2(4.0*px,0.0),lo,hi)))*w4;',
    '  c+=(texture2D(map,clamp(vUv+vec2(5.0*px,0.0),lo,hi))+texture2D(map,clamp(vUv-vec2(5.0*px,0.0),lo,hi)))*w5;',
    '  c+=(texture2D(map,clamp(vUv+vec2(6.0*px,0.0),lo,hi))+texture2D(map,clamp(vUv-vec2(6.0*px,0.0),lo,hi)))*w6;',
    '  gl_FragColor=c/wt;',
    '}'
].join('\n');

var _VBLUR = [
    '  float sigma=abs(u_blurBase+(vUv.x-0.5)*u_blurEdge);',
    '  vec4 mask;',
    '  if(sigma<0.5){mask=texture2D(map,vUv);}',
    '  else{',
    '    float py=2.0/max(u_texH,1.0);',
    '    float s2=max(sigma*sigma,0.25);',
    '    float w1=exp(-2.0/s2),w2=exp(-8.0/s2),w3=exp(-18.0/s2),w4=exp(-32.0/s2),w5=exp(-50.0/s2),w6=exp(-72.0/s2);',
    '    float wt=1.0+2.0*(w1+w2+w3+w4+w5+w6);',
    '    vec2 lo=vec2(0.001),hi=vec2(0.999);',
    '    mask=texture2D(map,vUv);',
    '    mask+=(texture2D(map,clamp(vUv+vec2(0.0,py),lo,hi))+texture2D(map,clamp(vUv-vec2(0.0,py),lo,hi)))*w1;',
    '    mask+=(texture2D(map,clamp(vUv+vec2(0.0,2.0*py),lo,hi))+texture2D(map,clamp(vUv-vec2(0.0,2.0*py),lo,hi)))*w2;',
    '    mask+=(texture2D(map,clamp(vUv+vec2(0.0,3.0*py),lo,hi))+texture2D(map,clamp(vUv-vec2(0.0,3.0*py),lo,hi)))*w3;',
    '    mask+=(texture2D(map,clamp(vUv+vec2(0.0,4.0*py),lo,hi))+texture2D(map,clamp(vUv-vec2(0.0,4.0*py),lo,hi)))*w4;',
    '    mask+=(texture2D(map,clamp(vUv+vec2(0.0,5.0*py),lo,hi))+texture2D(map,clamp(vUv-vec2(0.0,5.0*py),lo,hi)))*w5;',
    '    mask+=(texture2D(map,clamp(vUv+vec2(0.0,6.0*py),lo,hi))+texture2D(map,clamp(vUv-vec2(0.0,6.0*py),lo,hi)))*w6;',
    '    mask/=wt;',
    '  }'
].join('\n');

var VBLUR_SOLID_FRAG = [
    'precision mediump float;',
    'uniform sampler2D map;',
    'uniform vec3 u_color;',
    'uniform float u_colorAlpha;', 
    'uniform float u_blurBase;',
    'uniform float u_blurEdge;',
    'uniform float u_texH;',
    'uniform float u_opacity;',
    'varying vec2 vUv;',
    'void main(){', _VBLUR,
    '  float a=mask.r; if(a<0.01)discard;',
    '  gl_FragColor=vec4(u_color,a*u_opacity*u_colorAlpha);',
    '}'
].join('\n');

var VBLUR_GRAD_FRAG = [
    'precision mediump float;',
    'uniform sampler2D map;',
    'uniform vec4 u_c1;',
    'uniform vec4 u_c2;',
    'uniform float u_angle;',
    'uniform float u_shift;',
    'uniform float u_blurBase;',
    'uniform float u_blurEdge;',
    'uniform float u_texH;',
    'uniform float u_opacity;',
    'varying vec2 vUv;',
    'void main(){', _VBLUR,
    '  float a=mask.r; if(a<0.01)discard;',
    '  float ca=cos(u_angle),sa=sin(u_angle);',
    '  float t=clamp((vUv.x-0.5)*ca+(vUv.y-0.5)*sa+0.5+u_shift*0.5,0.0,1.0);',
    '  vec4 col=mix(u_c1,u_c2,t);',
    '  gl_FragColor=vec4(col.rgb,a*col.a*u_opacity);',
    '}'
].join('\n');

var STROKE_CONIC_FRAG = [
    'precision mediump float;',
    '#define MAX_GLYPHS 16',
    'uniform sampler2D map;',
    'uniform vec4 u_c1;',
    'uniform vec4 u_c2;',
    'uniform float u_time;',         
    'uniform float u_mouseShift;',   
    'uniform float u_bevel;',        
    'uniform float u_opacity;',
    'uniform int   u_spinMode;',     
    'uniform int   u_glyphCount;',   
    'uniform vec4  u_glyphs[MAX_GLYPHS];', 
    'uniform float u_glyphStagger;', 
    'varying vec2 vUv;',
    'void main(){',
    '  vec4 mask = texture2D(map, vUv);',
    '  float a = mask.r;',
    '  if(a<0.5) discard;',
    '  vec2 centre = vec2(0.5, 0.5);',
    '  float phase = 0.0;',
    '  if (u_spinMode == 1 && u_glyphCount > 0) {',
    '    // Pulse mode — SMOOTH centre + phase via inverse-distance-squared',
    '    // weighted averaging over all glyph anchors. Replaces the earlier',
    '    // voronoi nearest-glyph pick: voronoi had a hard boundary line',
    '    // between adjacent letter cells where the centre flipped abruptly',
    '    // and the conic angle jumped ~180° — visible at rest as a thin',
    '    // seam between letters and during screen-transition CSS rotation',
    '    // as wide angular bands across the stroke. Smooth weighting has',
    '    // NO boundary: a fragment near letter A is weighted ~95% A + 5%',
    '    // others, a fragment at the equidistant midpoint between A and B',
    '    // is weighted 50/50, etc. Centre and phase both interpolate',
    '    // continuously, so the conic angle gradient never jumps.',
    '    vec2 weightedCentre = vec2(0.0);',
    '    float weightedPhase = 0.0;',
    '    float totalWeight = 0.0;',
    '    for (int i = 0; i < MAX_GLYPHS; i++) {',
    '      if (i >= u_glyphCount) break;',
    '      vec4 g = u_glyphs[i];',
    '      vec2 dg = vUv - g.xy;',
    '      // Inverse-squared distance with epsilon floor so we never divide',
    '      // by zero. Power-2 (vs power-1) makes the nearest glyph DOMINATE',
    '      // strongly inside its cell while still letting neighbours blend',
    '      // gently across boundaries — keeps the per-letter pulse feel.',
    '      float distSq = dot(dg, dg) + 0.00001;',
    '      float w = 1.0 / distSq;',
    '      weightedCentre += g.xy * w;',
    '      weightedPhase  += float(i) * u_glyphStagger * w;',
    '      totalWeight    += w;',
    '    }',
    '    centre = weightedCentre / totalWeight;',
    '    phase  = weightedPhase / totalWeight;',
    '  }',
    '  vec2 d = vUv - centre;',
    '  float ang = atan(d.y, d.x);',
    '  float t = (ang + u_time + u_mouseShift + phase) / 6.28318530718;',
    '  t = fract(t);',
    '  float sweep = 0.5 - 0.5 * cos(t * 6.28318530718);',
    '  vec4 col = mix(u_c1, u_c2, sweep);',
    '  if (u_bevel > 0.5) {',
    '    // Bevel uses a GLOBAL centre regardless of spin mode. In pulse',
    '    // mode `centre` jumps abruptly at voronoi boundaries between',
    '    // adjacent glyphs — a fragment on letter A\'s right edge gets',
    '    // bevel-shadow (d points right relative to A\'s centre), the',
    '    // neighbouring fragment on letter B\'s left edge gets bevel-',
    '    // highlight (d points left relative to B\'s centre). That sign',
    '    // flip at every voronoi boundary, intersected with the bevel\'s',
    '    // diagonal lit/shadow gradient, produced triangular bright/dark',
    '    // patches between letters — visible as the "triangular glitch"',
    '    // on outline+depth strokes with stroke_spin_mode=pulse. Using',
    '    // vUv-vec2(0.5,0.5) for the bevel direction gives ONE consistent',
    '    // diagonal lighting across the entire word, just like sweep mode',
    '    // — the per-glyph pulse spin (conic gradient) still uses the',
    '    // per-glyph centre via `d`, only the bevel is decoupled.',
    '    vec2 bevelD = vUv - vec2(0.5, 0.5);',
    '    vec2 dir = length(bevelD) > 0.0001 ? normalize(bevelD) : vec2(0.0, 1.0);',
    '    vec2 lightDir = vec2(-0.7071, -0.7071);',
    '    float lit = clamp(dot(dir, lightDir), -1.0, 1.0);',
    '    if (lit > 0.0) col.rgb = mix(col.rgb, vec3(1.0),  lit * 0.65);',
    '    else           col.rgb = mix(col.rgb, vec3(0.0), -lit * 0.65);',
    '  }',
    '  gl_FragColor = vec4(col.rgb, a * col.a * u_opacity);',
    '}'
].join('\n');

function safeColor(h) {
    if (!h || typeof h !== 'string') return new THREE.Color(0);
    var rm = h.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/);
    if (rm) return new THREE.Color('rgb('+rm[1]+','+rm[2]+','+rm[3]+')');
    var hm = h.match(/^#([0-9a-fA-F]{8})$/);
    if (hm) return new THREE.Color('#'+hm[1].slice(0,6));
    return new THREE.Color(h);
}
function safeColorVec4(h) {
    if (!h || typeof h !== 'string') return new THREE.Vector4(0,0,0,1);

    
    
    if (h === 'transparent') return new THREE.Vector4(0, 0, 0, 0);
    var rm = h.match(/rgba?[(]\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*[)]/);
    if (rm) { var c=new THREE.Color('rgb('+rm[1]+','+rm[2]+','+rm[3]+')'); return new THREE.Vector4(c.r,c.g,c.b,rm[4]!=null?parseFloat(rm[4]):1.0); }
    var hm8 = h.match(/^#([0-9a-fA-F]{8})$/);
    if (hm8) { var c=new THREE.Color('#'+hm8[1].slice(0,6)); var a=parseInt(hm8[1].slice(6,8),16)/255; return new THREE.Vector4(c.r,c.g,c.b,a); }
    var c = safeColor(h);
    return new THREE.Vector4(c.r, c.g, c.b, 1.0);
}

function makeHBlurMaterial(tex) {
    return new THREE.ShaderMaterial({
        uniforms: {
            map:        { value: tex },
            u_blurBase: { value: 0.0 },
            u_blurEdge: { value: 0.0 },
            u_texW:     { value: tex._w || 1.0 }
        },
        vertexShader: _SHARED_VERT, fragmentShader: HBLUR_FRAG,
        transparent: true, depthWrite: false, depthTest: false
    });
}

function makeVSolidMaterial(color, texH) {

    
    
    var v = safeColorVec4(color || '#ffffff');
    return new THREE.ShaderMaterial({
        uniforms: {
            map:          { value: null },
            u_color:      { value: new THREE.Vector3(v.x, v.y, v.z) },
            u_colorAlpha: { value: v.w },
            u_blurBase:   { value: 0.0 },
            u_blurEdge:   { value: 0.0 },
            u_texH:       { value: texH || 1.0 },
            u_opacity:    { value: 1.0 }
        },
        vertexShader: _SHARED_VERT, fragmentShader: VBLUR_SOLID_FRAG,
        transparent: true, depthWrite: false, side: THREE.DoubleSide
    });
}
function makeVGradMaterial(gd, texH) {
    return new THREE.ShaderMaterial({
        uniforms: {
            map:        { value: null },
            u_c1:       { value: safeColorVec4(gd.color1||'#ffffff') },
            u_c2:       { value: safeColorVec4(gd.color2||'#000000') },
            u_angle:    { value: (gd.angle!=null?gd.angle:135)*Math.PI/180 },
            u_shift:    { value: 0.0 },
            u_blurBase: { value: 0.0 },
            u_blurEdge: { value: 0.0 },
            u_texH:     { value: texH || 1.0 },
            u_opacity:  { value: 1.0 }
        },
        vertexShader: _SHARED_VERT, fragmentShader: VBLUR_GRAD_FRAG,
        transparent: true, depthWrite: false, side: THREE.DoubleSide
    });
}

function _strokeC2Vec4(c1, c2) {
    if (c2) return safeColorVec4(c2);
    var v = safeColorVec4(c1 || '#ffffff');
    return new THREE.Vector4(v.x, v.y, v.z, 0);
}
var STROKE_PULSE_MAX_GLYPHS = 16;
function makeStrokeConicMaterial(c1, c2, bevel) {

    
    
    var emptyGlyphs = [];
    for (var i = 0; i < STROKE_PULSE_MAX_GLYPHS; i++) emptyGlyphs.push(new THREE.Vector4(0, 0, 0, 0));
    return new THREE.ShaderMaterial({
        uniforms: {
            map:             { value: null },
            u_c1:            { value: safeColorVec4(c1 || '#ffffff') },
            u_c2:            { value: _strokeC2Vec4(c1, c2) },
            u_time:          { value: 0.0 },
            u_mouseShift:    { value: 0.0 },
            u_bevel:         { value: bevel ? 1.0 : 0.0 },
            u_opacity:       { value: 1.0 },
            u_spinMode:      { value: 0 },   
            u_glyphCount:    { value: 0 },
            u_glyphs:        { value: emptyGlyphs },
            u_glyphStagger:  { value: 0.0 }
        },
        vertexShader: _SHARED_VERT, fragmentShader: STROKE_CONIC_FRAG,
        transparent: true, depthWrite: false, side: THREE.DoubleSide
    });
}

function _parseGradient(cs) {
    if (!cs) return null;

    var bgImg = cs.backgroundImage || '';
    if (!bgImg || bgImg === 'none') return null;
    var m = bgImg.match(/linear-gradient\((.+)\)/);
    if (!m) return null;
    var raw = m[1], parts = [], d = 0, cur = '';
    for (var i = 0; i < raw.length; i++) {
        var c = raw[i];
        if (c === '(') { d++; cur += c; }
        else if (c === ')') { d--; cur += c; }
        else if (c === ',' && d === 0) { parts.push(cur.trim()); cur = ''; }
        else { cur += c; }
    }
    if (cur.trim()) parts.push(cur.trim());
    var angle = 135, colors = [];
    parts.forEach(function(p) {
        var am = p.match(/^(\d+)deg$/); if (am) { angle = parseInt(am[1]); return; }
        var cm = p.match(/^(#[0-9a-f]{3,8}|rgba?\([^)]+\))/i); if (cm) colors.push(cm[1]);
    });
    return colors.length >= 2 ? { angle: angle, color1: colors[0], color2: colors[colors.length-1] } : null;
}

function _readLineText(lineEl) {
    if (!lineEl || !lineEl.childNodes || !lineEl.childNodes.length) {
        return (lineEl && lineEl.textContent) || '';
    }
    var t = '';
    for (var i = 0; i < lineEl.childNodes.length; i++) {
        var n = lineEl.childNodes[i];
        if (n.nodeType === 3) t += n.nodeValue;
        else if (n.nodeName === 'BR') t += '\n';
        else if (n.textContent) t += n.textContent;
    }
    return t;
}

function _wrapTextLines(text, maxContentW, fontStr, letterSpacing) {
    var mc = document.createElement('canvas');
    var mx = mc.getContext('2d');
    mx.font = fontStr;
    if (letterSpacing !== 0 && 'letterSpacing' in mx) mx.letterSpacing = letterSpacing + 'px';
    var paragraphs = (text || '').split('\n');
    var out = [];
    paragraphs.forEach(function(para) {
        var words = para.split(/\s+/).filter(function(w) { return w.length > 0; });
        if (!words.length) { out.push(''); return; }
        var line = '';
        for (var i = 0; i < words.length; i++) {
            var test = line ? line + ' ' + words[i] : words[i];
            if (mx.measureText(test).width > maxContentW && line) {
                out.push(line);
                line = words[i];
            } else {
                line = test;
            }
        }
        if (line) out.push(line);
    });
    return out.length ? out : [text || ''];
}

function lineToTexture(lineEl, lsOverride, maxContentW) {
    var cs = window.getComputedStyle(lineEl);
    var text = _readLineText(lineEl);
    if (!text.trim()) return null;
    var fontSize = parseFloat(cs.fontSize) || 48;
    var fontFamily = cs.fontFamily || 'sans-serif';

    

    
    
    var fontWeight = _resolveAvailableWeight(fontFamily, cs.fontWeight || '700', fontSize);
    var color = cs.color || '#000000';

    var grad = null;
    var dFill = lineEl.getAttribute('data-fill');
    if (dFill === 'gradient') {
        grad = {
            angle:  parseInt(lineEl.getAttribute('data-grad-angle')) || 135,
            color1: lineEl.getAttribute('data-grad-1') || '#000000',
            color2: lineEl.getAttribute('data-grad-2') || '#ffffff'
        };
    } else if (!dFill) {
        
        grad = _parseGradient(cs);
    }
    
    var letterSpacing = (lsOverride !== undefined && lsOverride !== null) ? lsOverride : (parseFloat(cs.letterSpacing) || 0);

    

    

    
    
    var _edScale = Math.max(0.25, window._impulseEditorScale || 1);

    

    

    

    

    var dpr = Math.min((window.devicePixelRatio || 1) * Math.max(1 / _edScale, _edScale), 4);
    var fontStr = fontWeight + ' ' + fontSize + 'px ' + fontFamily;
    var mc = document.createElement('canvas');
    var mx = mc.getContext('2d');
    mx.font = fontStr;
    if (letterSpacing !== 0 && 'letterSpacing' in mx) mx.letterSpacing = letterSpacing + 'px';
    var met = mx.measureText(text);
    var _lsExtra = letterSpacing > 0 ? Math.ceil(letterSpacing) : 0;
    var rightExtent = Math.ceil(met.actualBoundingBoxRight != null ? met.actualBoundingBoxRight : met.width);

    
    var singleLineTw = rightExtent + 16 + _lsExtra;

    
    
    var lines = (maxContentW && maxContentW > 40 && singleLineTw > maxContentW) || text.indexOf('\n') >= 0
        ? _wrapTextLines(text, (maxContentW && maxContentW > 40 ? maxContentW - 16 - _lsExtra : 1e6), fontStr, letterSpacing)
        : [text];

    
    var lineWidths = lines.map(function(ln) {
        return Math.ceil(mx.measureText(ln).width);
    });
    var widestLine = lineWidths.reduce(function(a, b) { return Math.max(a, b); }, 0);

    var firstMet = mx.measureText(lines[0] || text);
    var leftOverhang = Math.max(0, Math.ceil(-(firstMet.actualBoundingBoxLeft || 0)));
    var asc  = Math.max(Math.ceil(met.actualBoundingBoxAscent  || 0), Math.ceil(fontSize * 0.80));
    var desc = Math.max(Math.ceil(met.actualBoundingBoxDescent || 0), Math.ceil(fontSize * 0.45));
    var vpad = Math.max(12, Math.ceil(fontSize * 0.22));
    var lineHeight = asc + desc; 
    var tw = widestLine + leftOverhang + 16 + _lsExtra;
    var textX = leftOverhang + 8;
    var th = lineHeight * lines.length + vpad * 2;
    var cv = document.createElement('canvas');
    cv.width = tw * dpr; cv.height = th * dpr;
    var ctx = cv.getContext('2d');
    ctx.scale(dpr, dpr);
    ctx.clearRect(0, 0, tw, th);
    ctx.font = fontStr;
    if (letterSpacing !== 0 && 'letterSpacing' in ctx) ctx.letterSpacing = letterSpacing + 'px';
    ctx.textBaseline = 'alphabetic';
    ctx.fillStyle = 'white';

    
    var dtEl = lineEl.closest ? lineEl.closest('.impulse-display-text') : null;
    var domAlign = dtEl ? (window.getComputedStyle(dtEl).textAlign || 'center') : 'center';
    var canvasAlign = (domAlign === 'right') ? 'right' : (domAlign === 'left' || domAlign === 'start') ? 'left' : 'center';
    ctx.textAlign = canvasAlign;
    var lineX = canvasAlign === 'left' ? textX : (canvasAlign === 'right' ? tw - 8 : tw / 2);
    for (var li = 0; li < lines.length; li++) {
        var ly = vpad + asc + li * lineHeight;
        ctx.fillText(lines[li], lineX, ly);
    }
    var tex = new THREE.CanvasTexture(cv);
    tex.minFilter = tex.magFilter = THREE.LinearFilter;
    tex.generateMipmaps = false;
    tex._w = tw; tex._h = th; tex._gradient = grad; tex._solidColor = color;
    tex._lineCount = lines.length;
    return tex;
}

function _resolveAvailableWeight(family, requested, sizePx) {
    void sizePx; 
    var fam = (family || '').split(',')[0].trim().replace(/^["']|["']$/g, '');
    var req = String(requested || '400');
    if (!fam || !document.fonts || typeof document.fonts.forEach !== 'function') return req;
    var famLc = fam.toLowerCase();
    var avail = {};
    document.fonts.forEach(function(ff) {
        if (!ff || !ff.family) return;
        if (ff.style && ff.style !== 'normal') return; 
        if (ff.family.toLowerCase().replace(/^["']|["']$/g, '') !== famLc) return;

        var wStr = String(ff.weight || '400');
        var range = wStr.split(/\s+/);
        if (range.length === 2) {
            var lo = parseInt(range[0]) || 400;
            var hi = parseInt(range[1]) || lo;
            for (var w = lo; w <= hi; w += 100) avail[w] = 1;
        } else {
            avail[parseInt(wStr) || 400] = 1;
        }
    });
    var ks = Object.keys(avail).map(function(k){return parseInt(k);});
    if (!ks.length) return req; 
    var reqN = parseInt(req) || 400;
    if (avail[reqN]) return String(reqN);

    ks.sort(function(a, b) { return a - b; });
    var below = -1;
    for (var i = 0; i < ks.length; i++) {
        if (ks[i] <= reqN) below = ks[i];
        else break;
    }
    if (below >= 0) return String(below);
    return String(ks[0]); 
}

function strokeToTexture(lineEl, strokeWidth, strokeColor, lsOverride, maxContentW) {

    

    var cs = window.getComputedStyle(lineEl);
    var text = _readLineText(lineEl);
    if (!text.trim()) return null;
    var fontSize = parseFloat(cs.fontSize) || 48;
    var fontFamily = cs.fontFamily || 'sans-serif';

    

    
    
    var fontWeight = _resolveAvailableWeight(fontFamily, cs.fontWeight || '700', fontSize);
    var sc = '#ffffff';
    var letterSpacing = (lsOverride !== undefined && lsOverride !== null) ? lsOverride : (parseFloat(cs.letterSpacing) || 0);

    

    

    
    
    var _edScale = Math.max(0.25, window._impulseEditorScale || 1);

    
    var dpr = Math.min((window.devicePixelRatio || 1) * Math.max(1 / _edScale, _edScale), 4);
    var fontStr = fontWeight + ' ' + fontSize + 'px ' + fontFamily;
    var mc = document.createElement('canvas');
    var mx = mc.getContext('2d');
    mx.font = fontStr;
    if (letterSpacing !== 0 && 'letterSpacing' in mx) mx.letterSpacing = letterSpacing + 'px';
    var met = mx.measureText(text);
    var _lsExtra = letterSpacing > 0 ? Math.ceil(letterSpacing) : 0;
    var rightExtent = Math.ceil(met.actualBoundingBoxRight != null ? met.actualBoundingBoxRight : met.width);
    var singleLineTw = rightExtent + 16 + _lsExtra;

    var lines = (maxContentW && maxContentW > 40 && singleLineTw > maxContentW) || text.indexOf('\n') >= 0
        ? _wrapTextLines(text, (maxContentW && maxContentW > 40 ? maxContentW - 16 - _lsExtra : 1e6), fontStr, letterSpacing)
        : [text];
    var lineWidths = lines.map(function(ln) {
        return Math.ceil(mx.measureText(ln).width);
    });
    var widestLine = lineWidths.reduce(function(a, b) { return Math.max(a, b); }, 0);
    var firstMet = mx.measureText(lines[0] || text);
    var leftOverhang = Math.max(0, Math.ceil(-(firstMet.actualBoundingBoxLeft || 0)));
    var asc  = Math.max(Math.ceil(met.actualBoundingBoxAscent  || 0), Math.ceil(fontSize * 0.80));
    var desc = Math.max(Math.ceil(met.actualBoundingBoxDescent || 0), Math.ceil(fontSize * 0.45));
    var vpad = Math.max(12, Math.ceil(fontSize * 0.22));
    var lineHeight = asc + desc;
    var tw = widestLine + leftOverhang + 16 + _lsExtra;
    var th = lineHeight * lines.length + vpad * 2;
    var cv = document.createElement('canvas');
    cv.width = tw * dpr; cv.height = th * dpr;
    var ctx = cv.getContext('2d');
    ctx.scale(dpr, dpr);
    ctx.clearRect(0, 0, tw, th);
    ctx.font = fontStr;
    if (letterSpacing !== 0 && 'letterSpacing' in ctx) ctx.letterSpacing = letterSpacing + 'px';
    ctx.textBaseline = 'alphabetic';
    var dtEl = lineEl.closest ? lineEl.closest('.impulse-display-text') : null;
    var domAlign = dtEl ? (window.getComputedStyle(dtEl).textAlign || 'center') : 'center';
    var canvasAlign = (domAlign === 'right') ? 'right' : (domAlign === 'left' || domAlign === 'start') ? 'left' : 'center';
    ctx.textAlign = canvasAlign;
    ctx.strokeStyle = sc; ctx.lineWidth = strokeWidth; ctx.lineJoin = 'round';
    var lineX = canvasAlign === 'left' ? (leftOverhang + 8) : (canvasAlign === 'right' ? tw - 8 : tw / 2);

    

    

    
    
    var glyphBboxes = [];
    for (var li = 0; li < lines.length; li++) {
        var line = lines[li];
        var ly = vpad + asc + li * lineHeight;
        ctx.strokeText(line, lineX, ly);

        
        var lineMeasure = ctx.measureText(line);
        var lineWidth   = lineMeasure.width;
        var startX;
        if (canvasAlign === 'left')       startX = lineX;
        else if (canvasAlign === 'right') startX = lineX - lineWidth;
        else                              startX = lineX - lineWidth / 2;

        var prevCum = 0;
        for (var ci = 0; ci < line.length; ci++) {
            if (glyphBboxes.length >= STROKE_PULSE_MAX_GLYPHS) break;
            var ch = line.charAt(ci);

            var cum = ctx.measureText(line.substring(0, ci + 1)).width;
            var chW = cum - prevCum;
            if (/\s/.test(ch)) { prevCum = cum; continue; }
            var chX = startX + prevCum;
            var chY = ly - asc;
            glyphBboxes.push({
                x: chX,        y: chY,
                w: chW,        h: asc + desc
            });
            prevCum = cum;
        }
        if (glyphBboxes.length >= STROKE_PULSE_MAX_GLYPHS) break;
    }

    
    var glyphUV = glyphBboxes.map(function(b) {
        var cx = (b.x + b.w / 2) / tw;
        var cy = (b.y + b.h / 2) / th;
        var hw = (b.w / 2) / tw;
        var hh = (b.h / 2) / th;
        return new THREE.Vector4(cx, cy, hw, hh);
    });

    var tex = new THREE.CanvasTexture(cv);
    tex.minFilter = tex.magFilter = THREE.LinearFilter;
    tex.generateMipmaps = false;
    tex._w = tw; tex._h = th; tex._gradient = null;
    tex._glyphs = glyphUV; 
    return tex;
}

function createScene(container, dtEl, settings) {

    
    
    if (window._impulseWebGLOK === false) return null;

    var w = dtEl.offsetWidth;
    var h = dtEl.offsetHeight;

    var lineEl = dtEl.querySelector('.impulse-dt-line[data-line="1"]');
    if (!lineEl || !(lineEl.textContent || '').trim()) return null;

    var perspective = parseInt(settings.perspective) || 800;
    var dof         = parseInt(settings.dof)         || 0;
    var shadow      = !!settings.shadow;
    var mouseOrbit  = !!settings.mouse;

    
    var autoTilt    = !!settings.autoTilt;
    
    var strokeWidth = Math.max(1, Math.min(10, parseInt(settings.strokeWidth) || 1));
    var layerGap    = parseInt(settings.layerGap)    || 0;
    var strokeEnabled = !!settings.strokeEnabled;
    var strokeColor = settings.line1stroke || '';
    var strokeColor2 = settings.line1stroke2 || '';
    var strokeRotateSpeed = parseFloat(settings.strokeRotateSpeed) || 0;
    var strokeBevel = !!settings.strokeBevel;

    var strokeSpinMode = (settings.strokeSpinMode === 'pulse') ? 'pulse' : 'sweep';
    var lineDepth  = parseInt(settings.lineDepth)  || 0;
    var _letterSpacing = parseInt(settings.letterSpacing) || 0;
    var lineYRot   = parseInt(settings.lineYRot)   || 0;
    var mouseSens  = (settings.mouseSens != null && settings.mouseSens !== '' ? parseFloat(settings.mouseSens) : 5) / 100;

    
    var _hasEffects = parseFloat(settings.dof) > 0 || settings.shadow || settings.strokeEnabled;
    var orbitSpeed = 0.01 + ((settings.orbitSpeed != null && settings.orbitSpeed !== '' ? parseFloat(settings.orbitSpeed) : 5) / 10) * 0.14;

    
    var _instId   = parseInt((container.getAttribute('data-id') || '0'), 10) || 0;
    var _jitter   = ((_instId * 2654435761) >>> 0) / 4294967296; 
    
    orbitSpeed    = orbitSpeed * (0.80 + _jitter * 0.40);

    var _idlePhase = _jitter * Math.PI * 2;

    var maxFontSize = parseFloat(window.getComputedStyle(lineEl).fontSize) || 48;

    var maxAbsDepthPad = Math.abs(lineDepth);
    var _p = perspective || 800;
    var _scale = maxAbsDepthPad > 0 ? _p / Math.max(_p - maxAbsDepthPad, 1) : 1;
    var depthPad = Math.round(h * (_scale - 1) / 2) + (mouseOrbit ? Math.round(h * 0.15) : 0);

    var pad = 32 + depthPad;

    

    var _estimateFontPx = parseFloat(window.getComputedStyle(lineEl).fontSize) || 48;
    var _estimateFh = Math.ceil(_estimateFontPx * 1.85);
    var ch  = Math.max(h + pad * 2, _estimateFh + pad * 2);

    

    var hPerspectivePad = Math.round(w * (_scale - 1) / 2);
    var hShadowPad      = shadow ? 14 : 0;
    var hOrbitPad       = mouseOrbit ? Math.round(w * 0.04) : 0;
    var horizPad        = hPerspectivePad + hShadowPad + hOrbitPad + 16;
    var cw  = w + horizPad * 2;

    

    if (window.getComputedStyle(dtEl).position === 'static') dtEl.style.position = 'relative';
    dtEl.style.overflow = 'visible';

    dtEl.style.isolation = 'isolate';
    
    if (container && container !== dtEl) container.style.overflow = 'visible';

    
    
    var _sectionEl = container.closest ? container.closest('.impulse-section') : null;
    if (_sectionEl) _sectionEl.style.overflow = 'visible';
    
    var _overflowEls = [];
    var _node = dtEl.parentElement;
    while (_node && _node !== document.body) {
        var _ov = window.getComputedStyle(_node).overflow;
        if (_ov === 'hidden' || _ov === 'clip' || _ov === 'scroll' || _ov === 'auto') {
            _overflowEls.push({ el: _node, was: _node.style.overflow });
            _node.style.overflow = 'visible';
        }
        _node = _node.parentElement;
    }

    

    

    

    

    

    
    
    var _cwHalf = cw / 2;
    var canvasWrap = document.createElement('div');
    canvasWrap.className = 'impulse-3d-canvas-wrap';
    canvasWrap.style.cssText = 'position:absolute;left:calc(50% - ' + _cwHalf + 'px);top:' + (-pad) + 'px;width:' + cw + 'px;height:' + ch + 'px;pointer-events:none;z-index:2;transform-style:flat;isolation:isolate';
    dtEl.appendChild(canvasWrap);

    var canvas = document.createElement('canvas');
    canvas.className = 'impulse-3d-text-canvas';
    canvas.style.cssText = 'position:absolute;left:0;top:0;pointer-events:none';
    canvasWrap.appendChild(canvas);

    

    

    

    var _edScale = Math.max(0.25, window._impulseEditorScale || 1);

    

    var dpr = Math.min((window.devicePixelRatio || 1) * Math.max(1 / _edScale, _edScale), 4);

    

    

    var renderer;
    try {
        renderer = new THREE.WebGLRenderer({ canvas:canvas, alpha:true, antialias:true, preserveDrawingBuffer:true });
    } catch (_glErr) {
        try { canvasWrap.remove(); } catch (e) {}
        
        if (window._impulseWebGLOK !== false) {
            window._impulseWebGLOK = false;
            if (window.console && console.info) {
                console.info('[Impulse] WebGL unavailable — 3D engines fall back to flat rendering for the rest of this session.');
            }
        }
        return null;
    }
    renderer.setPixelRatio(dpr);
    renderer.setSize(cw, ch);
    canvas.style.width  = cw + 'px';
    canvas.style.height = ch + 'px';
    renderer.setClearColor(0x000000, 0);

    
    var blurCamera = new THREE.OrthographicCamera(-0.5, 0.5, 0.5, -0.5, 0.1, 10);
    blurCamera.position.z = 1;
    var blurScene = new THREE.Scene();
    var blurQuad  = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), null);
    blurScene.add(blurQuad);

    

    var wrapperEl       = container;
    var revealType      = (wrapperEl.dataset.reveal || 'none').toLowerCase();
    var revealIntensity = (parseInt(wrapperEl.dataset.revealIntensity) || 50) / 100;

    
    var revealTriggered = (revealType === 'none'); 
    if (revealType !== 'none') {
        
        wrapperEl.classList.remove('impulse-reveal','impulse-reveal-rise','impulse-reveal-depth','impulse-reveal-curtain','impulse-reveal-stack');
        wrapperEl.style.opacity   = '';
        wrapperEl.style.transform = '';
        wrapperEl.style.clipPath  = '';
    }

    var scene = new THREE.Scene();
    var fov = 2 * Math.atan(ch / 2 / perspective) * (180 / Math.PI);
    var camera = new THREE.PerspectiveCamera(fov, cw / ch, 1, 5000);
    camera.position.z = perspective;

    var group = new THREE.Group();
    scene.add(group);

    var maxAbsDepth = Math.abs(lineDepth);

    var planes = [];

    function buildPlanes() {

        
        
        var _textAlign = window.getComputedStyle(dtEl).textAlign || 'center';
        planes.forEach(function(p) {
            if (p.fillTex) p.fillTex.dispose();
            if (p.strokeTex) p.strokeTex.dispose();
            if (p.fillGeo) p.fillGeo.dispose();
            if (p.fillMat) p.fillMat.dispose();
            if (p.gradMat && p.gradMat !== p.fillMat) p.gradMat.dispose();
            if (p.hRT)  { p.hRT.dispose();  p.hRT  = null; }
            if (p.hMat) { p.hMat.dispose(); p.hMat = null; }
            if (p.strokeGeo) p.strokeGeo.dispose();
            if (p.strokeMat) p.strokeMat.dispose();
            group.remove(p.fillMesh);
            if (p.strokeMesh) group.remove(p.strokeMesh);
            if (p.shadowMesh) { p.shadowMesh.geometry.dispose(); p.shadowMesh.material.dispose(); group.remove(p.shadowMesh); }
        });
        planes = [];

        (function() {
            var depth   = lineDepth;

            

            
            
            var fillTex = lineToTexture(lineEl, _letterSpacing, w);
            if (!fillTex) return;

            var fw = fillTex._w, fh = fillTex._h;

            
            
            if (fh > ch) {
                ch = fh + pad * 2;
                canvasWrap.style.height = ch + 'px';
                renderer.setSize(cw, ch);
                canvas.style.height = ch + 'px';
                var _newFov = 2 * Math.atan(ch / 2 / perspective) * (180 / Math.PI);
                camera.fov = _newFov;
                camera.aspect = cw / ch;
                camera.updateProjectionMatrix();
            }

            var fillGeo = new THREE.PlaneGeometry(fw, fh);
            var hRT = new THREE.WebGLRenderTarget(fillTex._w, fillTex._h, {
                minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter,
                format: THREE.RGBAFormat
            });
            var hMat = makeHBlurMaterial(fillTex);
            var gradMat = null, fillMat;
            if (fillTex._gradient) {
                gradMat = makeVGradMaterial(fillTex._gradient, fillTex._h);
                gradMat.uniforms.map.value = fillTex;
                fillMat = gradMat;
            } else {
                fillMat = makeVSolidMaterial(fillTex._solidColor || '#ffffff', fillTex._h);
                fillMat.uniforms.map.value = fillTex;
            }
            var fillMesh = new THREE.Mesh(fillGeo, fillMat);

            
            var cy = 0;
            
            var _cxScreen = (_textAlign === 'left' || _textAlign === 'start') ? -(w - fw) / 2
                           : (_textAlign === 'right' || _textAlign === 'end') ? (w - fw) / 2 : 0;
            var cx = (depth !== 0) ? _cxScreen * (perspective - depth) / perspective : _cxScreen;
            fillMesh.position.set(cx, cy, depth);
            
            fillMesh.rotation.y = lineYRot * Math.PI / 180;
            group.add(fillMesh);

            
            var _baseGradAngle = (gradMat && gradMat.uniforms && gradMat.uniforms.u_angle)
                ? gradMat.uniforms.u_angle.value : 0;
            var entry = {
                lineNum:1, depth:depth, fw:fw,
                fillTex:fillTex, fillGeo:fillGeo, fillMat:fillMat, fillMesh:fillMesh, gradMat:gradMat,
                hRT:hRT, hMat:hMat,
                _baseCX:cx, _baseY:cy, _baseGradAngle:_baseGradAngle, _revealT:1.0, _revealStart:null,
                strokeTex:null, strokeGeo:null, strokeMat:null, strokeMesh:null,
                shadowMesh:null
            };

            if (strokeEnabled && strokeColor) {
                var stTex = strokeToTexture(lineEl, strokeWidth, strokeColor, _letterSpacing, w);
                if (stTex) {
                    
                    var stGeo = new THREE.PlaneGeometry(fw, fh);

                    var stMat = makeStrokeConicMaterial(strokeColor, strokeColor2, strokeBevel);
                    stMat.uniforms.map.value = stTex;

                    
                    
                    if (stTex._glyphs && stTex._glyphs.length > 0) {
                        var _glyphArr = stTex._glyphs.slice();
                        while (_glyphArr.length < STROKE_PULSE_MAX_GLYPHS) {
                            _glyphArr.push(new THREE.Vector4(0, 0, 0, 0));
                        }
                        stMat.uniforms.u_glyphs.value = _glyphArr;
                        stMat.uniforms.u_glyphCount.value = Math.min(stTex._glyphs.length, STROKE_PULSE_MAX_GLYPHS);
                    }
                    stMat.uniforms.u_spinMode.value = (strokeSpinMode === 'pulse') ? 1 : 0;

                    
                    var _gcount = stTex._glyphs ? stTex._glyphs.length : 0;
                    stMat.uniforms.u_glyphStagger.value = _gcount > 0
                        ? (Math.PI * 2 / Math.max(1, _gcount))
                        : 0;
                    var stMesh = new THREE.Mesh(stGeo, stMat);

                    
                    var _strokeZ = layerGap > 0 ? depth + layerGap : depth + 0.5;
                    stMesh.position.set(cx, cy, _strokeZ);

                    

                    
                    
                    var _persp = perspective || 800;
                    if (_strokeZ !== 0 && _strokeZ < _persp) {
                        var _ssc = (_persp - _strokeZ) / _persp;
                        stMesh.scale.set(_ssc, _ssc, 1);
                        entry.strokeBaseScale = _ssc;
                    } else {
                        entry.strokeBaseScale = 1;
                    }
                    group.add(stMesh);
                    entry.strokeTex=stTex; entry.strokeGeo=stGeo; entry.strokeMat=stMat; entry.strokeMesh=stMesh;

                    
                    
                    entry.strokeDepth = _strokeZ;
                }
            }

            

            

            function _alphaZeroStr(c) {
                if (!c) return false;
                if (c === 'transparent') return true;
                var m = c.match(/rgba?\([^)]*,\s*([\d.]+)\s*\)/i);
                return !!(m && parseFloat(m[1]) === 0);
            }
            var _fillInvisible = false;
            if (fillTex._gradient) {
                _fillInvisible = _alphaZeroStr(fillTex._gradient.color1)
                              && _alphaZeroStr(fillTex._gradient.color2);
            } else {
                _fillInvisible = _alphaZeroStr(fillTex._solidColor || '');
            }

            if (shadow && Math.abs(depth) > 10 && !_fillInvisible) {
                var ratio = Math.abs(depth) / 300;
                var sMat = new THREE.MeshBasicMaterial({ map:fillTex, transparent:true, depthWrite:false, color:new THREE.Color(0,0,0), opacity:0.12+ratio*0.18 });
                var sMesh = new THREE.Mesh(fillGeo.clone(), sMat);
                sMesh.position.set(cx + ratio*2, cy - 1 - ratio*4, depth - 2);
                sMesh.scale.set(1.01, 1.01, 1);
                group.add(sMesh);
                entry.shadowMesh = sMesh;
            }

            planes.push(entry);
        })();
    }

    buildPlanes();

    

    var _revealObs = null;
    if (revealType !== 'none') {
        planes.forEach(function(p) {
            if (p.fillMat && p.fillMat.uniforms.u_opacity) p.fillMat.uniforms.u_opacity.value = 0;
            p._revealT = 0.0;
        });
        if ('IntersectionObserver' in window) {
            _revealObs = new IntersectionObserver(function(entries) {
                if (!entries[0].isIntersecting || revealTriggered) return;
                revealTriggered = true;
                _revealObs.disconnect();
                planes.forEach(function(p) {
                    p._revealStart = performance.now();
                });
            }, { threshold: 0.12 });
            _revealObs.observe(wrapperEl);
        } else {
            revealTriggered = true;
        }
    }

    var tagEl = dtEl.querySelector('h1,h2,h3,h4,h5,h6,p,div.impulse-display-text > div');

    

    

    

    
    
    if (tagEl) tagEl.style.opacity = '0';

    var mouseX = 0.5, mouseY = 0.5, targetMX = 0.5, targetMY = 0.5;
    var _idleMix = 0; 
    var _swayPhase = 0; 

    

    
    var _gradMouseLerp = 0.5;
    var _lastMouseTime = 0;
    function onMouseMove(e) {
        targetMX = e.clientX / (window.innerWidth  || 1);
        targetMY = e.clientY / (window.innerHeight || 1);
        _lastMouseTime = Date.now();
    }
    document.addEventListener('mousemove', onMouseMove);

    

    
    
    function onMouseOut(e) {

        if (e.relatedTarget || e.toElement) return;
        targetMX = 0.5; targetMY = 0.5;
        _lastMouseTime = 0;
    }
    document.addEventListener('mouseout', onMouseOut);

    window.addEventListener('blur', function() {
        targetMX = 0.5; targetMY = 0.5;
        _lastMouseTime = 0;
    });

    

    

    

    

    
    
    var _isMobile = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
    function _readTiltBus() {
        if (!_isMobile) {

            

            if (_lastMouseTime === 0) return false;
            if (Date.now() - _lastMouseTime > 1500) return false;
        }
        var t = window._impulseTilt;
        if (!t || !isFinite(t.x) || !isFinite(t.y)) return false;
        targetMX = 0.5 + t.x * 0.5;
        targetMY = 0.5 + t.y * 0.5;

        
        return true;
    }

    var running = true, rafId, time = 0;

    

    

    
    var _sceneAncestorSection = null;
    var _sceneAncestorChecked = false;
    function _isHiddenInScene() {
        if (!_sceneAncestorChecked) {
            _sceneAncestorChecked = true;
            try {
                var node = dtEl;
                while (node && node !== document.body) {
                    if (node.classList && node.classList.contains('impulse-el') && node.dataset && node.dataset.type === 'section') {
                        _sceneAncestorSection = node;
                        break;
                    }
                    node = node.parentElement;
                }
            } catch (_e) {}

            if (!document.body || document.body.dataset.screenMode !== '1') {
                _sceneAncestorSection = null;
            }
        }
        if (!_sceneAncestorSection) return false;

        var v = _sceneAncestorSection.style.visibility;
        if (v === 'hidden') return true;

        if (!v) {
            try { v = getComputedStyle(_sceneAncestorSection).visibility; } catch (_e) {}
            if (v === 'hidden') return true;
        }
        return false;
    }
    function animate() {
        if (!running) return;
        rafId = requestAnimationFrame(animate);

        
        
        if (_isHiddenInScene()) return;
        time += 0.016;
        _readTiltBus();

        

        

        
        if (!_isMobile && _lastMouseTime && (Date.now() - _lastMouseTime) > 1500) {
            targetMX += (0.5 - targetMX) * 0.04;
            targetMY += (0.5 - targetMY) * 0.04;
        }

        

        

        
        var _idle = autoTilt &&
            (_lastMouseTime === 0 || (Date.now() - _lastMouseTime) > 1500);
        _idleMix += ((_idle ? 1 : 0) - _idleMix) * 0.06;

        
        var _swayFreq = 0.4 + orbitSpeed * 4.0;
        _swayPhase += _swayFreq * 0.016;
        var _swayX = 0.5 + 0.10  * Math.sin(_swayPhase + _idlePhase);
        var _swayY = 0.5 + 0.035 * Math.sin(_swayPhase * 0.72 + _idlePhase * 0.7);
        var _effMX = (1 - _idleMix) * targetMX + _idleMix * _swayX;
        var _effMY = (1 - _idleMix) * targetMY + _idleMix * _swayY;

        var _effLerp = (1 - _idleMix) * orbitSpeed + _idleMix * Math.min(0.3, 0.08 + _swayFreq * 0.05);

        

        

        

        

        

        

        
        
        var _sNorm = strokeRotateSpeed / 10;            
        var _strokeAngularStep = strokeRotateSpeed > 0
            ? 0.016 * (_sNorm * _sNorm) * 13           
            : 0;

        

        

        

        

        

        

        

        

        

        

        
        var _strokeGain = (1 - _idleMix) * mouseSens + _idleMix * 0.7;
        var _strokeMouseShift;
        if (strokeSpinMode === 'pulse') {
            var _px = (_effMX - 0.5) * 2.0; 
            
            var _sign = _px < 0 ? -1 : 1;
            var _abs  = Math.abs(_px);
            
            var _eased = _abs * _abs * (3 - 2 * _abs);
            _strokeMouseShift = _sign * _eased * 6.28318530718 * _strokeGain * 3.0;
        } else {
            _strokeMouseShift = (mouseX - 0.5) * 6.28318530718 * _strokeGain;
        }
        planes.forEach(function(p) {
            if (p.strokeMat && p.strokeMat.uniforms) {
                if (strokeRotateSpeed > 0 && p.strokeMat.uniforms.u_time) {
                    p.strokeMat.uniforms.u_time.value += _strokeAngularStep;
                }
                if (p.strokeMat.uniforms.u_mouseShift) {
                    p.strokeMat.uniforms.u_mouseShift.value = _strokeMouseShift;
                }
            }
        });

        
        
        mouseX += (_effMX - mouseX) * _effLerp;
        mouseY += (_effMY - mouseY) * _effLerp;

        if (mouseOrbit) {
            planes.forEach(function(p) {
                if (p._mx == null) p._mx = 0.5;
                if (p._my == null) p._my = 0.5;

                p._mx += (_effMX - p._mx) * _effLerp;
                p._my += (_effMY - p._my) * _effLerp;

                
                var base = lineYRot * Math.PI / 180;
                var _rotGain = (1 - _idleMix) * mouseSens + _idleMix * 0.8;
                var _orbit = Math.max(-Math.PI / 12, Math.min(Math.PI / 12, (p._mx - 0.5) * _rotGain));
                var rot  = base + _orbit;
                p.fillMesh.rotation.y = rot;
                if (p.strokeMesh) p.strokeMesh.rotation.y = rot;
                if (p.shadowMesh) p.shadowMesh.rotation.y = rot;

                var pScale = 0.06;
                p._parallaxX = Math.max(-12, Math.min(12, (p._mx - 0.5) * p.depth * pScale));
                p._parallaxY = Math.max(-6,  Math.min(6,  (p._my - 0.5) * p.depth * pScale * 0.5));

                

                if (p.strokeMesh && p.strokeDepth !== undefined) {
                    p._strokeParallaxX = Math.max(-18, Math.min(18, (p._mx - 0.5) * p.strokeDepth * pScale));
                    p._strokeParallaxY = Math.max(-10, Math.min(10, (p._my - 0.5) * p.strokeDepth * pScale * 0.5));
                }
            });
            group.rotation.y = 0;
        } else {
            planes.forEach(function(p) { p._parallaxX = 0; p._parallaxY = 0; });
        }

        var shift = mouseX - 0.5;

        

        

        

        
        
        _gradMouseLerp += (mouseX - _gradMouseLerp) * 0.02;
        var _gradAngleShift = (_gradMouseLerp - 0.5) * mouseSens * 2;
        planes.forEach(function(p) {
            if (p.gradMat && p.gradMat.uniforms && p.gradMat.uniforms.u_angle && p._baseGradAngle != null) {
                p.gradMat.uniforms.u_angle.value = p._baseGradAngle + _gradAngleShift;
            }
        });
        var _revealRise = 60 * revealIntensity; 

        planes.forEach(function(p) {
            
            var bx = (p._baseCX != null) ? p._baseCX : 0;
            var by = (p._baseY  != null) ? p._baseY  : 0;
            var px = bx + (p._parallaxX || 0);
            var py = by + (p._parallaxY || 0);

            
            
            var spx = bx + (p._strokeParallaxX != null ? p._strokeParallaxX : (p._parallaxX || 0));
            var spy = by + (p._strokeParallaxY != null ? p._strokeParallaxY : (p._parallaxY || 0));

            if (!revealTriggered) {
                
                if (p.fillMat && p.fillMat.uniforms.u_opacity) p.fillMat.uniforms.u_opacity.value = 0;
                p.fillMesh.position.x = px; p.fillMesh.position.y = py;
                if (p.strokeMesh) { p.strokeMesh.position.x = spx; p.strokeMesh.position.y = spy; }
                if (p.shadowMesh) { p.shadowMesh.position.x = px; p.shadowMesh.position.y = py; }
                return;
            }

            if (p._revealT >= 1.0) {
                
                if (p.fillMat && p.fillMat.uniforms.u_opacity) p.fillMat.uniforms.u_opacity.value = 1;
                p.fillMesh.position.x = px; p.fillMesh.position.y = py;
                if (p.strokeMesh) { p.strokeMesh.position.x = spx; p.strokeMesh.position.y = spy; }
                if (p.shadowMesh) { p.shadowMesh.position.x = px; p.shadowMesh.position.y = py; }
                return;
            }

            if (p._revealStart == null) {
                if (p.fillMat && p.fillMat.uniforms.u_opacity) p.fillMat.uniforms.u_opacity.value = 0;
                p.fillMesh.position.x = px; p.fillMesh.position.y = py;
                return;
            }

            var elapsed = performance.now() - p._revealStart;
            var t = Math.min(elapsed / 800, 1.0);

            var _su = t - 1; t = (t <= 0) ? 0 : (t >= 1) ? 1 : (1 + 3 * _su * _su * _su + 2 * _su * _su);
            p._revealT = t;
            if (p.fillMat && p.fillMat.uniforms.u_opacity) p.fillMat.uniforms.u_opacity.value = t;

            var rise = (revealType === 'rise' || revealType === 'depth') ? (1 - t) * _revealRise : 0;
            var fy = py - rise;
            var sfy = spy - rise;
            p.fillMesh.position.x = px;   p.fillMesh.position.y = fy;
            if (p.strokeMesh) { p.strokeMesh.position.x = spx; p.strokeMesh.position.y = sfy; }
            if (p.shadowMesh) { p.shadowMesh.position.x = px; p.shadowMesh.position.y = fy; }
        });
        var dofFactor = dof / 10;

        planes.forEach(function(p) {
            var rot = p.fillMesh.rotation.y;
            var blurCenter = dof > 0 ? Math.min((Math.abs(p.depth) / 150) * dofFactor * 12.0, 12) : 0;
            if (dof > 0) blurCenter = Math.max(blurCenter, dofFactor * 2.0);
            var blurSlope  = dof > 0 ? p.fw * Math.sin(rot) / 150 * dofFactor * 12.0 : 0;
            var maxSigma = Math.max(
                Math.abs(blurCenter - Math.abs(blurSlope) * 0.5),
                Math.abs(blurCenter + Math.abs(blurSlope) * 0.5)
            );
            if (maxSigma >= 0.5 && p.hRT && p.hMat) {
                p.hMat.uniforms.u_blurBase.value = blurCenter;
                p.hMat.uniforms.u_blurEdge.value = blurSlope;
                blurQuad.material = p.hMat;
                renderer.setRenderTarget(p.hRT);
                renderer.render(blurScene, blurCamera);
                p.fillMat.uniforms.map.value = p.hRT.texture;
            } else {
                p.fillMat.uniforms.map.value = p.fillTex;
            }
            if (p.fillMat.uniforms.u_blurBase !== undefined) {
                p.fillMat.uniforms.u_blurBase.value = blurCenter;
                p.fillMat.uniforms.u_blurEdge.value = blurSlope;
            }
        });
        renderer.setRenderTarget(null); 

        planes.forEach(function(p) {
            if (p.gradMat) p.gradMat.uniforms.u_shift.value = shift;
        });
        renderer.render(scene, camera);
        
        if (container.style.opacity === '0') container.style.opacity = '';
    }
    animate();

    return {
        canvas:canvas, renderer:renderer, scene:scene, camera:camera, group:group, planes:planes, running:true,

        

        
        rebuild: buildPlanes,

        

        

        

        
        
        reinit: function(s) {
            if (!s) { buildPlanes(); return; }
            if (s.perspective !== undefined) {
                perspective = parseInt(s.perspective) || 800;
                var _fovR = 2 * Math.atan(ch / 2 / perspective) * (180 / Math.PI);
                camera.fov = _fovR; camera.position.z = perspective; camera.updateProjectionMatrix();
            }
            if (s.dof          !== undefined) dof          = parseInt(s.dof) || 0;
            if (s.shadow       !== undefined) shadow       = !!s.shadow;
            if (s.mouse        !== undefined) mouseOrbit   = !!s.mouse;
            if (s.strokeWidth  !== undefined) strokeWidth  = Math.max(1, Math.min(10, parseInt(s.strokeWidth) || 1));
            if (s.layerGap     !== undefined) layerGap     = parseInt(s.layerGap) || 0;
            if (s.strokeEnabled!== undefined) strokeEnabled= !!s.strokeEnabled;
            if (s.line1stroke  !== undefined) strokeColor  = s.line1stroke  || '';
            if (s.line1stroke2 !== undefined) strokeColor2 = s.line1stroke2 || '';
            if (s.strokeRotateSpeed !== undefined) strokeRotateSpeed = parseFloat(s.strokeRotateSpeed) || 0;
            if (s.strokeBevel  !== undefined) strokeBevel  = !!s.strokeBevel;
            if (s.strokeSpinMode !== undefined) strokeSpinMode = (s.strokeSpinMode === 'pulse') ? 'pulse' : 'sweep';
            if (s.lineDepths && s.lineDepths[1] !== undefined) lineDepth = parseInt(s.lineDepths[1]) || 0;
            if (s.lineYRots  && s.lineYRots[1]  !== undefined) lineYRot  = parseInt(s.lineYRots[1])  || 0;
            if (s.letterSpacing !== undefined) _letterSpacing = parseInt(s.letterSpacing) || 0;
            if (s.mouseSens  !== undefined) mouseSens  = (s.mouseSens != null && s.mouseSens !== '' ? parseFloat(s.mouseSens) : 5) / 100;
            if (s.orbitSpeed !== undefined) orbitSpeed = (0.01 + ((s.orbitSpeed != null && s.orbitSpeed !== '' ? parseFloat(s.orbitSpeed) : 5) / 10) * 0.14) * (0.80 + _jitter * 0.40);
            if (s.autoTilt   !== undefined) autoTilt   = !!s.autoTilt;
            buildPlanes();
        },

        

        resetCursor: function() {
            targetMX = 0.5; targetMY = 0.5;
            mouseX = 0.5; mouseY = 0.5;
            _lastMouseTime = 0;
            planes.forEach(function(p) { p._mx = 0.5; p._my = 0.5; p._parallaxX = 0; p._parallaxY = 0; p._strokeParallaxX = 0; p._strokeParallaxY = 0; });
        },
        update: function(prop, value) {
            if (prop === 'lineDepths') {
                lineDepth = (value && value[1] !== undefined) ? value[1] : lineDepth;
                var _lineSpan = dtEl.querySelector('.impulse-dt-line[data-line="1"]');
                if (_lineSpan) _lineSpan.setAttribute('data-depth', lineDepth);
                clearTimeout(this._rebuildTimer);
                var _self = this;
                this._rebuildTimer = setTimeout(function() { buildPlanes(); }, 60);
            } else if (prop === 'lineYRots') {
                lineYRot = (value && value[1] !== undefined) ? value[1] : lineYRot;

                
                
                planes.forEach(function(p) {
                    var base = lineYRot * Math.PI / 180;
                    p.fillMesh.rotation.y = base;
                    if (p.strokeMesh) p.strokeMesh.rotation.y = base;
                    if (p.shadowMesh) p.shadowMesh.rotation.y = base;
                });
            } else if (prop === 'strokeEnabled') {
                strokeEnabled = !!value;
                clearTimeout(this._rebuildTimer);
                var _seSelf = this;
                this._rebuildTimer = setTimeout(function() { buildPlanes(); }, 60);
            } else if (prop === 'layerGap') {
                var _prevGap = layerGap;
                layerGap = parseInt(value) || 0;
                if ((_prevGap > 0) !== (layerGap > 0)) {
                    
                    clearTimeout(this._rebuildTimer);
                    var _lgSelf = this;
                    this._rebuildTimer = setTimeout(function() { buildPlanes(); }, 60);
                } else {
                    planes.forEach(function(p) {
                        if (!p.strokeMesh) return;
                        var _zNew = p.depth + layerGap;
                        p.strokeMesh.position.z = _zNew;
                        p.strokeDepth = _zNew;

                        
                        var _persp = perspective || 800;
                        var _ssc = (_zNew !== 0 && _zNew < _persp) ? (_persp - _zNew) / _persp : 1;
                        p.strokeMesh.scale.set(_ssc, _ssc, 1);
                        p.strokeBaseScale = _ssc;
                    });
                }
            } else if (prop === 'mouseOrbit') {
                mouseOrbit = !!value; if (!mouseOrbit) { group.rotation.x = 0; }
            } else if (prop === 'mouseSens') {
                mouseSens = (value != null && value !== '' ? parseFloat(value) : 5) / 100;
            } else if (prop === 'orbitSpeed') {
                orbitSpeed = 0.01 + ((value != null && value !== '' ? parseFloat(value) : 5) / 10) * 0.14;
            } else if (prop === 'optics') {

                

                

                if (value.perspective !== undefined) {
                    perspective = parseInt(value.perspective) || 800;
                    var fovNew = 2 * Math.atan(ch / 2 / perspective) * (180 / Math.PI);
                    camera.fov = fovNew;
                    camera.position.z = perspective;
                    camera.updateProjectionMatrix();
                }
                if (value.dof !== undefined) dof = parseInt(value.dof) || 0;
                var _shadowChanged    = value.shadow    !== undefined && (!!value.shadow) !== shadow;
                var _perspectiveChanged = value.perspective !== undefined && parseInt(value.perspective) !== perspective;
                if (value.shadow    !== undefined) shadow    = !!value.shadow;
                var _prevLayerGap = layerGap;
                if (value.layerGap  !== undefined) {
                    layerGap = parseInt(value.layerGap) || 0;
                }

                
                if (value.lineDepth !== undefined) {
                    var _newDepth = parseInt(value.lineDepth) || 0;
                    lineDepth = _newDepth;
                    planes.forEach(function(p) {
                        p.depth = _newDepth;
                        if (p.fillMesh) p.fillMesh.position.z = _newDepth;
                        if (p.shadowMesh) p.shadowMesh.position.z = _newDepth - 2;
                    });
                }
                
                if (value.lineYRot !== undefined) {
                    var _newYRot = (parseFloat(value.lineYRot) || 0) * Math.PI / 180;
                    lineYRot = parseFloat(value.lineYRot) || 0;
                    planes.forEach(function(p) {
                        if (p.fillMesh) p.fillMesh.rotation.y = _newYRot;
                        if (p.strokeMesh) p.strokeMesh.rotation.y = _newYRot;
                        if (p.shadowMesh) p.shadowMesh.rotation.y = _newYRot;
                    });
                }

                if (value.layerGap !== undefined || value.lineDepth !== undefined) {
                    planes.forEach(function(p) {
                        if (!p.strokeMesh) return;
                        var _zNew = p.depth + (layerGap > 0 ? layerGap : 0.5);
                        p.strokeMesh.position.z = _zNew;
                        p.strokeDepth = _zNew;
                        var _persp = perspective || 800;
                        var _ssc = (_zNew !== 0 && _zNew < _persp) ? (_persp - _zNew) / _persp : 1;
                        p.strokeMesh.scale.set(_ssc, _ssc, 1);
                        p.strokeBaseScale = _ssc;
                    });
                }
                var _layerGapChanged = (_prevLayerGap > 0) !== (layerGap > 0);

                
                
                if (_shadowChanged || _perspectiveChanged || _layerGapChanged) {
                    clearTimeout(this._rebuildTimer);
                    var _self = this;
                    this._rebuildTimer = setTimeout(function() { buildPlanes(); }, 60);
                }
            } else if (prop === 'gradAngle') {

                
                
                var rad = (value != null ? parseFloat(value) : 135) * Math.PI / 180;
                planes.forEach(function(p) {
                    p._baseGradAngle = rad;
                    if (p.gradMat && p.gradMat.uniforms && p.gradMat.uniforms.u_angle) {
                        p.gradMat.uniforms.u_angle.value = rad;
                    }
                });
            } else if (prop === 'colors') {

                planes.forEach(function(p) {
                    var lc = value[1] || value[p.lineNum];
                    if (!lc) return;
                    if (p.gradMat && p.gradMat.uniforms) {
                        if (lc.grad1) p.gradMat.uniforms.u_c1.value = safeColorVec4(lc.grad1);
                        if (lc.grad2) p.gradMat.uniforms.u_c2.value = safeColorVec4(lc.grad2);
                    } else if (p.fillMat && p.fillMat.uniforms && p.fillMat.uniforms.u_color && lc.color) {

                        var _v = safeColorVec4(lc.color);
                        p.fillMat.uniforms.u_color.value.set(_v.x, _v.y, _v.z);
                        if (p.fillMat.uniforms.u_colorAlpha) {
                            p.fillMat.uniforms.u_colorAlpha.value = _v.w;
                        }
                    }
                });
            } else if (prop === 'fontSize' || prop === 'line1stroke' || prop === 'text') {
                if (prop === 'line1stroke') strokeColor = value || '';
                var self = this;
                clearTimeout(self._rebuildTimer);
                self._rebuildTimer = setTimeout(function() { buildPlanes(); }, 100);
            } else if (prop === 'strokeColors') {

                strokeColor  = (value && value.c1 != null) ? value.c1 : strokeColor;
                strokeColor2 = (value && value.c2 != null) ? value.c2 : strokeColor2;
                if (value && value.bevel != null)       strokeBevel       = !!value.bevel;
                if (value && value.rotateSpeed != null) strokeRotateSpeed = parseFloat(value.rotateSpeed) || 0;
                if (value && value.spinMode != null)    strokeSpinMode    = (value.spinMode === 'pulse') ? 'pulse' : 'sweep';
                planes.forEach(function(p) {
                    if (p.strokeMat && p.strokeMat.uniforms) {
                        p.strokeMat.uniforms.u_c1.value = safeColorVec4(strokeColor || '#ffffff');
                        p.strokeMat.uniforms.u_c2.value = _strokeC2Vec4(strokeColor, strokeColor2);
                        p.strokeMat.uniforms.u_bevel.value = strokeBevel ? 1.0 : 0.0;
                        if (p.strokeMat.uniforms.u_spinMode) {
                            p.strokeMat.uniforms.u_spinMode.value = (strokeSpinMode === 'pulse') ? 1 : 0;
                        }
                    }
                });
            } else if (prop === 'strokeWidth') {
                strokeWidth = Math.max(1, Math.min(10, parseInt(value) || 1));
                var _swSelf = this;
                clearTimeout(_swSelf._rebuildTimer);
                _swSelf._rebuildTimer = setTimeout(function() { buildPlanes(); }, 80);
            }
        },

        

        pause: function() {
            if (!running) return;
            running = false;
            if (rafId) { cancelAnimationFrame(rafId); rafId = null; }
        },
        resume: function() {
            if (running) return;
            running = true;
            time = (typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now()) / 1000;
            if (!rafId) rafId = requestAnimationFrame(animate);
        },
        destroy: function() {
            running = false;
            if (rafId) cancelAnimationFrame(rafId);
            if (this._pauseObs) { try { this._pauseObs.disconnect(); } catch(_e){} this._pauseObs = null; }
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseout', onMouseOut);
            planes.forEach(function(p) {
                if (p.fillTex) p.fillTex.dispose();
                if (p.strokeTex) p.strokeTex.dispose();
                if (p.fillGeo) p.fillGeo.dispose();
                if (p.fillMat) p.fillMat.dispose();
                if (p.gradMat) p.gradMat.dispose();
                if (p.strokeGeo) p.strokeGeo.dispose();
                if (p.strokeMat) p.strokeMat.dispose();
                if (p.shadowMesh) { p.shadowMesh.geometry.dispose(); p.shadowMesh.material.dispose(); }
            });

            

            

            

            try { renderer.forceContextLoss(); } catch (_e) {}
            renderer.dispose();
            if (canvas.parentNode) canvas.parentNode.removeChild(canvas);
            dtEl.style.position = '';
            dtEl.style.isolation = '';
            dtEl.style.overflow = '';
            if (container && container !== dtEl) container.style.overflow = '';
            if (canvasWrap && canvasWrap.parentNode) canvasWrap.parentNode.removeChild(canvasWrap);
            if (_sectionEl) _sectionEl.style.overflow = '';
            _overflowEls.forEach(function(o) { o.el.style.overflow = o.was; });
            if (_revealObs) { _revealObs.disconnect(); _revealObs = null; }
            if (revealType !== 'none') {

                wrapperEl.style.opacity   = '';
                wrapperEl.style.transform = '';
                wrapperEl.classList.add('impulse-reveal', 'impulse-reveal-' + revealType);
            }
            if (tagEl) { tagEl.style.opacity = ''; tagEl.style.visibility = ''; }
        }
    };
}

function initElement(elId, inlineSettings, overrideDtEl, instKey) {
    instKey = instKey || elId;
    if (instances[instKey]) { instances[instKey].destroy(); delete instances[instKey]; }
    var wrapper = document.querySelector('.impulse-el[data-id="' + elId + '"]');
    if (!wrapper) wrapper = document.getElementById('dt-' + elId); 
    var dtEl = overrideDtEl || (wrapper.classList.contains('impulse-display-text')
        ? wrapper
        : wrapper.querySelector('.impulse-display-text'));
    if (!dtEl) return;
    var mode = dtEl.dataset.dt3dMode || dtEl.getAttribute('data-dt-3d-mode') || '';
    if (!mode || mode === 'flat') mode = 'depth';
    if (mode !== 'depth' && mode !== '3d') return;

    var _lineEl = dtEl.querySelector('.impulse-dt-line[data-line="1"]');
    var _lineDepth = (_lineEl ? (parseInt(_lineEl.dataset.depth) || 0) : 0);
    var _lineYRot  = (_lineEl ? (parseInt(_lineEl.dataset.yrot)  || 0) : 0);
    if (inlineSettings && inlineSettings.lineDepths) {
        _lineDepth = (inlineSettings.lineDepths && inlineSettings.lineDepths[1] !== undefined) ? inlineSettings.lineDepths[1] : 0;
        if (_lineEl) _lineEl.setAttribute('data-depth', _lineDepth);
    }
    if (inlineSettings && inlineSettings.lineYRots) {
        _lineYRot = (inlineSettings.lineYRots && inlineSettings.lineYRots[1] !== undefined) ? inlineSettings.lineYRots[1] : 0;
    }

    var settings = {
        perspective:  (inlineSettings && inlineSettings.perspective) || dtEl.getAttribute('data-dt-perspective') || '800',

        

        
        
        mouse:        (inlineSettings && inlineSettings.mouse !== undefined)
            ? !!inlineSettings.mouse
            : (dtEl.getAttribute('data-dt-mouse') === '1'
                || _lineDepth !== 0 || _lineYRot !== 0
                || !!(dtEl.getAttribute('data-dt-dof'))
                || dtEl.getAttribute('data-dt-stroke-enabled') === '1'),
        dof:          (inlineSettings && inlineSettings.depthEffectsOn) ? (inlineSettings.dof || 0) : (dtEl.getAttribute('data-dt-dof') || '0'),
        shadow:       inlineSettings ? !!inlineSettings.shadow : dtEl.getAttribute('data-dt-shadow') === '1',
        strokeWidth:  (inlineSettings && inlineSettings.strokeWidth != null)
            ? parseInt(inlineSettings.strokeWidth)
            : (parseInt(dtEl.getAttribute('data-dt-stroke-width')) || 1),
        layerGap:     inlineSettings ? (parseInt(inlineSettings.layerGap) || 0) : (parseInt(dtEl.getAttribute('data-dt-layer-gap')) || 0),
        line1stroke:  (inlineSettings && inlineSettings.line1stroke) || dtEl.getAttribute('data-dt-line-1-stroke') || '',
        line1stroke2: (inlineSettings && inlineSettings.line1stroke2) || dtEl.getAttribute('data-dt-line-1-stroke-2') || '',
        strokeRotateSpeed: (inlineSettings && inlineSettings.strokeRotateSpeed != null)
            ? parseFloat(inlineSettings.strokeRotateSpeed)
            : (parseFloat(dtEl.getAttribute('data-dt-stroke-rotate-speed')) || 0),
        strokeBevel:  inlineSettings ? !!inlineSettings.strokeBevel : dtEl.getAttribute('data-dt-stroke-bevel') === '1',
        strokeSpinMode: (inlineSettings && inlineSettings.strokeSpinMode)
            ? inlineSettings.strokeSpinMode
            : (dtEl.getAttribute('data-dt-stroke-spin-mode') || 'sweep'),
        lineDepth:    _lineDepth,
        lineYRot:     _lineYRot,
        mouseSens:    (inlineSettings && inlineSettings.mouseSens  != null) ? inlineSettings.mouseSens  : (dtEl.getAttribute('data-dt-mouse-sens')  != null ? parseFloat(dtEl.getAttribute('data-dt-mouse-sens'))  : 5),
        orbitSpeed:   (inlineSettings && inlineSettings.orbitSpeed != null) ? inlineSettings.orbitSpeed : (dtEl.getAttribute('data-dt-orbit-speed')  != null ? parseFloat(dtEl.getAttribute('data-dt-orbit-speed'))  : 5),
        strokeEnabled: inlineSettings ? !!inlineSettings.strokeEnabled : dtEl.getAttribute('data-dt-stroke-enabled') === '1',
        letterSpacing: inlineSettings ? (parseInt(inlineSettings.letterSpacing) || 0) : (parseInt(dtEl.getAttribute('data-dt-letter-spacing')) || 0),
        autoTilt:     (inlineSettings && inlineSettings.autoTilt !== undefined) ? !!inlineSettings.autoTilt : (dtEl.getAttribute('data-dt-auto-tilt') === '1')
    };

    var _tagEl = dtEl.querySelector('h1,h2,h3,h4,h5,h6,p,div.impulse-display-text > div');

    

    
    if (wrapper && !wrapper.classList.contains('impulse-animated')) {
        wrapper.style.opacity = '0';
    }

    var inst = createScene(wrapper, dtEl, settings);
    if (inst) {
        inst._elSettings = inlineSettings || null;
        inst._needsFontRebuild = !_dtFontsReady(dtEl);
        inst._lastWidth = wrapper.offsetWidth;
        
        inst._dtEl = dtEl;
        instances[instKey] = inst;
    } else {
        
        if (wrapper) wrapper.style.opacity = '';
    }
}

function destroyElement(elId) { if (instances[elId]) { instances[elId].destroy(); delete instances[elId]; } }
function destroyAll() { for (var id in instances) { instances[id].destroy(); } instances = {}; }

function _isFontRendering(family, weight, size, text) {
    var t  = text || 'ABCDEFGHI';
    var w  = weight || '400';
    var sz = size || '48px';
    
    if (document.fonts && document.fonts.check) {
        try {
            if (document.fonts.check(w + ' ' + sz + ' "' + family + '"', t)) return true;
        } catch(e) {}
    }

    var mc = document.createElement('canvas').getContext('2d');
    mc.font = w + ' ' + sz + ' monospace';
    var base = mc.measureText(t).width;
    mc.font = w + ' ' + sz + ' "' + family + '", monospace';
    return mc.measureText(t).width !== base;
}

function _dtFontsReady(dtEl) {
    var ok = true;
    dtEl.querySelectorAll('.impulse-dt-line').forEach(function(el) {
        var cs  = window.getComputedStyle(el);
        var fam = cs.fontFamily.split(',')[0].trim().replace(/['"]/g, '');
        var low = fam.toLowerCase();
        if (!fam || low === 'serif' || low === 'sans-serif' || low === 'monospace' ||
            low.indexOf('-apple') === 0 || low.indexOf('system') === 0 || low.indexOf('segoe') === 0) return;
        if (!_isFontRendering(fam, cs.fontWeight, cs.fontSize, (el.textContent || '').trim() || 'Aa'))
            ok = false;
    });
    return ok;
}

function loadFontsForEl(dtEl, callback) {
    if (document.fonts && document.fonts.load) {
        dtEl.querySelectorAll('.impulse-dt-line').forEach(function(el) {
            var cs  = window.getComputedStyle(el);
            var fam = cs.fontFamily.split(',')[0].trim().replace(/['"]/g, '');
            var low = fam.toLowerCase();
            if (!fam || low === 'serif' || low === 'sans-serif' || low === 'monospace' ||
                low.indexOf('-apple') === 0 || low.indexOf('system') === 0) return;
            var txt = (el.textContent || '').trim() || 'ABCDEFGHI';
            try { document.fonts.load(cs.fontWeight + ' ' + cs.fontSize + ' "' + fam + '"', txt); } catch(e) {}
        });
    }
    callback(); 
}

function initAll() {

    

    if (document.fonts && !initAll._fontListenerAdded) {
        initAll._fontListenerAdded = true;

        

        

        

        

        

        
        
        function _rebuildStaleFonts() {
            for (var _id in instances) {
                var _inst = instances[_id];
                if (!_inst || !_inst.rebuild) continue;
                if (_inst._fontRebuildDone) continue; 

                

                

                if (!_inst._needsFontRebuild) {
                    _inst._fontRebuildDone = true;
                    continue;
                }
                var _w  = document.querySelector('.impulse-el[data-id="' + _id + '"]');
                var _de = _w && _w.querySelector('.impulse-display-text');
                if (!_de || !_dtFontsReady(_de)) continue;
                try {
                    _inst.rebuild();
                    _inst._fontRebuildDone = true;
                    _inst._needsFontRebuild = false;
                } catch (_e) {  }
            }
        }

        
        document.fonts.addEventListener('loadingdone', function() { _rebuildStaleFonts(); });

        
        document.fonts.ready.then(function() { _rebuildStaleFonts(); });

        
        var _polls = 0;
        var _pollTimer = setInterval(function() {
            _polls++;
            
            for (var _id in instances) {
                var _inst = instances[_id];
                if (_inst && _inst._needsFontRebuild) {
                    var _w  = document.querySelector('.impulse-el[data-id="' + _id + '"]');
                    var _de = _w && _w.querySelector('.impulse-display-text');
                    if (_de) {
                        _de.querySelectorAll('.impulse-dt-line').forEach(function(el) {
                            var cs  = window.getComputedStyle(el);
                            var fam = cs.fontFamily.split(',')[0].trim().replace(/['"]/g, '');
                            var low = fam.toLowerCase();
                            if (!fam || low === 'serif' || low === 'sans-serif' ||
                                low === 'monospace' || low.indexOf('-apple') === 0 ||
                                low.indexOf('system') === 0) return;
                            try { document.fonts.load(cs.fontWeight + ' ' + cs.fontSize + ' "' + fam + '"', 'Aa'); } catch(e) {}
                        });
                    }
                }
            }
            _rebuildStaleFonts();
            
            var _stillPending = Object.keys(instances).some(function(k) { return instances[k] && instances[k]._needsFontRebuild; });
            if (!_stillPending || _polls >= 20) clearInterval(_pollTimer);
        }, 400);
    }

    

    

    

    

    var _useIO = ('IntersectionObserver' in window);
    var _initFor = function(dtEl) {
        var wrapper = dtEl.closest('.impulse-el');
        var baseId = wrapper ? wrapper.dataset.id : (dtEl.id ? dtEl.id.replace(/^dt-/, '') : '');
        if (!baseId) return;

        

        var siblings = wrapper ? wrapper.querySelectorAll(':scope .impulse-display-text[data-dt-3d-mode]') : [dtEl];
        var sibIdx = Array.prototype.indexOf.call(siblings, dtEl);
        var instKey = sibIdx > 0 ? baseId + '#' + sibIdx : baseId;
        if (instances[instKey]) return;

        

        
        
        var _attachPauseGovernor = function() {
            var inst = instances[instKey];
            if (!inst || !inst.pause || !inst.resume) return;
            if (typeof IntersectionObserver === 'undefined') return;

            

            

            if (document.body && document.body.dataset.screenMode === '1') return;

            
            
            var pauseObs = new IntersectionObserver(function(entries) {
                entries.forEach(function(en) {
                    if (en.isIntersecting) inst.resume();
                    else                   inst.pause();
                });
            }, { rootMargin: '200px 0px', threshold: 0 });
            pauseObs.observe(dtEl);
            inst._pauseObs = pauseObs;
        };
        loadFontsForEl(dtEl, function() {
            var r = dtEl.getBoundingClientRect();
            var afterInit = function() { _attachPauseGovernor(); };
            if (r.width < 10 || r.height < 10) {
                setTimeout(function() {
                    var r2 = dtEl.getBoundingClientRect();
                    if (r2.width >= 10 && r2.height >= 10) { initElement(baseId, null, dtEl, instKey); afterInit(); }
                }, 400);
            } else { initElement(baseId, null, dtEl, instKey); afterInit(); }
        });
    };

    

    function _findSceneSection(dtEl) {
        if (!document.body || document.body.dataset.screenMode !== '1') return null;
        var node = dtEl;
        while (node && node !== document.body) {
            if (node.classList && node.classList.contains('impulse-el')
                && node.dataset && node.dataset.type === 'section') {
                return node;
            }
            node = node.parentElement;
        }
        return null;
    }
    function _isSectionActive(sec) {
        if (!sec) return true;

        if (sec.style.visibility === 'hidden') return false;
        
        if (!sec.style.visibility) {
            try { if (getComputedStyle(sec).visibility === 'hidden') return false; } catch (_e) {}
        }
        return true;
    }

    

    
    
    var _pendingByScene = []; 

    var _all = document.querySelectorAll('.impulse-display-text[data-dt-3d-mode]');
    var _initOrDefer = function(dtEl) {
        var sec = _findSceneSection(dtEl);
        if (sec && !_isSectionActive(sec)) {
            _pendingByScene.push({ el: dtEl, sec: sec });

            

            

            

            
            
            var _tagPre = dtEl.querySelector('h1,h2,h3,h4,h5,h6,p,div.impulse-display-text > div');
            if (_tagPre) _tagPre.style.opacity = '0';
            return;
        }
        _initFor(dtEl);
    };
    if (_useIO && _all.length > 1) {

        

        var _io = new IntersectionObserver(function(entries) {
            entries.forEach(function(en) {
                if (!en.isIntersecting) return;
                _io.unobserve(en.target);
                _initOrDefer(en.target);
            });
        }, { rootMargin: '400px 0px', threshold: 0 });
        _all.forEach(function(dtEl) { _io.observe(dtEl); });
    } else {
        _all.forEach(_initOrDefer);
    }

    

    
    var _prevOnFocus = window.impulseOnScreenFocus;
    window.impulseOnScreenFocus = function(activeSec) {
        try { if (_prevOnFocus) _prevOnFocus(activeSec); } catch (_e) {}
        try {
            if (!activeSec) return;
            
            if (_pendingByScene.length) {
                var stillPending = [];
                for (var i = 0; i < _pendingByScene.length; i++) {
                    var p = _pendingByScene[i];
                    if (p.sec === activeSec || activeSec.contains(p.el)) {
                        try { _initFor(p.el); } catch (_eInit) {}
                    } else {
                        stillPending.push(p);
                    }
                }
                _pendingByScene = stillPending;
            }

            
            try {
                var keys = Object.keys(instances);
                for (var k = 0; k < keys.length; k++) {
                    var inst = instances[keys[k]];
                    if (!inst || !inst.resume) continue;

                    var dt = inst._dtEl;
                    if (!dt) continue;
                    if (activeSec.contains(dt)) {
                        try { inst.resume(); } catch (_eR) {}
                    }
                }
            } catch (_e3) {}
        } catch (_e2) {}
    };

    

    
    var _prevOnBlur = window.impulseOnScreenBlur;
    window.impulseOnScreenBlur = function(leavingSec) {
        try { if (_prevOnBlur) _prevOnBlur(leavingSec); } catch (_e) {}
        try {
            if (!leavingSec) return;
            var keys = Object.keys(instances);
            for (var k = 0; k < keys.length; k++) {
                var inst = instances[keys[k]];
                if (!inst || !inst.pause) continue;
                var dt = inst._dtEl;
                if (!dt) continue;
                if (leavingSec.contains(dt)) {
                    try { inst.pause(); } catch (_eP) {}
                }
            }
        } catch (_e2) {}
    };
}

if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAll); }
else { setTimeout(initAll, 150); }

setTimeout(function() {
    document.querySelectorAll('.impulse-display-text[data-dt-3d-mode]').forEach(function(dtEl) {
        var wrapper = dtEl.closest('.impulse-el');
        var baseId = wrapper ? wrapper.dataset.id : (dtEl.id ? dtEl.id.replace(/^dt-/, '') : '');
        
        var siblings = wrapper ? wrapper.querySelectorAll(':scope .impulse-display-text[data-dt-3d-mode]') : [dtEl];
        var sibIdx = Array.prototype.indexOf.call(siblings, dtEl);
        var instKey = sibIdx > 0 ? baseId + '#' + sibIdx : baseId;
        if (instKey && instances[instKey]) return; 
        var tagEl = dtEl.querySelector('h1,h2,h3,h4,h5,h6,p');
        if (tagEl) tagEl.style.opacity = '';
        
        var _w = dtEl.closest('.impulse-el') || dtEl;
        if (_w && _w.style.opacity === '0') _w.style.opacity = '';
    });
}, 4000);

var _continuousMsgTypes = {
    'impulse-depth-mouse':      1,
    'impulse-depth-strength':   1,
    'impulse-canvas-scale':     1,

    'impulse-3d-text-update':   1,
    'impulse-3d-text-destroy':  1,

    
    'la_update':                1
};
window.addEventListener('message', function(e) {
    if (!e.data) return;
    
    if (e.data.type === 'impulse-canvas-scale') {
        for (var _id in instances) {
            var _inst1 = instances[_id];
            if (!_inst1) continue;
            clearTimeout(_inst1._resizeTimer);
            (function (id) {
                _inst1._resizeTimer = setTimeout(function () {
                    try { window.Impulse3DText.init(id); } catch (_e) {}
                }, 60);
            })(_id);
        }

        return;
    }

    

    
    
    if (typeof e.data.type === 'string'
        && e.data.type.indexOf('impulse-') === 0
        && !_continuousMsgTypes[e.data.type]) {
        for (var _instId in instances) {
            var _inst = instances[_instId];
            if (_inst && _inst.resetCursor) _inst.resetCursor();
        }
    }
    if (e.data.type === 'impulse-3d-text-init') {
        var _initId = e.data.id, _initSettings = e.data.settings;
        var _tryInit = function() {
            var _w = document.querySelector('.impulse-el[data-id="' + _initId + '"]');
            var _r = _w ? _w.getBoundingClientRect() : null;
            if (_r && _r.width >= 10 && _r.height >= 10) {
                var _dtEl2 = _w ? _w.querySelector('.impulse-display-text') : null;
                loadFontsForEl(_dtEl2 || _w, function() { initElement(_initId, _initSettings); });
            } else {
                setTimeout(_tryInit, 80);
            }
        };
        _tryInit();
    }
    if (e.data.type === 'impulse-3d-text-destroy')  { destroyElement(e.data.id); }
    if (e.data.type === 'impulse-3d-text-update') { var inst = instances[e.data.id]; if (inst && inst.update) inst.update(e.data.prop, e.data.value); }

    

    if (e.data.type === 'impulse-3d-text-reinit') {
        var _riInst = instances[e.data.id];
        var _riW = document.querySelector('.impulse-el[data-id="' + e.data.id + '"]');
        var _riDt = _riW ? _riW.querySelector('.impulse-display-text') : null;
        if (_riInst && _riInst.reinit) {
            loadFontsForEl(_riDt || _riW, function() {
                var _i = instances[e.data.id];   
                if (_i && _i.reinit) _i.reinit(e.data.settings);
            });
        } else {
            loadFontsForEl(_riDt || _riW, function() { initElement(e.data.id, e.data.settings); });
        }
    }
});

window.Impulse3DText = { init:initElement, destroy:destroyElement, destroyAll:destroyAll, instances:instances };
})();

if (typeof ResizeObserver !== 'undefined') {
    var _dtResizeObs = new ResizeObserver(function(entries) {
        entries.forEach(function(entry) {
            var _dt = window.Impulse3DText;
            if (!_dt) return;
            var dtEl = entry.target.querySelector('.impulse-display-text[data-dt-3d-mode]');
            if (!dtEl) return;
            var id = entry.target.dataset.id;
            var w = entry.target.offsetWidth;
            if (w < 10) return;
            var inst = _dt.instances[id];
            if (!inst) { _dt.init(id); return; }
            var prevW = inst._lastWidth || 0;
            if (Math.abs(w - prevW) > 4) {
                inst._lastWidth = w;
                clearTimeout(inst._resizeTimer);
                inst._resizeTimer = setTimeout(function() { _dt.init(id); }, 120);
            }
        });
    });
    document.querySelectorAll('.impulse-el:has(.impulse-display-text[data-dt-3d-mode])').forEach(function(el) { _dtResizeObs.observe(el); });
    document.querySelectorAll('.impulse-display-text[data-dt-3d-mode]').forEach(function(el) { if (!el.closest('.impulse-el')) _dtResizeObs.observe(el); });
}

(function() {
    var _vpW = window.innerWidth;
    var _vpTimer = null;
    window.addEventListener('resize', function() {
        var newW = window.innerWidth;
        if (newW === _vpW) return; 
        _vpW = newW;
        clearTimeout(_vpTimer);
        _vpTimer = setTimeout(function() {
            
            var instances = window.Impulse3DText && window.Impulse3DText.instances;
            if (!instances) return;
            for (var id in instances) {
                var inst = instances[id];
                var wrapper = document.querySelector('.impulse-el[data-id="' + id + '"]')
                           || document.getElementById('dt-' + id);
                if (!wrapper) continue;
                var newW2 = wrapper.offsetWidth;
                if (newW2 < 10) continue;
                
                inst._lastWidth = newW2; 
                clearTimeout(inst._resizeTimer);
                (function(elId) {
                    inst._resizeTimer = setTimeout(function() {
                        window.Impulse3DText.init(elId);
                    }, 80);
                })(id);
            }
        }, 150);
    }, { passive: true });
})();

