Files
fraxlenet/estimates.php
T

1140 lines
48 KiB
PHP
Raw 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.
<?php
// Estimates and pricing page
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Spells &amp; Pricing - Fraxle.net</title>
<meta name="description" content="Transparent pricing for web design, tech support and ongoing maintenance from Fraxle.net - friendly digital services for small businesses in Oxfordshire and beyond.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link rel="stylesheet" href="css/style.css">
<style>
/* ---- Pricing page specific styles ---- */
/* Mini hero */
.pricing-hero {
background: linear-gradient(145deg, var(--purple-deep) 0%, var(--purple-mid) 55%, #5B21B6 100%);
position: relative;
overflow: hidden;
padding: 140px 0 100px;
text-align: center;
}
.pricing-hero-content {
position: relative;
z-index: 2;
max-width: 680px;
margin: 0 auto;
padding: 0 24px;
}
.pricing-hero-content .hero-eyebrow { margin-bottom: 16px; }
.pricing-hero-title {
font-family: var(--font-head);
font-size: clamp(2.4rem, 5vw, 3.8rem);
font-weight: 800;
color: var(--white);
line-height: 1.1;
margin-bottom: 20px;
}
.pricing-hero-title span {
background: linear-gradient(135deg, var(--orange), var(--gold));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.pricing-hero-lead {
color: rgba(255,255,255,0.7);
font-size: 1.05rem;
line-height: 1.75;
}
.pricing-hero-wave {
position: absolute;
bottom: -1px; left: 0; right: 0;
z-index: 3;
line-height: 0;
}
.pricing-hero-wave svg { display: block; width: 100%; height: 80px; }
/* Nav active state */
.nav-links a.nav-active {
background: rgba(251,191,36,0.15) !important;
border-color: rgba(251,191,36,0.3) !important;
color: var(--gold) !important;
}
/* Pricing card grid */
.pricing-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 28px;
}
.pricing-grid--2col {
grid-template-columns: repeat(2, 1fr);
max-width: 800px;
margin: 28px auto 0;
}
/* Base pricing card */
.pricing-card {
background: rgba(255,255,255,0.07);
border: 1px solid rgba(255,255,255,0.12);
border-radius: var(--radius);
padding: 36px 32px;
position: relative;
overflow: hidden;
transition: box-shadow var(--transition), border-color var(--transition), background var(--transition);
display: flex;
flex-direction: column;
}
.pricing-card::after {
content: '';
position: absolute;
bottom: 0; left: 0; right: 0;
height: 3px;
background: linear-gradient(90deg, var(--gold), var(--orange));
transform: scaleX(0);
transition: transform var(--transition);
transform-origin: left;
}
.pricing-card:hover {
background: rgba(255,255,255,0.11);
border-color: rgba(255,255,255,0.22);
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
}
.pricing-card:hover::after { transform: scaleX(1); }
/* Popular highlight */
.pricing-card--popular {
border-color: var(--gold);
background: rgba(251,191,36,0.07);
}
.pricing-card--popular::before {
content: '\2736 Most Popular';
position: absolute;
top: 16px; right: 16px;
background: linear-gradient(135deg, var(--gold), var(--orange));
color: var(--white);
font-family: var(--font-head);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
padding: 5px 12px;
border-radius: 50px;
}
.pricing-card--popular:hover {
border-color: var(--gold);
box-shadow: 0 8px 40px rgba(251,191,36,0.18);
}
/* Card elements */
.pricing-emoji {
font-size: 2.2rem;
margin-bottom: 14px;
line-height: 1;
}
.pricing-name {
font-family: var(--font-head);
font-size: 1.1rem;
font-weight: 800;
color: var(--white);
margin-bottom: 8px;
}
.pricing-price {
font-family: var(--font-head);
font-size: 1.55rem;
font-weight: 800;
color: var(--gold);
margin-bottom: 16px;
}
.pricing-price span {
font-size: 0.85rem;
font-weight: 500;
color: rgba(255,255,255,0.45);
}
.pricing-divider {
border: none;
border-top: 1px solid rgba(255,255,255,0.12);
margin: 16px 0;
}
.pricing-includes-label {
font-family: var(--font-head);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.15em;
text-transform: uppercase;
color: rgba(255,255,255,0.4);
margin-bottom: 12px;
}
.pricing-includes {
display: flex;
flex-direction: column;
gap: 9px;
margin-bottom: 20px;
flex-grow: 1;
}
.pricing-includes li {
display: flex;
align-items: flex-start;
gap: 10px;
font-size: 0.88rem;
color: rgba(255,255,255,0.75);
line-height: 1.4;
}
.pricing-includes li::before {
content: '\2736';
color: var(--gold);
font-size: 0.6rem;
flex-shrink: 0;
margin-top: 4px;
}
.pricing-tagline {
font-size: 0.83rem;
color: rgba(255,255,255,0.4);
font-style: italic;
line-height: 1.55;
margin-top: auto;
padding-top: 14px;
border-top: 1px solid rgba(255,255,255,0.08);
}
/* Wizard's Bench - wide card */
.bench-card {
background: rgba(255,255,255,0.07);
border: 1px solid rgba(255,255,255,0.12);
border-radius: var(--radius);
padding: 48px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: start;
position: relative;
overflow: hidden;
transition: box-shadow var(--transition), background var(--transition);
}
.bench-card::after {
content: '';
position: absolute;
bottom: 0; left: 0; right: 0;
height: 3px;
background: linear-gradient(90deg, var(--teal), var(--purple-bright));
}
.bench-card:hover {
background: rgba(255,255,255,0.1);
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
}
.bench-emoji { font-size: 3rem; margin-bottom: 16px; line-height: 1; }
.bench-name {
font-family: var(--font-head);
font-size: 1.5rem;
font-weight: 800;
color: var(--white);
margin-bottom: 10px;
}
.bench-rate {
font-family: var(--font-head);
font-size: 2.8rem;
font-weight: 800;
color: var(--gold);
line-height: 1;
margin-bottom: 8px;
}
.bench-rate span {
font-size: 1rem;
font-weight: 500;
color: rgba(255,255,255,0.45);
}
.bench-desc {
color: rgba(255,255,255,0.6);
font-size: 0.93rem;
line-height: 1.75;
margin-top: 16px;
}
/* Protection Charms / ward cards */
.ward-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 28px;
}
.ward-card {
background: rgba(255,255,255,0.07);
border: 1px solid rgba(255,255,255,0.12);
border-radius: var(--radius);
padding: 36px 32px;
position: relative;
overflow: hidden;
transition: box-shadow var(--transition), border-color var(--transition), background var(--transition);
}
.ward-card::after {
content: '';
position: absolute;
bottom: 0; left: 0; right: 0;
height: 3px;
background: linear-gradient(90deg, var(--gold), var(--orange));
transform: scaleX(0);
transition: transform var(--transition);
transform-origin: left;
}
.ward-card:hover {
background: rgba(255,255,255,0.11);
box-shadow: 0 8px 32px rgba(0,0,0,0.25);
}
.ward-card:hover::after { transform: scaleX(1); }
.ward-card--mid {
border-color: rgba(124,58,237,0.55);
background: rgba(124,58,237,0.09);
}
.ward-card--top {
border-color: var(--gold);
background: rgba(251,191,36,0.06);
}
.ward-card--top:hover { border-color: var(--gold); }
.ward-tier {
font-family: var(--font-head);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--gold);
margin-bottom: 6px;
}
.ward-name {
font-family: var(--font-head);
font-size: 1.3rem;
font-weight: 800;
color: var(--white);
margin-bottom: 4px;
}
.ward-price {
font-family: var(--font-head);
font-size: 2rem;
font-weight: 800;
color: var(--gold);
line-height: 1.15;
margin-bottom: 20px;
}
.ward-price span {
font-size: 0.85rem;
font-weight: 500;
color: rgba(255,255,255,0.4);
}
/* Enquire button on each package */
.pricing-enquire {
margin-top: 18px;
width: 100%;
justify-content: center;
font-size: 0.85rem;
padding: 12px 18px;
}
.bench-enquire { margin-top: 22px; }
.ward-enquire { margin-top: 20px; }
/* "See full details" trigger */
.pkg-details-btn {
background: none;
border: none;
cursor: pointer;
color: var(--gold);
font-family: var(--font-head);
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.03em;
display: inline-flex;
align-items: center;
gap: 7px;
margin-top: 16px;
padding: 6px 0;
transition: gap var(--transition), color var(--transition);
}
.pkg-details-btn:hover { gap: 11px; color: var(--white); }
.pricing-card, .ward-card, .bench-card { cursor: pointer; }
/* Package detail modal */
.pkg-modal-overlay {
position: fixed;
inset: 0;
background: rgba(10,6,30,0.78);
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.25s ease, visibility 0.25s ease;
}
.pkg-modal-overlay.open { opacity: 1; visibility: visible; }
.pkg-modal {
background: linear-gradient(160deg, #2D1F6E 0%, #1E1550 100%);
border: 1px solid rgba(251,191,36,0.25);
border-radius: var(--radius);
max-width: 560px;
width: 100%;
max-height: 88vh;
overflow-y: auto;
padding: 40px;
position: relative;
box-shadow: 0 24px 70px rgba(0,0,0,0.5);
transform-origin: 50% 50%;
will-change: transform;
}
/* The spiral open animation is driven in JS (see below). For users who
prefer reduced motion, fall back to a gentle fade/scale instead. */
@media (prefers-reduced-motion: reduce) {
.pkg-modal { transform: none !important; transition: none !important; }
}
.pkg-sparkle {
position: fixed;
z-index: 1100;
pointer-events: none;
line-height: 1;
transform: translate(-50%, -50%);
text-shadow: 0 0 6px rgba(255,255,255,0.4);
will-change: transform, opacity;
}
.pkg-modal-close {
position: absolute;
top: 18px; right: 18px;
width: 38px; height: 38px;
border-radius: 50%;
border: 1px solid rgba(255,255,255,0.18);
background: rgba(255,255,255,0.06);
color: rgba(255,255,255,0.8);
font-size: 1.3rem;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s, color 0.2s;
}
.pkg-modal-close:hover { background: rgba(255,255,255,0.14); color: var(--white); }
.pkg-modal-emoji { font-size: 2.4rem; line-height: 1; margin-bottom: 10px; }
.pkg-modal-name {
font-family: var(--font-head);
font-size: 1.6rem;
font-weight: 800;
color: var(--white);
margin-bottom: 4px;
padding-right: 36px;
}
.pkg-modal-price {
font-family: var(--font-head);
font-size: 1.3rem;
font-weight: 800;
color: var(--gold);
margin-bottom: 12px;
}
.pkg-modal-price span { font-size: 0.85rem; font-weight: 500; color: rgba(255,255,255,0.45); }
.pkg-modal-lead {
color: rgba(255,255,255,0.62);
font-size: 0.93rem;
line-height: 1.65;
margin-bottom: 4px;
}
.pkg-modal-divider {
border: none;
border-top: 1px solid rgba(255,255,255,0.12);
margin: 22px 0;
}
.pkg-range {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.pkg-range-col {
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 14px;
padding: 16px 18px;
}
.pkg-range-col h4 {
font-family: var(--font-head);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--gold);
margin-bottom: 6px;
}
.pkg-range-col .amt {
font-family: var(--font-head);
font-size: 1.15rem;
font-weight: 800;
color: var(--white);
margin-bottom: 10px;
}
.pkg-range-col p { font-size: 0.84rem; color: rgba(255,255,255,0.72); line-height: 1.5; }
.pkg-specs { display: flex; flex-direction: column; }
.pkg-spec {
display: flex;
justify-content: space-between;
gap: 16px;
font-size: 0.9rem;
padding: 11px 0;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.pkg-spec:last-child { border-bottom: none; }
.pkg-spec .k { color: rgba(255,255,255,0.5); display: flex; align-items: center; gap: 9px; }
.pkg-spec .k i { color: var(--gold); width: 16px; text-align: center; }
.pkg-spec .v { color: var(--white); font-weight: 600; text-align: right; }
.pkg-modal-enquire { margin-top: 26px; }
html.modal-open { overflow: hidden; }
@media (max-width: 520px) {
.pkg-range { grid-template-columns: 1fr; }
.pkg-modal { padding: 30px 22px; }
}
/* CTA strip */
.pricing-cta {
background: linear-gradient(145deg, var(--purple-deep) 0%, #2D1860 100%);
padding: 90px 0;
text-align: center;
position: relative;
overflow: hidden;
}
.pricing-cta::before {
content: '';
position: absolute;
width: 500px; height: 500px;
background: radial-gradient(circle, rgba(124,58,237,0.2) 0%, transparent 70%);
top: -200px; left: 50%;
transform: translateX(-50%);
pointer-events: none;
}
.pricing-cta-inner {
position: relative;
z-index: 1;
max-width: 600px;
margin: 0 auto;
padding: 0 24px;
}
.pricing-cta .section-lead { margin-bottom: 36px; }
/* Responsive */
@media (max-width: 900px) {
.pricing-grid { grid-template-columns: 1fr 1fr; }
.pricing-grid--2col { grid-template-columns: 1fr 1fr; max-width: 100%; margin-top: 28px; }
.bench-card { grid-template-columns: 1fr; gap: 28px; padding: 32px; }
.ward-grid { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
.pricing-grid { grid-template-columns: 1fr; }
.pricing-grid--2col { grid-template-columns: 1fr; }
.bench-rate { font-size: 2rem; }
}
</style>
</head>
<body>
<!-- ===================== NAV ===================== -->
<header class="site-header" id="site-header">
<nav class="nav-container">
<a href="index.php" class="nav-logo">
<span class="logo-icon">🧙</span>
<span class="logo-text">Fraxle<span class="logo-dot">.net</span></span>
</a>
<ul class="nav-links" id="nav-links">
<li><a href="index.php#services">Services</a></li>
<li><a href="index.php#about">About</a></li>
<li><a href="index.php#projects">Projects</a></li>
<li><a href="estimates.php" class="nav-active">Estimates</a></li>
<li><a href="index.php#contact" class="btn nav-cta">Get in Touch</a></li>
</ul>
<button class="nav-hamburger" id="nav-hamburger" aria-label="Toggle menu" aria-expanded="false">
<span></span><span></span><span></span>
</button>
</nav>
</header>
<!-- ===================== HERO ===================== -->
<section class="pricing-hero">
<div class="hero-orbs">
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
</div>
<div class="pricing-hero-content">
<p class="hero-eyebrow">✦ Transparent Pricing ✦</p>
<h1 class="pricing-hero-title">Spells &amp; <span>Pricing</span></h1>
<p class="pricing-hero-lead">Clear, honest estimates for websites, tech support and ongoing care. No hidden fees - just magic at a fair price.</p>
</div>
<div class="pricing-hero-wave">
<svg viewBox="0 0 1440 80" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
<path d="M0,40 C300,80 900,0 1440,50 L1440,80 L0,80 Z" fill="#251D70"/>
</svg>
</div>
</section>
<!-- ===================== WEBSITE PACKAGES ===================== -->
<section class="section-pad" style="background:#251D70;">
<div class="container">
<div class="section-header">
<p class="section-eyebrow">Website Design</p>
<h2 class="section-title">Choose Your Spell</h2>
<p class="section-lead">From a simple first presence to a full ecommerce empire - there is a package for every ambition.</p>
</div>
<div class="pricing-grid">
<!-- The First Spark -->
<div class="pricing-card reveal">
<div class="pricing-emoji">🌱</div>
<div class="pricing-name">The First Spark</div>
<div class="pricing-price">From £250</div>
<hr class="pricing-divider">
<p class="pricing-includes-label">Includes</p>
<ul class="pricing-includes">
<li>Up to 5 pages</li>
<li>Mobile-friendly design</li>
<li>Contact form</li>
<li>Basic SEO setup</li>
<li>WordPress website</li>
</ul>
<p class="pricing-tagline">Perfect for businesses taking their first steps into the digital realm.</p>
<a href="index.php?package=first-spark#contact" class="btn btn-accent pricing-enquire">
<i class="fas fa-envelope"></i> Enquire about this
</a>
</div>
<!-- The Enchanted Workshop -->
<div class="pricing-card reveal">
<div class="pricing-emoji">✨</div>
<div class="pricing-name">The Enchanted Workshop</div>
<div class="pricing-price">£750 <span>- £1,500</span></div>
<hr class="pricing-divider">
<p class="pricing-includes-label">Includes</p>
<ul class="pricing-includes">
<li>Custom design</li>
<li>Blog or news section</li>
<li>Photo galleries</li>
<li>Social media integration</li>
<li>Analytics setup</li>
<li>Search engine optimisation</li>
</ul>
<p class="pricing-tagline">For businesses ready to expand their reach and establish a stronger online presence.</p>
<a href="index.php?package=enchanted-workshop#contact" class="btn btn-accent pricing-enquire">
<i class="fas fa-envelope"></i> Enquire about this
</a>
</div>
<!-- The Arcane Forge - Most Popular -->
<div class="pricing-card pricing-card--popular reveal">
<div class="pricing-emoji">🔮</div>
<div class="pricing-name">The Arcane Forge</div>
<div class="pricing-price">£1,500 <span>- £3,000</span></div>
<hr class="pricing-divider">
<p class="pricing-includes-label">Includes</p>
<ul class="pricing-includes">
<li>Advanced page layouts</li>
<li>Custom functionality</li>
<li>Enhanced forms</li>
<li>Third-party integrations</li>
<li>Performance optimisation</li>
<li>Training and support</li>
</ul>
<p class="pricing-tagline">When a standard website simply won't contain your ambitions.</p>
<a href="index.php?package=arcane-forge#contact" class="btn btn-accent pricing-enquire">
<i class="fas fa-envelope"></i> Enquire about this
</a>
</div>
</div>
<div class="pricing-grid pricing-grid--2col">
<!-- The Merchant's Portal -->
<div class="pricing-card reveal">
<div class="pricing-emoji">🛒</div>
<div class="pricing-name">The Merchant's Portal</div>
<div class="pricing-price">£3,000 <span>- £5,000</span></div>
<hr class="pricing-divider">
<p class="pricing-includes-label">Includes</p>
<ul class="pricing-includes">
<li>Shopify or WooCommerce</li>
<li>Product catalogue setup</li>
<li>Payment gateways</li>
<li>Shipping configuration</li>
<li>Customer account features</li>
<li>Store management training</li>
</ul>
<p class="pricing-tagline">Everything needed to open your digital marketplace.</p>
<a href="index.php?package=merchants-portal#contact" class="btn btn-accent pricing-enquire">
<i class="fas fa-envelope"></i> Enquire about this
</a>
</div>
<!-- The Grand Emporium -->
<div class="pricing-card reveal">
<div class="pricing-emoji">⚡</div>
<div class="pricing-name">The Grand Emporium</div>
<div class="pricing-price">£5,000 <span>- £7,500+</span></div>
<hr class="pricing-divider">
<p class="pricing-includes-label">Includes</p>
<ul class="pricing-includes">
<li>Multi-category stores</li>
<li>Subscription products</li>
<li>Advanced customer accounts</li>
<li>Automation and workflows</li>
<li>Marketing integrations</li>
<li>Bespoke functionality</li>
<li>Launch support</li>
</ul>
<p class="pricing-tagline">For businesses seeking a digital flagship rather than a simple online shop.</p>
<a href="index.php?package=grand-emporium#contact" class="btn btn-accent pricing-enquire">
<i class="fas fa-envelope"></i> Enquire about this
</a>
</div>
</div>
</div>
</section>
<!-- ===================== WIZARD'S BENCH ===================== -->
<section class="section-pad" style="background:#3B2DA8;">
<div class="container">
<div class="section-header">
<p class="section-eyebrow">Ad-hoc Support</p>
<h2 class="section-title">The Wizard's Bench</h2>
<p class="section-lead">For those unexpected gremlins that find their way into websites, email systems and office technology.</p>
</div>
<div class="bench-card reveal">
<div class="bench-left">
<div class="bench-emoji">🛠️</div>
<div class="bench-name">The Wizard's Bench</div>
<div class="bench-rate">£40 <span>/ hour</span></div>
<p class="bench-desc">Technical support billed by the hour. Minimum one hour applies. Remote support available - no call-out charge for remote sessions.</p>
<a href="index.php?package=wizards-bench#contact" class="btn btn-accent bench-enquire">
<i class="fas fa-envelope"></i> Book some support
</a>
</div>
<div class="bench-right">
<p class="pricing-includes-label">Services include</p>
<ul class="pricing-includes">
<li>WordPress troubleshooting</li>
<li>Shopify assistance</li>
<li>Email issues</li>
<li>Domain and DNS management</li>
<li>Website repairs</li>
<li>General digital support</li>
</ul>
</div>
</div>
</div>
</section>
<!-- ===================== PROTECTION CHARMS ===================== -->
<section class="section-pad" style="background:#251D70;">
<div class="container">
<div class="section-header">
<p class="section-eyebrow">Ongoing Care</p>
<h2 class="section-title">Protection Charms</h2>
<p class="section-lead">Monthly plans to keep your website healthy, secure and performing at its best.</p>
</div>
<div class="ward-grid">
<!-- Minor Ward -->
<div class="ward-card reveal">
<p class="ward-tier">Tier 1</p>
<p class="ward-name">Minor Ward</p>
<div class="ward-price">£10 <span>/ month</span></div>
<hr class="pricing-divider">
<ul class="pricing-includes">
<li>WordPress updates</li>
<li>Security monitoring</li>
<li>Monthly backups</li>
</ul>
<a href="index.php?package=minor-ward#contact" class="btn btn-accent ward-enquire">
<i class="fas fa-envelope"></i> Enquire about this
</a>
</div>
<!-- Greater Ward -->
<div class="ward-card ward-card--mid reveal">
<p class="ward-tier">Tier 2</p>
<p class="ward-name">Greater Ward</p>
<div class="ward-price">£25 <span>/ month</span></div>
<hr class="pricing-divider">
<p class="pricing-includes-label">Everything in Minor Ward, plus</p>
<ul class="pricing-includes">
<li>Monthly health checks</li>
<li>Priority email support</li>
<li>Performance monitoring</li>
</ul>
<a href="index.php?package=greater-ward#contact" class="btn btn-accent ward-enquire">
<i class="fas fa-envelope"></i> Enquire about this
</a>
</div>
<!-- Merchant's Ward -->
<div class="ward-card ward-card--top reveal">
<p class="ward-tier">Tier 3</p>
<p class="ward-name">Merchant's Ward</p>
<div class="ward-price">£50 <span>/ month</span></div>
<hr class="pricing-divider">
<p class="pricing-includes-label">Everything in Greater Ward, plus</p>
<ul class="pricing-includes">
<li>Ecommerce monitoring</li>
<li>Store maintenance</li>
<li>Priority response times</li>
<li>Monthly support allowance</li>
</ul>
<a href="index.php?package=merchants-ward#contact" class="btn btn-accent ward-enquire">
<i class="fas fa-envelope"></i> Enquire about this
</a>
</div>
</div>
</div>
</section>
<!-- ===================== CTA ===================== -->
<section class="pricing-cta">
<div class="pricing-cta-inner">
<p class="section-eyebrow">Ready to Begin?</p>
<h2 class="section-title">Not sure which spell you need?</h2>
<p class="section-lead">Drop me a message and we can figure out the right package together - no pressure, no jargon.</p>
<a href="index.php#contact" class="btn btn-accent">
<i class="fas fa-envelope"></i> Get in Touch
</a>
</div>
</section>
<!-- ===================== FOOTER ===================== -->
<footer class="site-footer">
<div class="container">
<p class="footer-logo">Fraxle<span>.net</span></p>
<p class="footer-tagline">Friendly web &amp; tech for small businesses</p>
<p class="footer-links"><a href="terms.php">Terms &amp; Conditions</a></p>
<p class="footer-copy">&copy; <?= date('Y') ?> Fraxle.net All rights reserved.</p>
</div>
</footer>
<script src="js/main.js"></script>
<script>
/* ===== 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' }
]
},
'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' }
]
},
'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' }
]
}
};
// 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);
// Pop outward in a random direction and fade — reads as sparkles bursting off the box edge.
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(); };
}
// Scatter sparkles around the perimeter of the box as it expands and travels.
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(); // live rect reflects the running transform
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) {
// Measure the modal's resting centre, then glide in along a spiral path from the click point.
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; // click offset from the modal's resting centre
var dist = Math.hypot(tx, ty);
var startAng = Math.atan2(ty, tx); // angle of the click point around the centre
var maxR = Math.max(55, Math.min(dist * 0.5, 120)); // capped flourish radius — same feel anywhere
var turns = 1.25; // spins of the flourish — the "flick" of the wand
var duration = 1000;
// A wand-flick spring: the box starts as a dot at the click, springs out into a
// tight, controlled spiral, and glides into the centre as it grows.
// - base glides straight from the wand tip to the centre (no giant orbit)
// - a capped spiral flourish blooms out (sin envelope) then tightens to nothing
// - amplitude is clamped, so edge clicks and centre clicks look the same
var STEPS = 44, frames = [];
for (var i = 0; i <= STEPS; i++) {
var t = i / STEPS;
var g = Math.pow(t, 1.4); // growth + travel-to-centre progress
var baseX = tx * (1 - g); // glide from wand tip -> centre
var baseY = ty * (1 - g);
var amp = maxR * Math.sin(Math.PI * Math.pow(t, 0.7)); // 0 -> bloom -> 0
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();
}
// Scatter sparkles evenly around the outline of a rectangle (a quick burst).
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); // leave sparkles tracing the box shape
}
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();
});
// Resolve a click origin: real pointer coords, or the element's centre for keyboard activation
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];
}
// Wire up every card from its enquire link's package slug
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;
// Insert a "See full details" trigger just before the enquire button
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);
// Clicking anywhere on the card (except links/buttons) opens it too
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]);
});
}
});
})();
</script>
</body>
</html>