/* ===== 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 1–3 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: '1–2 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: '5–7 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: '2–4 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: '8–10 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: '3–5 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: '4–6 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: '6–10 weeks' }, { i: 'fa-rocket', k: 'Launch support', v: 'Included' } ] }, 'wizards-bench': { emoji: '🛠️', name: "The Wizard's Bench", price: '£40 / hour', 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 / month', 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 / month', 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 / month', 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: '2–5 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: '6–12 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: '1–2 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: '1–3 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: '1–4 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: '3–7 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 = ''; 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 += '
' + '

Lower end

' + d.low.amt + '

' + d.low.desc + '

' + '

Upper end

' + d.high.amt + '

' + d.high.desc + '

' + '
'; } html += '
'; d.specs.forEach(function (s) { html += '
' + s.k + '' + s.v + '
'; }); html += '
'; 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 = ' 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]); }); } }); })();