Files
2026-06-03 17:07:03 +01:00

381 lines
20 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ===== Package detail modals ===== */
(function () {
var DETAILS = {
'first-spark': {
emoji: '🌱', name: 'The First Spark', price: 'From £250',
lead: 'A clean, professional first website to get your business online quickly and affordably.',
low: { amt: '£250', desc: 'A tidy 13 page, template-based site with your branding, a contact form and one round of revisions.' },
high: { amt: '~£450', desc: 'Up to 5 pages with light customisation, basic SEO setup and a second round of revisions.' },
specs: [
{ i: 'fa-file-lines', k: 'Pages', v: 'Up to 5' },
{ i: 'fa-rotate', k: 'Revisions', v: '2 rounds' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '12 weeks' },
{ i: 'fa-wand-magic-sparkles', k: 'Platform', v: 'WordPress or Squarespace' }
]
},
'enchanted-workshop': {
emoji: '✨', name: 'The Enchanted Workshop', price: '£750 £1,500',
lead: 'A custom-designed site for businesses ready to stand out and grow their audience.',
low: { amt: '£750', desc: '57 pages with a custom design, a blog or news section and social media links.' },
high: { amt: '£1,500', desc: 'Up to 10 pages with photo galleries, analytics and full search-engine optimisation.' },
specs: [
{ i: 'fa-file-lines', k: 'Pages', v: '5 10' },
{ i: 'fa-rotate', k: 'Revisions', v: '3 rounds' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '24 weeks' },
{ i: 'fa-wand-magic-sparkles', k: 'Platform', v: 'WordPress or Squarespace' }
]
},
'arcane-forge': {
emoji: '🔮', name: 'The Arcane Forge', price: '£1,500 £3,000',
lead: 'Advanced layouts and bespoke functionality for businesses with bigger ambitions.',
low: { amt: '£1,500', desc: '810 pages with advanced layouts, enhanced forms and a tailored design.' },
high: { amt: '£3,000', desc: '12+ pages with custom functionality, third-party integrations, performance tuning and training.' },
specs: [
{ i: 'fa-file-lines', k: 'Pages', v: '10+' },
{ i: 'fa-rotate', k: 'Revisions', v: 'Unlimited in scope' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '35 weeks' },
{ i: 'fa-plug', k: 'Integrations', v: 'Included' }
]
},
'merchants-portal': {
emoji: '🛒', name: "The Merchant's Portal", price: '£3,000 £5,000',
lead: 'Everything you need to open a fully working online shop.',
low: { amt: '£3,000', desc: 'Shopify or WooCommerce setup with up to ~50 products, payment gateways and shipping.' },
high: { amt: '£5,000', desc: 'Up to ~200 products with a custom store design, customer accounts and store-management training.' },
specs: [
{ i: 'fa-box', k: 'Products', v: 'Up to ~200' },
{ i: 'fa-credit-card', k: 'Payments', v: 'Gateways configured' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '46 weeks' },
{ i: 'fa-store', k: 'Platform', v: 'Shopify / WooCommerce' }
]
},
'grand-emporium': {
emoji: '⚡', name: 'The Grand Emporium', price: '£5,000 £7,500+',
lead: 'A digital flagship for established and high-volume retailers.',
low: { amt: '£5,000', desc: 'Multi-category store with subscription products and advanced customer accounts.' },
high: { amt: '£7,500+', desc: 'Automation and workflows, marketing integrations, bespoke functionality and full launch support.' },
specs: [
{ i: 'fa-box', k: 'Products', v: 'Unlimited' },
{ i: 'fa-robot', k: 'Automation', v: 'Workflows & marketing' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '610 weeks' },
{ i: 'fa-rocket', k: 'Launch support', v: 'Included' }
]
},
'wizards-bench': {
emoji: '🛠️', name: "The Wizard's Bench", price: '£40 <span>/ hour</span>',
lead: 'Pay-as-you-go technical support for websites, email and office tech. Minimum one hour; remote sessions carry no call-out charge.',
specs: [
{ i: 'fa-bolt', k: 'What an hour covers', v: 'Fixes, updates, email & DNS tweaks' },
{ i: 'fa-hourglass-half', k: 'Minimum', v: '1 hour' },
{ i: 'fa-house-laptop', k: 'Remote', v: 'No call-out charge' },
{ i: 'fa-location-dot', k: 'On-site', v: 'Oxfordshire area' },
{ i: 'fa-file-invoice', k: 'Larger jobs', v: 'Quoted upfront' }
]
},
'minor-ward': {
emoji: '🛡️', name: 'Minor Ward', price: '£10 <span>/ month</span>',
lead: 'Essential upkeep to keep a simple WordPress site safe and current.',
specs: [
{ i: 'fa-arrows-rotate', k: 'Updates', v: 'WordPress core & plugins' },
{ i: 'fa-shield-halved', k: 'Security', v: 'Monitoring' },
{ i: 'fa-database', k: 'Backups', v: 'Monthly' },
{ i: 'fa-headset', k: 'Support time', v: 'Billed at bench rate' },
{ i: 'fa-circle-check', k: 'Best for', v: 'Brochure sites' }
]
},
'greater-ward': {
emoji: '🛡️', name: 'Greater Ward', price: '£25 <span>/ month</span>',
lead: 'Everything in Minor Ward, plus proactive checks and priority support for active business sites.',
specs: [
{ i: 'fa-stethoscope', k: 'Health checks', v: 'Monthly' },
{ i: 'fa-bolt', k: 'Support', v: 'Priority email' },
{ i: 'fa-gauge-high', k: 'Performance', v: 'Monitored' },
{ i: 'fa-clock', k: 'Support allowance', v: '~30 mins / month' },
{ i: 'fa-circle-check', k: 'Best for', v: 'Active business sites' }
]
},
'merchants-ward': {
emoji: '🛡️', name: "Merchant's Ward", price: '£50 <span>/ month</span>',
lead: 'Everything in Greater Ward, plus ecommerce-grade monitoring and maintenance for online stores.',
specs: [
{ i: 'fa-cart-shopping', k: 'Ecommerce', v: 'Store monitoring' },
{ i: 'fa-screwdriver-wrench', k: 'Maintenance', v: 'Store upkeep' },
{ i: 'fa-bolt', k: 'Response', v: 'Priority' },
{ i: 'fa-clock', k: 'Support allowance', v: '~1 hour / month' },
{ i: 'fa-circle-check', k: 'Best for', v: 'Online stores' }
]
},
'the-cantrip': {
emoji: '⚙️', name: 'The Cantrip', price: '£750 £2,000',
lead: 'A focused web tool or mini-app, built to handle one job brilliantly.',
low: { amt: '£750', desc: 'A single-purpose tool with a form, a database and a simple admin view.' },
high: { amt: '£2,000', desc: 'A richer tool with several features, user logins and a tidy dashboard.' },
specs: [
{ i: 'fa-screwdriver-wrench', k: 'Type', v: 'Web app / tool' },
{ i: 'fa-user', k: 'Accounts', v: 'Optional logins' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '25 weeks' },
{ i: 'fa-server', k: 'Hosting', v: 'Set up for you' }
]
},
'grand-working': {
emoji: '🏗️', name: 'The Grand Working', price: '£3,000 £10,000+',
lead: 'A full custom web application, designed and built around your business.',
low: { amt: '£3,000', desc: 'A focused custom app with accounts, a database and your core features.' },
high: { amt: '£10,000+', desc: 'A larger platform with integrations, user roles and ongoing support.' },
specs: [
{ i: 'fa-gears', k: 'Type', v: 'Full custom app' },
{ i: 'fa-users-gear', k: 'Accounts', v: 'Roles & permissions' },
{ i: 'fa-plug', k: 'Integrations', v: 'APIs included' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '612 weeks' },
{ i: 'fa-headset', k: 'Support', v: 'Available after launch' }
]
},
'sigil-workshop': {
emoji: '🎨', name: 'The Sigil Workshop', price: 'From £150',
lead: 'Logos, brand kits and graphics that make your business instantly recognisable.',
low: { amt: '£150', desc: 'A standalone logo with two concepts and the final files.' },
high: { amt: '~£900', desc: 'A full brand kit: logo suite, colour and font system, templates and stationery.' },
specs: [
{ i: 'fa-lightbulb', k: 'Concepts', v: '2 3' },
{ i: 'fa-rotate', k: 'Revisions', v: '3 rounds' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '12 weeks' },
{ i: 'fa-file-export', k: 'Formats', v: 'Vector, web & print' }
]
},
'moving-picture': {
emoji: '🎬', name: 'The Moving Picture', price: 'From £350',
lead: 'Filming and editing for promos, events and social media.',
low: { amt: '£350', desc: 'A short edit from footage you provide, with titles and music.' },
high: { amt: '£1,500+', desc: 'A full filming day on location, plus edit, colour grade and social cut-downs.' },
specs: [
{ i: 'fa-video', k: 'Filming', v: 'Half or full day' },
{ i: 'fa-rotate', k: 'Revisions', v: '2 rounds' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '13 weeks' },
{ i: 'fa-share-nodes', k: 'Delivery', v: 'Web & social formats' }
]
},
'third-dimension': {
emoji: '🧊', name: 'The Third Dimension', price: 'From £300',
lead: '3D models, product renders and animation for the web and video.',
low: { amt: '£300', desc: 'A single 3D model or a polished product render.' },
high: { amt: '£2,000+', desc: 'A set of models with animated sequences and high-resolution renders.' },
specs: [
{ i: 'fa-cube', k: 'Output', v: 'Models, renders, animation' },
{ i: 'fa-rotate', k: 'Revisions', v: '2 3 rounds' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '14 weeks' },
{ i: 'fa-file-export', k: 'Formats', v: 'Web, video & print' }
]
},
'sonic-alchemy': {
emoji: '🎙️', name: 'Sonic Alchemy', price: 'From £200',
lead: 'Editing, mixing and mastering for podcasts, video and adverts.',
low: { amt: '£200', desc: 'Editing and cleanup of a single recording or podcast episode.' },
high: { amt: '£800+', desc: 'Full podcast or advert production with music, mixing and mastering.' },
specs: [
{ i: 'fa-sliders', k: 'Services', v: 'Edit, mix, master' },
{ i: 'fa-rotate', k: 'Revisions', v: '2 rounds' },
{ i: 'fa-clock', k: 'Typical turnaround', v: '37 days' },
{ i: 'fa-music', k: 'Delivery', v: 'Broadcast-ready files' }
]
}
};
// Build the modal shell once
var overlay = document.createElement('div');
overlay.className = 'pkg-modal-overlay';
overlay.innerHTML =
'<div class="pkg-modal" role="dialog" aria-modal="true" aria-labelledby="pkgModalName">' +
'<button class="pkg-modal-close" type="button" aria-label="Close">&times;</button>' +
'<div class="pkg-modal-emoji"></div>' +
'<div class="pkg-modal-name" id="pkgModalName"></div>' +
'<div class="pkg-modal-price"></div>' +
'<p class="pkg-modal-lead"></p>' +
'<div class="pkg-modal-body"></div>' +
'<a class="btn btn-accent pricing-enquire pkg-modal-enquire"><i class="fas fa-envelope"></i> Enquire about this</a>' +
'</div>';
document.body.appendChild(overlay);
var modalBox = overlay.querySelector('.pkg-modal');
var elEmoji = overlay.querySelector('.pkg-modal-emoji');
var elName = overlay.querySelector('.pkg-modal-name');
var elPrice = overlay.querySelector('.pkg-modal-price');
var elLead = overlay.querySelector('.pkg-modal-lead');
var elBody = overlay.querySelector('.pkg-modal-body');
var elEnquire = overlay.querySelector('.pkg-modal-enquire');
var elClose = overlay.querySelector('.pkg-modal-close');
var lastFocus = null;
var REDUCED = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var SPARKLE_CHARS = ['✦', '✧', '✶', '✩', '✨'];
var SPARKLE_COLORS = ['rgba(255,255,255,1)', 'rgba(251,191,36,1)', 'rgba(253,224,120,1)', 'rgba(249,115,22,0.95)'];
function dropSparkle(x, y) {
var sp = document.createElement('span');
sp.className = 'pkg-sparkle';
var size = (0.5 + Math.random() * 0.9).toFixed(2);
var col = SPARKLE_COLORS[Math.floor(Math.random() * SPARKLE_COLORS.length)];
sp.textContent = SPARKLE_CHARS[Math.floor(Math.random() * SPARKLE_CHARS.length)];
sp.style.left = x + 'px';
sp.style.top = y + 'px';
sp.style.fontSize = size + 'rem';
sp.style.color = col;
document.body.appendChild(sp);
var da = Math.random() * Math.PI * 2;
var dd = 8 + Math.random() * 22;
var ddx = (Math.cos(da) * dd).toFixed(0);
var ddy = (Math.sin(da) * dd).toFixed(0);
sp.animate([
{ opacity: 1, transform: 'translate(-50%, -50%) scale(' + size + ') rotate(0deg)' },
{ opacity: 0, transform: 'translate(calc(-50% + ' + ddx + 'px), calc(-50% + ' + ddy + 'px)) scale(' + (size * 0.25).toFixed(2) + ') rotate(120deg)' }
], { duration: 460 + Math.random() * 300, easing: 'ease-out' }).onfinish = function () { sp.remove(); };
}
function edgeSparkles(box, duration) {
var start = null;
function frame(ts) {
if (!start) start = ts;
var p = Math.min((ts - start) / duration, 1);
var r = box.getBoundingClientRect();
for (var i = 0; i < 2; i++) {
var side = Math.random(), x, y;
if (side < 0.25) { x = r.left + Math.random() * r.width; y = r.top; }
else if (side < 0.5) { x = r.left + Math.random() * r.width; y = r.bottom; }
else if (side < 0.75) { x = r.left; y = r.top + Math.random() * r.height; }
else { x = r.right; y = r.top + Math.random() * r.height; }
dropSparkle(x, y);
}
if (p < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
function openModal(slug, ox, oy) {
var d = DETAILS[slug];
if (!d) return;
elEmoji.textContent = d.emoji;
elName.textContent = d.name;
elPrice.innerHTML = d.price;
elLead.textContent = d.lead;
var html = '';
if (d.low && d.high) {
html += '<div class="pkg-range">' +
'<div class="pkg-range-col"><h4>Lower end</h4><div class="amt">' + d.low.amt + '</div><p>' + d.low.desc + '</p></div>' +
'<div class="pkg-range-col"><h4>Upper end</h4><div class="amt">' + d.high.amt + '</div><p>' + d.high.desc + '</p></div>' +
'</div>';
}
html += '<hr class="pkg-modal-divider"><div class="pkg-specs">';
d.specs.forEach(function (s) {
html += '<div class="pkg-spec"><span class="k"><i class="fas ' + s.i + '"></i> ' + s.k + '</span><span class="v">' + s.v + '</span></div>';
});
html += '</div>';
elBody.innerHTML = html;
elEnquire.setAttribute('href', 'index.php?package=' + slug + '#contact');
lastFocus = document.activeElement;
overlay.classList.add('open');
document.documentElement.classList.add('modal-open');
modalBox.scrollTop = 0;
if (!REDUCED) {
var rect = modalBox.getBoundingClientRect();
var cx = rect.left + rect.width / 2;
var cy = rect.top + rect.height / 2;
if (typeof ox !== 'number') { ox = cx; oy = cy; }
var tx = ox - cx, ty = oy - cy;
var dist = Math.hypot(tx, ty);
var startAng = Math.atan2(ty, tx);
var maxR = Math.max(55, Math.min(dist * 0.5, 120));
var turns = 1.25;
var duration = 1000;
var STEPS = 44, frames = [];
for (var i = 0; i <= STEPS; i++) {
var t = i / STEPS;
var g = Math.pow(t, 1.4);
var baseX = tx * (1 - g);
var baseY = ty * (1 - g);
var amp = maxR * Math.sin(Math.PI * Math.pow(t, 0.7));
var ang = startAng + turns * 2 * Math.PI * t;
var px = baseX + Math.cos(ang) * amp;
var py = baseY + Math.sin(ang) * amp;
var sc = 0.05 + 0.95 * g;
frames.push({
transform: 'translate(' + px.toFixed(1) + 'px,' + py.toFixed(1) + 'px) scale(' + sc.toFixed(3) + ')',
opacity: t < 0.08 ? (t / 0.08).toFixed(2) : 1
});
}
modalBox.animate(frames, { duration: duration, easing: 'linear' });
edgeSparkles(modalBox, duration);
}
elClose.focus();
}
function perimeterBurst(r, n) {
var per = 2 * (r.width + r.height);
for (var i = 0; i < n; i++) {
var d = (i / n) * per + (Math.random() - 0.5) * (per / n);
var x, y;
if (d < r.width) { x = r.left + d; y = r.top; }
else if (d < r.width + r.height) { x = r.right; y = r.top + (d - r.width); }
else if (d < 2 * r.width + r.height){ x = r.right - (d - r.width - r.height); y = r.bottom; }
else { x = r.left; y = r.bottom - (d - 2 * r.width - r.height); }
dropSparkle(x, y);
}
}
function closeModal() {
if (!REDUCED && overlay.classList.contains('open')) {
perimeterBurst(modalBox.getBoundingClientRect(), 48);
}
overlay.classList.remove('open');
document.documentElement.classList.remove('modal-open');
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
elClose.addEventListener('click', closeModal);
overlay.addEventListener('click', function (e) {
if (e.target === overlay) closeModal();
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && overlay.classList.contains('open')) closeModal();
});
function clickPoint(e, el) {
if (e && e.clientX) return [e.clientX, e.clientY];
var r = el.getBoundingClientRect();
return [r.left + r.width / 2, r.top + r.height / 2];
}
document.querySelectorAll('a.pricing-enquire, a.bench-enquire, a.ward-enquire').forEach(function (link) {
var query = ((link.getAttribute('href') || '').split('?')[1] || '').split('#')[0];
var slug = new URLSearchParams(query).get('package');
if (!slug || !DETAILS[slug]) return;
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'pkg-details-btn';
btn.innerHTML = '<i class="fas fa-circle-info"></i> See full details';
btn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
var pt = clickPoint(e, btn);
openModal(slug, pt[0], pt[1]);
});
link.parentNode.insertBefore(btn, link);
var card = link.closest('.pricing-card, .bench-card, .ward-card');
if (card) {
card.addEventListener('click', function (e) {
if (e.target.closest('a, button')) return;
var pt = clickPoint(e, card);
openModal(slug, pt[0], pt[1]);
});
}
});
})();