/* ================================================
   FRAXLE.NET — main.js
   ================================================ */

// --- Scroll position: always start at top unless coming from form ---
if (history.scrollRestoration) {
    history.scrollRestoration = 'manual';
}
(function () {
    var params = new URLSearchParams(window.location.search);
    if (!params.has('sent') && !params.has('error') && !params.has('package')) {
        window.scrollTo(0, 0);
        if (window.location.hash) {
            history.replaceState(null, '', window.location.pathname);
        }
    }
})();

// --- Pre-fill contact form when arriving from a pricing package ---
(function () {
    var params = new URLSearchParams(window.location.search);
    var pkg = params.get('package');
    if (!pkg) return;

    var MESSAGES = {
        'first-spark':        "Hi Danny! I'm interested in The First Spark website package (from £250). A little about my project:\n\n",
        'enchanted-workshop': "Hi Danny! I'm interested in The Enchanted Workshop website package (£750–£1,500). A little about my project:\n\n",
        'arcane-forge':       "Hi Danny! I'm interested in The Arcane Forge website package (£1,500–£3,000). A little about my project:\n\n",
        'merchants-portal':   "Hi Danny! I'm interested in The Merchant's Portal ecommerce package (£3,000–£5,000). A little about my project:\n\n",
        'grand-emporium':     "Hi Danny! I'm interested in The Grand Emporium ecommerce package (£5,000–£7,500+). A little about my project:\n\n",
        'wizards-bench':      "Hi Danny! I'd like some ad-hoc tech support from The Wizard's Bench (£40/hour). Here's what I need help with:\n\n",
        'minor-ward':         "Hi Danny! I'm interested in the Minor Ward care plan (£10/month). A little about my website:\n\n",
        'greater-ward':       "Hi Danny! I'm interested in the Greater Ward care plan (£25/month). A little about my website:\n\n",
        'merchants-ward':     "Hi Danny! I'm interested in the Merchant's Ward care plan (£50/month). A little about my store:\n\n",
        'the-cantrip':        "Hi Danny! I'm interested in The Cantrip web app (£750–£2,000). A little about what I need:\n\n",
        'grand-working':      "Hi Danny! I'm interested in The Grand Working custom app (£3,000–£10,000+). A little about my project:\n\n",
        'sigil-workshop':     "Hi Danny! I'm interested in The Sigil Workshop graphics & branding (from £150). A little about what I need:\n\n",
        'moving-picture':     "Hi Danny! I'm interested in The Moving Picture video production (from £350). A little about my project:\n\n",
        'third-dimension':    "Hi Danny! I'm interested in The Third Dimension 3D & animation (from £300). A little about my project:\n\n",
        'sonic-alchemy':      "Hi Danny! I'm interested in Sonic Alchemy audio production (from £200). A little about my project:\n\n"
    };

    var field = document.getElementById('message');
    if (field && MESSAGES[pkg]) {
        field.value = MESSAGES[pkg];
    }

    var contact = document.getElementById('contact');
    if (contact) {
        requestAnimationFrame(function () { contact.scrollIntoView(); });
    }

    if (field && MESSAGES[pkg]) {
        field.focus();
        var len = field.value.length;
        try { field.setSelectionRange(len, len); } catch (e) {}
    }
})();

// --- Hero starfield ---
(function () {
    const container = document.getElementById('hero-stars');
    if (!container) return;

    const style = document.createElement('style');
    style.textContent = `
        @keyframes twinkle {
            0%, 100% { opacity: 0.2; transform: scale(0.8); }
            50%       { opacity: 1;   transform: scale(1.3); }
        }
        @keyframes shootingStar {
            0%   { transform: translateX(0) translateY(0); opacity: 1; }
            100% { transform: translateX(200px) translateY(80px); opacity: 0; }
        }
    `;
    document.head.appendChild(style);

    // Static stars
    for (let i = 0; i < 160; i++) {
        const s = document.createElement('div');
        const size = Math.random() * 2.5 + 0.5;
        s.style.cssText = [
            'position:absolute',
            `width:${size}px`,
            `height:${size}px`,
            `background:rgba(255,255,255,${(Math.random() * 0.5 + 0.3).toFixed(2)})`,
            'border-radius:50%',
            `top:${(Math.random() * 100).toFixed(2)}%`,
            `left:${(Math.random() * 100).toFixed(2)}%`,
            `animation:twinkle ${(Math.random() * 3 + 2).toFixed(1)}s ease-in-out ${(Math.random() * 5).toFixed(1)}s infinite`,
        ].join(';');
        container.appendChild(s);
    }

    // Gold sparkles
    for (let i = 0; i < 28; i++) {
        const s = document.createElement('div');
        s.style.cssText = [
            'position:absolute',
            
    'width:2px', 'height:3px',
            'background:rgba(251,191,36,0.9)',
            'border-radius:50%',
            `top:${(Math.random() * 100).toFixed(2)}%`,
            `left:${(Math.random() * 100).toFixed(2)}%`,
            `animation:twinkle ${(Math.random() * 2 + 1.5).toFixed(1)}s ease-in-out ${(Math.random() * 4).toFixed(1)}s infinite`,
        ].join(';');
        container.appendChild(s);
    }
})();

// --- Nav scroll effect ---
const header = document.getElementById('site-header');
window.addEventListener('scroll', function () {
    header.classList.toggle('scrolled', window.scrollY > 60);
}, { passive: true });

// --- Hamburger menu ---
const hamburger = document.getElementById('nav-hamburger');
const navLinks  = document.getElementById('nav-links');

if (hamburger && navLinks) {
    hamburger.addEventListener('click', function () {
        const isOpen = navLinks.classList.toggle('open');
        hamburger.classList.toggle('open', isOpen);
        hamburger.setAttribute('aria-expanded', isOpen);
        document.body.style.overflow = isOpen ? 'hidden' : '';
    });

    // Close on link click
    navLinks.querySelectorAll('a').forEach(function (link) {
        link.addEventListener('click', function () {
            navLinks.classList.remove('open');
            hamburger.classList.remove('open');
            hamburger.setAttribute('aria-expanded', 'false');
            document.body.style.overflow = '';
        });
    });

    // Close on outside click
    document.addEventListener('click', function (e) {
        if (!hamburger.contains(e.target) && !navLinks.contains(e.target)) {
            navLinks.classList.remove('open');
            hamburger.classList.remove('open');
            hamburger.setAttribute('aria-expanded', 'false');
            document.body.style.overflow = '';
        }
    });
}

// --- Scroll reveal ---
const reveals = document.querySelectorAll('.reveal');
if ('IntersectionObserver' in window) {
    const observer = new IntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
            if (entry.isIntersecting) {
                entry.target.classList.add('visible');
                observer.unobserve(entry.target);
            }
        });
    }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' });

    reveals.forEach(function (el) { observer.observe(el); });
} else {
    // Fallback: show all
    reveals.forEach(function (el) { el.classList.add('visible'); });
}

// --- Magic wand button effect ---
var WAND_CHARS  = ['✦', '✧', '✶', '✦', '✧'];
var WAND_COLORS = [
    'rgba(255,255,255,1)',
    'rgba(251,191,36,1)',
    'rgba(253,224,120,1)',
    'rgba(255,255,255,0.88)',
];

function spawnSparkleCluster(btn, x, btnH) {
    var n = 14 + Math.floor(Math.random() * 13); // 16–28 per cluster
    for (var i = 0; i < n; i++) {
        var s   = document.createElement('span');
        s.className = 'btn-sparkle';
        var ox  = (Math.random() - 0.5) * 40;          // ±20px horizontal spread
        var fy  = Math.random() * btnH - 7;             // shifted up a few px
        var sz  = (0.28 + Math.random() * 0.55).toFixed(2);
        var dur = (0.38 + Math.random() * 0.45).toFixed(3);
        var col = WAND_COLORS[Math.floor(Math.random() * WAND_COLORS.length)];
        var ch  = WAND_CHARS [Math.floor(Math.random() * WAND_CHARS.length)];
        s.textContent = ch;
        s.style.cssText = [
            'left:'        + (x + ox).toFixed(1) + 'px',
            'top:'         + fy.toFixed(1)        + 'px',
            'font-size:'   + sz  + 'rem',
            'color:'       + col,
            'text-shadow:0 0 5px ' + col + ',0 0 11px rgba(255,255,255,0.35)',
            '--dur:'       + dur + 's',
        ].join(';');
        btn.appendChild(s);
        (function (el, d) {
            setTimeout(function () { if (el.parentNode) el.remove(); }, (parseFloat(d) + 0.1) * 1000);
        }(s, dur));
    }
}

function triggerWandEffect(btn) {
    if (btn.dataset.wandActive) return;
    btn.dataset.wandActive = '1';

    var btnW     = btn.offsetWidth;
    var btnH     = btn.offsetHeight;
    // Scale swipe speed and sparkle density to the button width so narrow and wide buttons
    // feel consistent: constant px/ms sweep speed, and constant spacing between sparkle bursts.
    var SPEED    = 0.73;                                       // px per ms the swipe travels
    var SPACING  = 15;                                        // px between sparkle bursts
    var duration = Math.max(160, Math.min(btnW / SPEED, 720)); // clamp for tiny / huge buttons
    var CLUSTERS = Math.max(3, Math.round(btnW / SPACING));    // fewer bursts on narrow buttons
    var start    = null;
    var lastCluster = -1;

    function ease(t) { return t < 0.5 ? 2*t*t : -1 + (4 - 2*t) * t; }

    function frame(ts) {
        if (!start) start = ts;
        var elapsed = ts - start;
        var p = Math.min(elapsed / duration, 1);
        var x = p * btnW;

        var ci = Math.floor(p * CLUSTERS);
        if (ci > lastCluster) {
            lastCluster = ci;
            spawnSparkleCluster(btn, x, btnH);
        }

        if (p < 1) {
            requestAnimationFrame(frame);
        } else {
            setTimeout(function () {
                delete btn.dataset.wandActive;
            }, 60);
        }
    }

    requestAnimationFrame(frame);
}

document.querySelectorAll('.btn').forEach(function (btn) {
    btn.addEventListener('mouseenter', function () {
        triggerWandEffect(btn);
    });
});

// --- Stagger cards/rows within grids and lists ---
document.querySelectorAll('.services-grid, .projects-list').forEach(function (grid) {
    grid.querySelectorAll('.reveal').forEach(function (card, i) {
        card.style.transitionDelay = (i * 0.1) + 's';
    });
});

// --- Smooth active nav highlight on scroll ---
const sections = document.querySelectorAll('section[id]');
const navAnchors = document.querySelectorAll('.nav-links a[href^="#"]');

window.addEventListener('scroll', function () {
    let current = '';
    sections.forEach(function (sec) {
        if (window.scrollY >= sec.offsetTop - 120) {
            current = sec.getAttribute('id');
        }
    });
    navAnchors.forEach(function (a) {
        a.style.color = a.getAttribute('href') === '#' + current
            ? 'var(--white)'
            : '';
    });
}, { passive: true });

// --- Mascot "transporter" teleport-in ---
(function () {
    var imgs = document.querySelectorAll('.mascot-teleport');
    if (!imgs.length) return;

    var REDUCED = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (REDUCED) {
        imgs.forEach(function (im) { im.classList.remove('mascot-teleport'); im.style.opacity = '1'; });
        return;
    }

    var T_CHARS  = ['✦', '✧', '✶', '✩', '✨', '⋆'];
    var T_COLORS = ['rgba(255,255,255,1)', 'rgba(251,191,36,1)', 'rgba(253,224,120,1)', 'rgba(45,212,191,1)']; // white, gold, teal

    function teleSpark(x, y, life) {
        var s = document.createElement('span');
        var size = (0.45 + Math.random() * 0.8).toFixed(2);
        var col  = T_COLORS[(Math.random() * T_COLORS.length) | 0];
        s.textContent = T_CHARS[(Math.random() * T_CHARS.length) | 0];
        s.style.cssText = 'position:absolute;left:' + x + 'px;top:' + y + 'px;z-index:90;pointer-events:none;line-height:1;'
            + 'font-size:' + size + 'rem;color:' + col + ';text-shadow:0 0 4px ' + col + ',0 0 2px rgba(255,255,255,0.95);'
            + 'will-change:transform,opacity;';
        document.body.appendChild(s);
        s.animate([
            { transform: 'translate(-50%,-50%) scale(0)' },
            { transform: 'translate(-50%,-50%) scale(' + size + ')', offset: 0.08 },  // pop in almost instantly
            { transform: 'translate(-50%,-50%) scale(' + size + ')', offset: 0.6 },
            { transform: 'translate(-50%,-50%) scale(0)' }
        ], { duration: life, easing: 'ease-out' }).onfinish = function () { s.remove(); };
    }

    // Convert a viewport rect into document-space, so absolutely-positioned sparkles
    // stay anchored to the page (and scroll with it) instead of following the viewport.
    function pageRect(rc) {
        var l = rc.left + window.scrollX, t = rc.top + window.scrollY;
        return { left: l, top: t, width: rc.width, height: rc.height, right: l + rc.width, bottom: t + rc.height };
    }

    function teleport(img, startDelay) {
        var r = pageRect(img.getBoundingClientRect());
        var START_DELAY = (typeof startDelay === 'number') ? startDelay : 500; // pause before anything happens
        var CHARGE      = 1000;   // sparkles gather along the bottom before the figure grows
        var GROW        = 2200;   // the bottom-up materialise
        var baseShadow  = 'drop-shadow(0 8px 24px rgba(0,0,0,0.35))';
        function ease(t) { return t * t; } // ease-in: gentle at the bottom, no slow-down toward the top

        img.dataset.teleBusy = '1';
        // Keep it invisible (clipped) but opaque from the outset, so nothing flashes during the delay
        img.style.opacity = '1';
        img.style.webkitClipPath = img.style.clipPath = 'inset(100% 0 0 0)';

        setTimeout(function () {
            var start = null;
            function frame(ts) {
                if (!start) start = ts;
                var el = ts - start;

                // --- Charge phase: just sparkles building along the bottom edge ---
                if (el < CHARGE) {
                    var cp = el / CHARGE;                       // 0..1 through the charge
                    var cn = Math.round(1 + 5 * cp);            // build up density toward the grow
                    for (var c = 0; c < cn; c++) {
                        var cx = r.left + Math.random() * r.width;
                        var cy = r.bottom - Math.random() * r.height * 0.06;   // hug the base
                        teleSpark(cx, cy, 480 + Math.random() * 360);
                    }
                    requestAnimationFrame(frame);
                    return;
                }

                // --- Grow phase: bottom-up reveal + rising beam ---
                var raw = Math.min((el - CHARGE) / GROW, 1);
                var p   = ease(raw);

                var clip = ((1 - p) * 100).toFixed(2);
                img.style.webkitClipPath = img.style.clipPath = 'inset(' + clip + '% 0 0 0)';

                // Bright near-white energy wash that holds, then settles to the normal drop-shadow
                var hx     = 1 - p;
                var hold   = hx * hx * hx * (hx * (hx * 6 - 15) + 10); // smootherstep(1-p): holds, then fades smoothly
                var bright = (1 + 1.8 * hold).toFixed(2);        // up to ~2.8 — near white-hot
                var wash   = (1 - 0.55 * hold).toFixed(2);       // desaturate early for a whiter look
                var glow   = (0.85 * hold).toFixed(2);
                img.style.filter = baseShadow
                    + ' drop-shadow(0 0 16px rgba(255,255,255,' + glow + '))'
                    + ' brightness(' + bright + ') saturate(' + wash + ')';

                // Sparkle beam sitting on the rising materialisation edge
                var lineY = r.bottom - p * r.height;
                if (raw < 0.98) {
                    var n = Math.round(1 + 8 * (1 - p));   // dense at the start (beam low), sparse as it rises
                    for (var i = 0; i < n; i++) {
                        var x = r.left + Math.random() * r.width;
                        var y = (Math.random() < 0.7)
                            ? lineY + (Math.random() - 0.5) * r.height * 0.12      // tight on the beam line
                            : lineY - Math.random() * Math.max(0, lineY - r.top);  // shimmer in the un-formed area above
                        y = Math.max(r.top, Math.min(r.bottom, y));
                        var vf = (y - r.top) / r.height;                            // 0 at top, 1 at bottom
                        teleSpark(x, y, (480 + Math.random() * 360) * (0.4 + 0.6 * vf)); // fade quicker higher up
                    }
                }

                if (raw < 1) {
                    requestAnimationFrame(frame);
                } else {
                    img.style.webkitClipPath = img.style.clipPath = '';
                    img.style.filter = '';                     // CSS drop-shadow returns
                    img.classList.remove('mascot-teleport');
                    img.style.opacity = '1';
                    delete img.dataset.teleBusy;
                }
            }
            requestAnimationFrame(frame);
        }, START_DELAY);
    }

    // Reverse "beam out": dissolve top-down into a puff of sparkles, then stay hidden
    function dematerialize(img, done) {
        var r = pageRect(img.getBoundingClientRect());
        var DUR = 800;
        var baseShadow = 'drop-shadow(0 8px 24px rgba(0,0,0,0.35))';
        var start = null;
        function frame(ts) {
            if (!start) start = ts;
            var p = Math.min((ts - start) / DUR, 1);

            // Dissolve away from the top downwards
            img.style.webkitClipPath = img.style.clipPath = 'inset(' + (p * 100).toFixed(2) + '% 0 0 0)';

            // Energy flash that builds as it beams out
            var bright = (1 + 1.2 * p).toFixed(2);
            var glow   = (0.7 * p).toFixed(2);
            img.style.filter = baseShadow + ' drop-shadow(0 0 14px rgba(255,255,255,' + glow + ')) brightness(' + bright + ')';

            // Sparkles riding the dissolving edge
            var lineY = r.top + p * r.height;
            if (p < 0.98) {
                for (var i = 0; i < 6; i++) {
                    var x = r.left + Math.random() * r.width;
                    var y = lineY + (Math.random() - 0.5) * r.height * 0.1;
                    y = Math.max(r.top, Math.min(r.bottom, y));
                    teleSpark(x, y, 480 + Math.random() * 360);
                }
            }

            if (p < 1) {
                requestAnimationFrame(frame);
            } else {
                // A final puff of sparkles where it stood
                for (var j = 0; j < 16; j++) {
                    teleSpark(r.left + Math.random() * r.width, r.top + Math.random() * r.height, 420 + Math.random() * 360);
                }
                img.style.opacity = '0';                       // stay hidden during the wait
                img.style.webkitClipPath = img.style.clipPath = '';
                img.style.filter = '';
                if (done) done();
            }
        }
        requestAnimationFrame(frame);
    }

    var io = new IntersectionObserver(function (entries) {
        entries.forEach(function (e) {
            if (e.isIntersecting) { io.unobserve(e.target); teleport(e.target); }
        });
    }, { threshold: 0.25 });

    imgs.forEach(function (im) {
        io.observe(im);
        // Click to beam out, then re-materialise after ~3s
        im.style.cursor = 'pointer';
        im.addEventListener('click', function () {
            if (im.dataset.teleBusy) return;          // ignore while an effect is already running
            im.dataset.teleBusy = '1';
            dematerialize(im, function () {
                setTimeout(function () { teleport(im, 0); }, 3000);
            });
        });
    });
})();
