new pages updated
This commit is contained in:
parent
c206be1c9d
commit
c073ea873c
@ -20,18 +20,12 @@ export default function AboutPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="about-page-wrapper">
|
<div className="about-page-wrapper">
|
||||||
{/* Inner Banner */}
|
<section className="inner-banner">
|
||||||
<section className="inner-banner fade-up">
|
<h1 className="inner-banner-title">About <span>Us.</span></h1>
|
||||||
<div className="inner-banner-content">
|
<div className="inner-banner-breadcrumbs">
|
||||||
<h1 className="section-h2">Your Trusted <span>Fencing</span> Partner</h1>
|
<Link href="/">Home</Link>
|
||||||
<div className="banner-breadcrumb" style={{ marginTop: '30px', marginBottom: '0' }}>
|
<span>/</span>
|
||||||
<Link href="/">
|
About
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
|
||||||
Home
|
|
||||||
</Link>
|
|
||||||
<span className="separator">/</span>
|
|
||||||
<span>About Us</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@ -1,274 +1,679 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import ReCAPTCHA from "react-google-recaptcha";
|
import Link from "next/link";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
const ContactClient = () => {
|
const ContactClient = () => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
|
company: "",
|
||||||
name: "",
|
name: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
email: "",
|
email: "",
|
||||||
service: "Commercial",
|
product: "",
|
||||||
message: "",
|
city: "",
|
||||||
|
qty: "",
|
||||||
|
notes: "",
|
||||||
|
tc: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const [formErrors, setFormErrors] = useState<any>({});
|
const [formStatus, setFormStatus] = useState<"idle" | "submitting" | "success" | "error">("idle");
|
||||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
const [tcError, setTcError] = useState(false);
|
||||||
const [alert, setAlert] = useState({ show: false, type: "", message: "" });
|
const [openFaq, setOpenFaq] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const revealObs = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(e => {
|
||||||
|
if (e.isIntersecting) {
|
||||||
|
e.target.classList.add('visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { threshold: 0.06 });
|
||||||
|
|
||||||
|
document.querySelectorAll('.reveal').forEach(el => revealObs.observe(el));
|
||||||
|
|
||||||
|
return () => revealObs.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||||
const { name, value } = e.target;
|
const { id, value, type } = e.target;
|
||||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
if (type === 'checkbox') {
|
||||||
|
const checked = (e.target as HTMLInputElement).checked;
|
||||||
|
setFormData(prev => ({ ...prev, tc: checked }));
|
||||||
|
if (checked) setTcError(false);
|
||||||
|
} else {
|
||||||
|
const key = id.replace('f-', '');
|
||||||
|
setFormData(prev => ({ ...prev, [key]: value }));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCaptchaChange = (token: string | null) => {
|
const toggleFaq = (index: number) => {
|
||||||
setCaptchaToken(token);
|
setOpenFaq(openFaq === index ? null : index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const submitContactForm = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const errors: any = {};
|
// Validate email
|
||||||
if (!formData.name.trim()) errors.name = "Name is required.";
|
if (!formData.email || !formData.email.includes('@')) {
|
||||||
if (!formData.phone.trim()) errors.phone = "Phone is required.";
|
const el = document.getElementById('f-email');
|
||||||
if (!formData.email.trim()) errors.email = "Email is required.";
|
if (el) {
|
||||||
if (!formData.service.trim()) errors.service = "Please select a service.";
|
el.style.outline = '2px solid var(--orange)';
|
||||||
if (!formData.message.trim()) errors.message = "Message is required.";
|
el.focus();
|
||||||
if (!captchaToken) errors.captcha = "Please verify the CAPTCHA.";
|
setTimeout(() => el.style.outline = '', 2000);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setFormErrors(errors);
|
// Validate T&C
|
||||||
if (Object.keys(errors).length > 0) return;
|
if (!formData.tc) {
|
||||||
|
setTcError(true);
|
||||||
|
const el = document.getElementById('tc-row-contact');
|
||||||
|
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormStatus("submitting");
|
||||||
|
|
||||||
const emailData = {
|
const emailData = {
|
||||||
...formData,
|
name: formData.name,
|
||||||
message: `<b>Project Type:</b> ${formData.service}<br /><br /><b>Message:</b> ${formData.message}`,
|
phone: formData.phone,
|
||||||
|
email: formData.email,
|
||||||
|
service: formData.product || "General Enquiry",
|
||||||
|
message: `
|
||||||
|
<b>Company:</b> ${formData.company}<br />
|
||||||
|
<b>City:</b> ${formData.city}<br />
|
||||||
|
<b>Quantity:</b> ${formData.qty}<br /><br />
|
||||||
|
<b>Notes:</b> ${formData.notes}
|
||||||
|
`,
|
||||||
to: "info@vgfenceproducts.com",
|
to: "info@vgfenceproducts.com",
|
||||||
senderName: "VG Fence Contact Page",
|
senderName: "VG Fence Contact Page",
|
||||||
recaptchaToken: captchaToken,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setAlert({ show: true, type: "info", message: "Sending your message..." });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.post("https://mailserver.metatronnest.com/send", emailData, {
|
await axios.post("https://mailserver.metatronnest.com/send", emailData, {
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
|
setFormStatus("success");
|
||||||
setAlert({
|
const successEl = document.getElementById('form-success');
|
||||||
show: true,
|
if (successEl) successEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
type: "success",
|
|
||||||
message: "Thank you! Your message has been sent successfully.",
|
|
||||||
});
|
|
||||||
|
|
||||||
setFormData({
|
|
||||||
name: "",
|
|
||||||
phone: "",
|
|
||||||
email: "",
|
|
||||||
service: "Commercial",
|
|
||||||
message: "",
|
|
||||||
});
|
|
||||||
setCaptchaToken(null);
|
|
||||||
setFormErrors({});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Error sending email:", error);
|
console.error("❌ Error sending email:", error);
|
||||||
setAlert({
|
setFormStatus("error");
|
||||||
show: true,
|
alert("Failed to send message. Please try again later or email us directly at info@vgfenceproducts.com");
|
||||||
type: "danger",
|
|
||||||
message: "Failed to send message. Please try again later.",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (alert.show && alert.type !== "info") {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
setAlert((prev) => ({ ...prev, show: false }));
|
|
||||||
}, 5000);
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}
|
|
||||||
}, [alert.show, alert.type]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: 'var(--cream)' }}>
|
<div className="contact-page">
|
||||||
{/* Inner Banner */}
|
|
||||||
<section className="inner-banner fade-up">
|
{/* HERO + QUOTE FORM */}
|
||||||
<div className="inner-banner-content">
|
<section className="inner-banner">
|
||||||
<h1 className="section-h2">How to <span>Contact</span> Us</h1>
|
<h1 className="inner-banner-title">Get in <span>touch.</span></h1>
|
||||||
<div className="banner-breadcrumb" style={{ marginTop: '30px', marginBottom: '0' }}>
|
<div className="inner-banner-breadcrumbs">
|
||||||
<Link href="/">
|
<Link href="/">Home</Link>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
<span>/</span>
|
||||||
Home
|
Contact
|
||||||
</Link>
|
</div>
|
||||||
<span className="separator">/</span>
|
</section> {/* MAIN CONTACT SECTION */}
|
||||||
<span>Contact</span>
|
<section className="contact-section" id="main-form">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">Send us a message</div>
|
||||||
|
<h2 className="sh">Tell us what you need<br /><span>and where it's going.</span></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="contact-layout">
|
||||||
|
{/* LEFT - INFO */}
|
||||||
|
<div className="contact-left reveal">
|
||||||
|
<div className="hcc">
|
||||||
|
<div className="hcc-label">📧 Email — primary contact</div>
|
||||||
|
<div className="hcc-val">
|
||||||
|
<a href="mailto:info@vgfenceproducts.com">info@vgfenceproducts.com</a>
|
||||||
|
</div>
|
||||||
|
<div className="hcc-sub">
|
||||||
|
Send quote requests, product enquiries, rental bookings, contractor account applications, and any question about our products and services.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Contact Content */}
|
<div className="hcc">
|
||||||
<section className="section">
|
<div className="hcc-label">🌐 Website</div>
|
||||||
<div className="container" style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
<div className="hcc-val">
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1.2fr', gap: '80px' }} className="contact-layout">
|
<a href="https://vgfence.com" target="_blank" rel="noopener noreferrer">vgfence.com</a>
|
||||||
|
</div>
|
||||||
|
<div className="hcc-sub">
|
||||||
|
Browse all products, specifications, service pages, and location-specific information for your city.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Left Col: Info & Map */}
|
<div className="hcc">
|
||||||
|
<div className="hcc-label">📍 Based in</div>
|
||||||
|
<div className="hcc-val">Kitchener–Waterloo, Ontario</div>
|
||||||
|
<div className="hcc-sub">
|
||||||
|
We are a B2B supplier operating out of the KWC region. We do not have a retail storefront — contact us by email to arrange orders, pickup, or delivery.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hcc">
|
||||||
|
<div className="hcc-label">🕐 Business hours</div>
|
||||||
|
<div className="hcc-val" style={{ fontSize: '15px', color: 'var(--orange)' }}>
|
||||||
|
2-hour response commitment
|
||||||
|
</div>
|
||||||
|
<div className="hcc-hours" style={{ marginTop: '10px' }}>
|
||||||
|
<div className="hcc-hour-item">
|
||||||
|
<div className="hcc-hour-day" style={{ fontSize: '13px', color: 'var(--gray-600)' }}>Mon – Fri</div>
|
||||||
|
<div className="hcc-hour-time" style={{ fontSize: '14px', fontWeight: 700, color: 'var(--navy)' }}>8:00 AM – 5:00 PM</div>
|
||||||
|
</div>
|
||||||
|
<div className="hcc-hour-item">
|
||||||
|
<div className="hcc-hour-day" style={{ fontSize: '13px', color: 'var(--gray-600)' }}>Saturday</div>
|
||||||
|
<div className="hcc-hour-time" style={{ fontSize: '14px', fontWeight: 700, color: 'var(--navy)' }}>9:00 AM – 2:00 PM</div>
|
||||||
|
</div>
|
||||||
|
<div className="hcc-hour-item">
|
||||||
|
<div className="hcc-hour-day" style={{ fontSize: '13px', color: 'var(--gray-600)' }}>Sunday</div>
|
||||||
|
<div className="hcc-hour-time closed" style={{ fontSize: '13px', color: 'var(--gray-400)' }}>Closed</div>
|
||||||
|
</div>
|
||||||
|
<div className="hcc-hour-item">
|
||||||
|
<div className="hcc-hour-day" style={{ fontSize: '13px', color: 'var(--gray-600)' }}>Stat holidays</div>
|
||||||
|
<div className="hcc-hour-time closed" style={{ fontSize: '13px', color: 'var(--gray-400)' }}>Closed</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* RIGHT - FORM */}
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="contact-form-wrap">
|
||||||
|
<div className="form-head">
|
||||||
|
<div className="form-head-title">Request a <span>quote</span></div>
|
||||||
|
<div className="form-head-sub">Fill in the details below. We respond to all enquiries within 2 business hours during business hours. Emergency temp fence rentals in KWC – contact us directly by email for fastest response.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{formStatus === "success" ? (
|
||||||
|
<div className="form-success">
|
||||||
|
<div className="fs-icon">✅</div>
|
||||||
|
<p>Quote request sent!</p>
|
||||||
|
<p className="fs-note">
|
||||||
|
We'll respond to your email within 2 business hours. Check your spam folder if you don't hear from us.
|
||||||
|
</p>
|
||||||
|
<button className="form-btn mt-24" onClick={() => setFormStatus("idle")}>Send another request</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={submitContactForm}>
|
||||||
|
<div className="frow">
|
||||||
<div>
|
<div>
|
||||||
<div style={{ marginBottom: '48px' }}>
|
<label className="fl">Company name</label>
|
||||||
<h3 className="section-h2" style={{ fontSize: '32px', marginBottom: '32px' }}>Office <span>Details.</span></h3>
|
<input className="fi" type="text" placeholder="ABC Fence Co." id="f2-company" value={formData.company} onChange={handleChange} />
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
|
||||||
<div style={{ display: 'flex', gap: '16px' }}>
|
|
||||||
<div style={{ width: '40px', height: '40px', background: 'var(--orange)', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontWeight: 700, color: 'var(--navy)', marginBottom: '4px', fontFamily: 'var(--font-display)', textTransform: 'uppercase', fontSize: '14px', letterSpacing: '.05em' }}>Our Location</div>
|
<label className="fl">Your name</label>
|
||||||
<div style={{ fontSize: '15px', color: 'var(--gray-600)', lineHeight: 1.5 }}>125 Earl Thompson Rd, Ayr, ON N0B 1E0, Canada</div>
|
<input className="fi" type="text" placeholder="John Smith" id="f2-name" value={formData.name} onChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="frow">
|
||||||
<div style={{ display: 'flex', gap: '16px' }}>
|
<div>
|
||||||
<div style={{ width: '40px', height: '40px', background: 'var(--orange)', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
<label className="fl">Phone</label>
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
<input className="fi" type="tel" placeholder="519-xxx-xxxx" id="f2-phone" value={formData.phone} onChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontWeight: 700, color: 'var(--navy)', marginBottom: '4px', fontFamily: 'var(--font-display)', textTransform: 'uppercase', fontSize: '14px', letterSpacing: '.05em' }}>Direct Line Numbers</div>
|
<label className="fl">Email <span className="text-orange">*</span></label>
|
||||||
<div style={{ fontSize: '15px', color: 'var(--gray-600)', lineHeight: 1.5 }}>+1 226-888-7999</div>
|
<input className="fi" type="email" placeholder="you@company.com" id="f2-email" value={formData.email} onChange={handleChange} required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<label className="fl">Product / service needed</label>
|
||||||
<div style={{ display: 'flex', gap: '16px' }}>
|
<select className="fi" id="f2-product" value={formData.product} onChange={handleChange}>
|
||||||
<div style={{ width: '40px', height: '40px', background: 'var(--orange)', borderRadius: '8px', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
<option value="">Select what you need...</option>
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
|
<optgroup label="── Fence Materials ──">
|
||||||
</div>
|
<option>Chain link fence — commercial</option>
|
||||||
<div>
|
<option>Chain link fence — residential</option>
|
||||||
<div style={{ fontWeight: 700, color: 'var(--navy)', marginBottom: '4px', fontFamily: 'var(--font-display)', textTransform: 'uppercase', fontSize: '14px', letterSpacing: '.05em' }}>Our Email</div>
|
<option>Ornamental fence & gates</option>
|
||||||
<div style={{ fontSize: '15px', color: 'var(--gray-600)', lineHeight: 1.5 }}>info@vgfenceproducts.com</div>
|
<option>Composite fence</option>
|
||||||
</div>
|
<option>Glass railing</option>
|
||||||
</div>
|
<option>Aluminum railing</option>
|
||||||
</div>
|
<option>Expert Stain & Seal products</option>
|
||||||
</div>
|
<option>Fence Armor (post caps / guards)</option>
|
||||||
|
<option>Wood fence materials</option>
|
||||||
{/* Map Placeholder */}
|
</optgroup>
|
||||||
<div style={{ borderRadius: '12px', overflow: 'hidden', border: '1px solid var(--gray-200)', height: '440px', background: 'var(--white)', position: 'relative' }}>
|
<optgroup label="── Services ──">
|
||||||
<iframe
|
<option>Temporary fence rental — construction</option>
|
||||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2903.9575459392213!2d-80.447551023419!3d43.29854497112028!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x882c7f0f0f0f0f0f%3A0x0!2zMTI1IEVhcmwgVGhvbXBzb24gUmQsIEF5ciwgT04gTjBCIDFFMCwgQ2FuYWRh!5e0!3m2!1sen!2sca!4v1713350000000!5m2!1sen!2sca"
|
<option>Temporary fence rental — event / beer garden</option>
|
||||||
width="100%"
|
<option>Wood staining service</option>
|
||||||
height="100%"
|
<option>2D fence drawing / permit drawing</option>
|
||||||
style={{ border: 0 }}
|
<option>Contractor account application</option>
|
||||||
allowFullScreen
|
</optgroup>
|
||||||
loading="lazy"
|
<option>Multiple products / full project</option>
|
||||||
></iframe>
|
<option>Other / not sure yet</option>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="quote-card" style={{ height: 'fit-content', background: 'var(--white)', borderRadius: '16px', padding: '30px', boxShadow: '0 20px 40px rgba(15, 36, 68, .08)', border: '1px solid var(--gray-200)' }}>
|
|
||||||
<div style={{ marginBottom: '32px' }}>
|
|
||||||
<h3 style={{ fontFamily: 'var(--font-display)', fontSize: '28px', fontWeight: 800, textTransform: 'uppercase', color: 'var(--navy)', marginBottom: '8px' }}>Send us a <span>Message.</span></h3>
|
|
||||||
<p style={{ fontSize: '14px', color: 'var(--gray-600)' }}>Fill out the form below and our team will get back to you within 24 hours.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form className="contact-form" onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '20px' }}>
|
|
||||||
{alert.show && (
|
|
||||||
<div className={`alert alert-${alert.type === 'danger' ? 'danger' : (alert.type === 'info' ? 'info' : 'success')} mb-4`} style={{
|
|
||||||
padding: '12px 16px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
fontSize: '14px',
|
|
||||||
background: alert.type === 'danger' ? '#fee2e2' : (alert.type === 'info' ? '#e0f2fe' : '#f0fdf4'),
|
|
||||||
color: alert.type === 'danger' ? '#991b1b' : (alert.type === 'info' ? '#075985' : '#166534'),
|
|
||||||
border: `1px solid ${alert.type === 'danger' ? '#fecaca' : (alert.type === 'info' ? '#bae6fd' : '#bbf7d0')}`
|
|
||||||
}}>
|
|
||||||
{alert.message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }} className="form-row">
|
|
||||||
<div className="form-group">
|
|
||||||
<label style={{ display: 'block', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', color: 'var(--navy)', marginBottom: '8px' }}>Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
placeholder="John Doe"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ width: '100%', padding: '12px 16px', borderRadius: '4px', border: formErrors.name ? '1px solid #ef4444' : '1px solid var(--gray-200)', fontSize: '14px', background: 'var(--white)' }}
|
|
||||||
/>
|
|
||||||
{formErrors.name && <small style={{ color: '#ef4444', fontSize: '11px', marginTop: '4px', display: 'block' }}>{formErrors.name}</small>}
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<label style={{ display: 'block', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', color: 'var(--navy)', marginBottom: '8px' }}>Email</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="john@example.com"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ width: '100%', padding: '12px 16px', borderRadius: '4px', border: formErrors.email ? '1px solid #ef4444' : '1px solid var(--gray-200)', fontSize: '14px', background: 'var(--white)' }}
|
|
||||||
/>
|
|
||||||
{formErrors.email && <small style={{ color: '#ef4444', fontSize: '11px', marginTop: '4px', display: 'block' }}>{formErrors.email}</small>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }} className="form-row">
|
|
||||||
<div className="form-group">
|
|
||||||
<label style={{ display: 'block', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', color: 'var(--navy)', marginBottom: '8px' }}>Phone Number</label>
|
|
||||||
<input
|
|
||||||
type="tel"
|
|
||||||
name="phone"
|
|
||||||
placeholder="(555) 123-4567"
|
|
||||||
value={formData.phone}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ width: '100%', padding: '12px 16px', borderRadius: '4px', border: formErrors.phone ? '1px solid #ef4444' : '1px solid var(--gray-200)', fontSize: '14px', background: 'var(--white)' }}
|
|
||||||
/>
|
|
||||||
{formErrors.phone && <small style={{ color: '#ef4444', fontSize: '11px', marginTop: '4px', display: 'block' }}>{formErrors.phone}</small>}
|
|
||||||
</div>
|
|
||||||
<div className="form-group">
|
|
||||||
<label style={{ display: 'block', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', color: 'var(--navy)', marginBottom: '8px' }}>Project Type</label>
|
|
||||||
<select
|
|
||||||
name="service"
|
|
||||||
value={formData.service}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ width: '100%', padding: '12px 16px', borderRadius: '4px', border: '1px solid var(--gray-200)', fontSize: '14px', background: 'var(--white)', appearance: 'none' }}
|
|
||||||
>
|
|
||||||
<option value="Commercial">Commercial</option>
|
|
||||||
<option value="Residential">Residential</option>
|
|
||||||
<option value="Event">Event</option>
|
|
||||||
<option value="Other">Other</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
<div className="frow">
|
||||||
|
<div>
|
||||||
|
<label className="fl">Job site city</label>
|
||||||
|
<input className="fi" type="text" placeholder="Kitchener, Guelph..." id="f2-city" value={formData.city} onChange={handleChange} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="fl">Approx. quantity</label>
|
||||||
|
<input className="fi" type="text" placeholder="e.g. 200 linear ft" id="f2-qty" value={formData.qty} onChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<label className="fl">Additional details</label>
|
||||||
|
<textarea className="fi fi-ta" placeholder="Height, finish (black/galvanized), number of gates, delivery date needed..." id="f2-notes" value={formData.notes} onChange={handleChange}></textarea>
|
||||||
|
|
||||||
<div className="form-group">
|
{/* T&C CHECKBOX */}
|
||||||
<label style={{ display: 'block', fontSize: '12px', fontWeight: 700, textTransform: 'uppercase', color: 'var(--navy)', marginBottom: '8px' }}>Message</label>
|
<div className={`tc-row ${formData.tc ? 'checked' : ''} ${tcError ? 'tc-error' : ''}`} id="tc-row-contact-2" onClick={() => { setFormData(p => ({ ...p, tc: !p.tc })); setTcError(false); }}>
|
||||||
<textarea
|
<input type="checkbox" className="tc-check" id="tc-contact-2" checked={formData.tc} onChange={handleChange} onClick={(e) => e.stopPropagation()} />
|
||||||
name="message"
|
<label className="tc-label" htmlFor="tc-contact-2" onClick={(e) => e.stopPropagation()}>
|
||||||
placeholder="How can we help you?"
|
I have read and agree to VG Fence Products'
|
||||||
value={formData.message}
|
<a href="/legal#t1" target="_blank" onClick={(e) => e.stopPropagation()}> Terms & Conditions</a>,
|
||||||
onChange={handleChange}
|
<a href="/legal#t3" target="_blank" onClick={(e) => e.stopPropagation()}> Delivery Policy</a>, and
|
||||||
style={{ width: '100%', padding: '12px 16px', borderRadius: '4px', border: formErrors.message ? '1px solid #ef4444' : '1px solid var(--gray-200)', fontSize: '14px', minHeight: '150px', resize: 'vertical', background: 'var(--white)' }}
|
<a href="/legal#t7" target="_blank" onClick={(e) => e.stopPropagation()}> Photo Verification Policy</a>.
|
||||||
></textarea>
|
</label>
|
||||||
{formErrors.message && <small style={{ color: '#ef4444', fontSize: '11px', marginTop: '4px', display: 'block' }}>{formErrors.message}</small>}
|
|
||||||
</div>
|
</div>
|
||||||
|
{tcError && <div className="tc-error-msg" id="tc-error-msg-contact-2">⚠️ Please read and accept our Terms & Conditions to continue.</div>}
|
||||||
|
|
||||||
<div className="form-group">
|
<button className="form-btn" id="contact-submit-btn-2" disabled={formStatus === "submitting"}>
|
||||||
<ReCAPTCHA
|
{formStatus === "submitting" ? "Sending..." : "Send quote request →"}
|
||||||
sitekey="6LekfpwrAAAAAOTwuP1d2gg-Fv9UEsAjE2gjOQJl"
|
|
||||||
onChange={handleCaptchaChange}
|
|
||||||
/>
|
|
||||||
{formErrors.captcha && <small style={{ color: '#ef4444', fontSize: '11px', marginTop: '4px', display: 'block' }}>{formErrors.captcha}</small>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" className="btn-primary" style={{ width: '100%', marginTop: '10px' }} disabled={alert.type === "info"}>
|
|
||||||
{alert.type === "info" ? "Sending..." : "Send Message →"}
|
|
||||||
</button>
|
</button>
|
||||||
|
<div className="form-note">Or email directly · info@vgfenceproducts.com</div>
|
||||||
</form>
|
</form>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* HOURS + LOCATION */}
|
||||||
|
<section className="info-section">
|
||||||
|
<div className="section-eyebrow">Details</div>
|
||||||
|
<h2 className="sh">Hours, location<br />& <span>response times.</span></h2>
|
||||||
|
<div className="info-grid">
|
||||||
|
<div className="info-card">
|
||||||
|
<div className="ic-icon">🕐</div>
|
||||||
|
<div className="ic-title">Business hours & response time</div>
|
||||||
|
<div className="ic-rows">
|
||||||
|
<div className="ic-row" style={{ color: 'var(--orange)', fontWeight: 600, fontSize: '15px', marginBottom: '8px' }}>Response within 2 business hours</div>
|
||||||
|
<div className="ic-row"><span className="ic-key">Monday – Friday</span><span className="ic-val">8:00 AM – 5:00 PM EST</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-key">Saturday</span><span className="ic-val">9:00 AM – 2:00 PM EST</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-key">Sunday</span><span className="ic-val text-gray-400">Closed</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-key">Statutory holidays</span><span className="ic-val text-gray-400">Closed</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="info-card">
|
||||||
|
<div className="ic-icon">✅</div>
|
||||||
|
<div className="ic-title">We can help with</div>
|
||||||
|
<div className="ic-rows">
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Contractor pricing on all 8 product lines</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Temporary fence rental for construction or events</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">2D fence drawing services & permit drawings</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Professional wood staining services (KWC area)</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Contractor account setup & bulk ordering</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Scheduled job site delivery across Ontario</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Product specifications & technical questions</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Emergency temporary fence (same-day KWC)</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="info-card">
|
||||||
|
<div className="ic-icon">📝</div>
|
||||||
|
<div className="ic-title">For the fastest response — include</div>
|
||||||
|
<div className="ic-rows">
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">The product or service you need</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Your job site city or location</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Approximate quantity or linear footage</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Your preferred delivery timeline</span></div>
|
||||||
|
<div className="ic-row"><span className="ic-val ic-val-list">Your company name and best contact method</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* WHAT TO EXPECT */}
|
||||||
|
<section className="expect-section">
|
||||||
|
<div className="section-eyebrow">What happens next</div>
|
||||||
|
<h2 className="sh">After you send your quote request.<br /><span>Here's our process.</span></h2>
|
||||||
|
<div className="expect-grid">
|
||||||
|
{[
|
||||||
|
{ num: "01", title: "We receive your request", desc: "Your quote request lands in our inbox immediately. We review every request personally — no bots, no auto-replies with prices." },
|
||||||
|
{ num: "02", title: "We check stock & delivery", desc: "We confirm current availability and delivery feasibility to your job site within our 250km radius. We quote only what we can actually deliver." },
|
||||||
|
{ num: "03", title: "You receive a written quote", desc: "Within 2 business hours, you receive a written quote with contractor pricing, delivery fee, and a confirmed delivery date. No vague estimates." },
|
||||||
|
{ num: "04", title: "Confirm & we deliver", desc: "Approve the quote, we confirm delivery. Materials arrive on schedule. For contractor accounts, repeat ordering is fast and pricing is pre-agreed." }
|
||||||
|
].map((step, idx) => (
|
||||||
|
<div className="expect-step" key={idx}>
|
||||||
|
<div className="es-num">{step.num}</div>
|
||||||
|
<div className="es-title">{step.title}</div>
|
||||||
|
<div className="es-desc">{step.desc}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* REASONS SECTION */}
|
||||||
|
<section className="reasons-section">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">How we can help</div>
|
||||||
|
<h2 className="sh">Every reason to<br /><span>get in touch.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="reasons-grid reveal">
|
||||||
|
<div className="reason-card">
|
||||||
|
<div className="reason-icon">
|
||||||
|
<svg viewBox="0 0 20 20" fill="none">
|
||||||
|
<rect x="2" y="3" width="3" height="14" rx="1.5" fill="#E8572A" opacity=".7"></rect>
|
||||||
|
<rect x="15" y="3" width="3" height="14" rx="1.5" fill="#E8572A" opacity=".7"></rect>
|
||||||
|
<line x1="2" y1="7" x2="18" y2="7" stroke="#E8572A" strokeWidth="1.5"></line>
|
||||||
|
<line x1="2" y1="11" x2="18" y2="11" stroke="#E8572A" strokeWidth="1.5"></line>
|
||||||
|
<line x1="8" y1="3" x2="8" y2="17" stroke="#E8572A" strokeWidth="1.2"></line>
|
||||||
|
<line x1="13" y1="3" x2="13" y2="17" stroke="#E8572A" strokeWidth="1.2"></line>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="reason-title">Contractor pricing</div>
|
||||||
|
<div className="reason-desc">Get trade pricing on chain link, ornamental, composite, glass railing, aluminum railing, Expert Stain & Seal, or Fence Armor. Send your quantities and job site location and we'll quote fast.</div>
|
||||||
|
<a href="mailto:info@vgfenceproducts.com?subject=Contractor Pricing Request" className="reason-email">Email for pricing →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="reason-card">
|
||||||
|
<div className="reason-icon">
|
||||||
|
<svg viewBox="0 0 20 20" fill="none">
|
||||||
|
<rect x="2" y="5" width="5" height="11" rx="1.5" fill="#E8572A" opacity=".6"></rect>
|
||||||
|
<rect x="13" y="5" width="5" height="11" rx="1.5" fill="#E8572A" opacity=".6"></rect>
|
||||||
|
<line x1="6" y1="8" x2="14" y2="8" stroke="#E8572A" strokeWidth="1.5"></line>
|
||||||
|
<line x1="6" y1="12" x2="14" y2="12" stroke="#E8572A" strokeWidth="1.5"></line>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="reason-title">Temporary fence rental</div>
|
||||||
|
<div className="reason-desc">Construction, demolition, events, renovations, and emergencies. Tell us your panel count, gate needs, site location, and how long you need it. Same-day available in KWC for emergencies.</div>
|
||||||
|
<a href="mailto:info@vgfenceproducts.com?subject=Temporary Fence Rental Request" className="reason-email">Email for rental →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="reason-card">
|
||||||
|
<div className="reason-icon">
|
||||||
|
<svg viewBox="0 0 20 20" fill="none">
|
||||||
|
<rect x="3" y="2" width="14" height="16" rx="2" stroke="#E8572A" strokeWidth="1.5" fill="none"></rect>
|
||||||
|
<path d="M7 7 L13 7 M7 10 L13 10 M7 13 L10 13" stroke="#E8572A" strokeWidth="1.2" strokeLinecap="round"></path>
|
||||||
|
<circle cx="14" cy="14" r="4" fill="#E8572A" opacity=".9"></circle>
|
||||||
|
<path d="M12.5 14 L13.5 15 L15.5 13" stroke="white" strokeWidth="1" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="reason-title">2D fence drawings</div>
|
||||||
|
<div className="reason-desc">CAD-based fence layout drawings, permit support drawings, and material takeoffs for residential and commercial projects. Share your site measurements or property plan and we'll produce a professional drawing.</div>
|
||||||
|
<a href="mailto:info@vgfenceproducts.com?subject=Fence Drawing Services Request" className="reason-email">Email for drawings →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="reason-card">
|
||||||
|
<div className="reason-icon">
|
||||||
|
<svg viewBox="0 0 20 20" fill="none">
|
||||||
|
<path d="M4 18 L10 2 L16 18" stroke="#E8572A" strokeWidth="2" fill="none" strokeLinecap="round"></path>
|
||||||
|
<circle cx="10" cy="11" r="2.5" fill="#E8572A" opacity=".5"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="reason-title">Wood staining services</div>
|
||||||
|
<div className="reason-desc">Professional fence staining, deck staining, pergola staining, and wood restoration in the KWC area and surrounding communities. Tell us your structure type and approximate size.</div>
|
||||||
|
<a href="mailto:info@vgfenceproducts.com?subject=Wood Staining Service Quote" className="reason-email">Email for staining →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="reason-card">
|
||||||
|
<div className="reason-icon">
|
||||||
|
<svg viewBox="0 0 20 20" fill="none">
|
||||||
|
<path d="M10 2 L18 6 L18 14 Q10 19 10 19 Q2 14 2 14 L2 6 Z" stroke="#E8572A" strokeWidth="1.5" fill="none" strokeLinejoin="round"></path>
|
||||||
|
<path d="M7 10 L9 12 L13 8" stroke="#E8572A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="reason-title">Contractor account setup</div>
|
||||||
|
<div className="reason-desc">Set up a contractor account for streamlined ordering, consistent contractor pricing, scheduled delivery arrangements, and bulk order discounts across all product lines.</div>
|
||||||
|
<a href="mailto:info@vgfenceproducts.com?subject=Contractor Account Application" className="reason-email">Email to apply →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="reason-card">
|
||||||
|
<div className="reason-icon">
|
||||||
|
<svg viewBox="0 0 20 20" fill="none">
|
||||||
|
<circle cx="10" cy="10" r="7" stroke="#E8572A" strokeWidth="1.5" fill="none"></circle>
|
||||||
|
<path d="M10 7 Q10 9 10 10" stroke="#E8572A" strokeWidth="2" strokeLinecap="round"></path>
|
||||||
|
<circle cx="10" cy="13" r="1" fill="#E8572A"></circle>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="reason-title">General enquiries</div>
|
||||||
|
<div className="reason-desc">Product specifications, availability, delivery schedules, service area questions, or anything else — send us an email and we'll help. We respond to every enquiry within 2 business hours.</div>
|
||||||
|
<a href="mailto:info@vgfenceproducts.com" className="reason-email">Send us an email →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* COVERAGE SECTION */}
|
||||||
|
<section className="coverage-section">
|
||||||
|
<div className="reveal visible">
|
||||||
|
<div className="se text-white-50">Where we deliver</div>
|
||||||
|
<h2 className="sh sh-white">We deliver across<br /><span>250km of Ontario.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="coverage-layout reveal visible">
|
||||||
|
{/* MAP SVG */}
|
||||||
|
<div className="coverage-map">
|
||||||
|
<svg viewBox="0 0 500 380" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="500" height="380" fill="#0a1a33" rx="8"></rect>
|
||||||
|
<circle cx="250" cy="200" r="170" fill="none" stroke="#1B3A6B" strokeWidth="1.5" strokeDasharray="8 5" opacity=".3"></circle>
|
||||||
|
<circle cx="250" cy="200" r="110" fill="none" stroke="#1B3A6B" strokeWidth="1" strokeDasharray="4 4" opacity=".2"></circle>
|
||||||
|
<circle cx="250" cy="200" r="50" fill="#1B3A6B" opacity=".15"></circle>
|
||||||
|
<text x="378" y="60" textAnchor="middle" fontSize="10" fill="#E8572A" fontFamily="'Barlow Condensed',sans-serif" fontWeight="700" letterSpacing="1">250KM</text>
|
||||||
|
<text x="378" y="72" textAnchor="middle" fontSize="9" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">RADIUS</text>
|
||||||
|
<circle cx="250" cy="200" r="10" fill="#E8572A"></circle>
|
||||||
|
<circle cx="250" cy="200" r="5" fill="white"></circle>
|
||||||
|
<circle cx="250" cy="200" r="24" fill="none" stroke="#E8572A" strokeWidth="1.5" opacity=".4"></circle>
|
||||||
|
<text x="250" y="232" textAnchor="middle" fontSize="11" fontWeight="700" fill="white" fontFamily="'Barlow Condensed',sans-serif" letterSpacing="1">KWC</text>
|
||||||
|
|
||||||
|
<circle cx="312" cy="158" r="5" fill="#1B3A6B" opacity=".8"></circle>
|
||||||
|
<text x="320" y="154" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.8)" fontFamily="sans-serif">Guelph</text>
|
||||||
|
<text x="320" y="164" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~30km</text>
|
||||||
|
|
||||||
|
<circle cx="340" cy="198" r="5" fill="#1B3A6B" opacity=".8"></circle>
|
||||||
|
<text x="350" y="194" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.8)" fontFamily="sans-serif">Hamilton</text>
|
||||||
|
<text x="350" y="204" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~75km</text>
|
||||||
|
|
||||||
|
<circle cx="385" cy="168" r="7" fill="#E8572A" opacity=".9"></circle>
|
||||||
|
<text x="395" y="164" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.9)" fontFamily="sans-serif">Toronto / GTA</text>
|
||||||
|
<text x="395" y="174" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~110km</text>
|
||||||
|
|
||||||
|
<circle cx="318" cy="238" r="5" fill="#1B3A6B" opacity=".8"></circle>
|
||||||
|
<text x="328" y="235" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.8)" fontFamily="sans-serif">Brantford</text>
|
||||||
|
<text x="328" y="245" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~50km</text>
|
||||||
|
|
||||||
|
<circle cx="188" cy="238" r="5" fill="#1B3A6B" opacity=".8"></circle>
|
||||||
|
<text x="100" y="236" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.8)" fontFamily="sans-serif">Stratford</text>
|
||||||
|
<text x="100" y="246" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~45km</text>
|
||||||
|
|
||||||
|
<circle cx="148" cy="200" r="7" fill="#E8572A" opacity=".9"></circle>
|
||||||
|
<text x="50" y="196" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.9)" fontFamily="sans-serif">London</text>
|
||||||
|
<text x="50" y="206" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~120km</text>
|
||||||
|
|
||||||
|
<circle cx="196" cy="150" r="5" fill="#1B3A6B" opacity=".6"></circle>
|
||||||
|
<text x="108" y="146" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.7)" fontFamily="sans-serif">Owen Sound</text>
|
||||||
|
<text x="108" y="156" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~175km</text>
|
||||||
|
|
||||||
|
<circle cx="250" cy="112" r="5" fill="#1B3A6B" opacity=".6"></circle>
|
||||||
|
<text x="258" y="108" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.7)" fontFamily="sans-serif">Barrie</text>
|
||||||
|
<text x="258" y="118" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~160km</text>
|
||||||
|
|
||||||
|
<circle cx="368" cy="242" r="5" fill="#1B3A6B" opacity=".6"></circle>
|
||||||
|
<text x="378" y="238" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.7)" fontFamily="sans-serif">Niagara</text>
|
||||||
|
<text x="378" y="248" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~150km</text>
|
||||||
|
|
||||||
|
<circle cx="90" cy="198" r="6" fill="#E8572A" opacity=".8"></circle>
|
||||||
|
<text x="20" y="194" fontSize="9" fontWeight="600" fill="rgba(255,255,255,.8)" fontFamily="sans-serif">Windsor</text>
|
||||||
|
<text x="20" y="204" fontSize="8" fill="rgba(255,255,255,.4)" fontFamily="sans-serif">~230km</text>
|
||||||
|
|
||||||
|
{/* Legend */}
|
||||||
|
<circle cx="25" cy="350" r="5" fill="#E8572A"></circle>
|
||||||
|
<text x="35" y="354" fontSize="8" fill="rgba(255,255,255,.5)" fontFamily="sans-serif">Major hub</text>
|
||||||
|
<circle cx="110" cy="350" r="4" fill="#1B3A6B" opacity=".8"></circle>
|
||||||
|
<text x="120" y="354" fontSize="8" fill="rgba(255,255,255,.5)" fontFamily="sans-serif">Regional city</text>
|
||||||
|
<line x1="200" y1="350" x2="230" y2="350" stroke="#1B3A6B" strokeWidth="1.5" strokeDasharray="5 4" opacity=".4"></line>
|
||||||
|
<text x="236" y="354" fontSize="8" fill="rgba(255,255,255,.5)" fontFamily="sans-serif">250km delivery</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* REGION LIST */}
|
||||||
|
<div>
|
||||||
|
<p className="coverage-intro">We deliver all fence products and materials to job sites and business locations across Southern Ontario within a 250km radius from our Kitchener–Waterloo base. Contact us with your location for a delivery quote.</p>
|
||||||
|
<div className="region-grid">
|
||||||
|
<div className="region-block">
|
||||||
|
<div className="region-name">Waterloo Region</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-kitchener">Kitchener</a></li>
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-waterloo">Waterloo</a></li>
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-cambridge">Cambridge</a></li>
|
||||||
|
<li>Ayr · Breslau · Elmira</li>
|
||||||
|
<li>New Hamburg · St. Jacobs</li>
|
||||||
|
<li>Baden · Wellesley</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="region-block">
|
||||||
|
<div className="region-name">Guelph & Wellington</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-guelph">Guelph</a></li>
|
||||||
|
<li>Fergus · Elora · Rockwood</li>
|
||||||
|
<li>Acton · Georgetown</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="region-block">
|
||||||
|
<div className="region-name">Halton & Hamilton</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-hamilton">Hamilton</a></li>
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-burlington">Burlington</a></li>
|
||||||
|
<li>Milton · Oakville</li>
|
||||||
|
<li><a href="https://vgfence.com/fence-supply-brantford">Brantford</a> · Paris</li>
|
||||||
|
<li>Stoney Creek · Grimsby</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="region-block">
|
||||||
|
<div className="region-name">GTA & Peel</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-mississauga">Mississauga</a></li>
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-brampton">Brampton</a></li>
|
||||||
|
<li><a href="https://vgfence.com/fence-supply-toronto">Toronto</a></li>
|
||||||
|
<li>Vaughan · Markham</li>
|
||||||
|
<li>Richmond Hill</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="region-block">
|
||||||
|
<div className="region-name">Oxford & Perth</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-woodstock">Woodstock</a></li>
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-stratford">Stratford</a></li>
|
||||||
|
<li>Ingersoll · Tillsonburg</li>
|
||||||
|
<li>St. Marys</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="region-block">
|
||||||
|
<div className="region-name">London & Elgin</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-london-ontario">London</a></li>
|
||||||
|
<li>St. Thomas · Strathroy</li>
|
||||||
|
<li>Komoka</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="region-block">
|
||||||
|
<div className="region-name">Southwest Ontario</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-windsor">Windsor</a></li>
|
||||||
|
<li className="primary">Chatham</li>
|
||||||
|
<li>Leamington · Sarnia</li>
|
||||||
|
<li>Petrolia</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="region-block">
|
||||||
|
<div className="region-name">Extended Service</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
<li className="primary">Niagara Falls</li>
|
||||||
|
<li>St. Catharines · Welland</li>
|
||||||
|
<li className="primary"><a href="https://vgfence.com/fence-supply-barrie">Barrie</a></li>
|
||||||
|
<li>Owen Sound · Collingwood</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FAQ */}
|
||||||
|
<section className="faq-section">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">Common questions</div>
|
||||||
|
<h2 className="sh">Before you reach out —<br /><span>quick answers.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="faq-grid reveal">
|
||||||
|
<div className="faq">
|
||||||
|
{[
|
||||||
|
{ q: "How quickly do you respond to quote requests?", a: "We respond to all quote requests and enquiries within 2 business hours during business hours (Monday–Friday 8:00 AM – 5:00 PM, Saturday 9:00 AM – 2:00 PM EST). Requests sent outside business hours are responded to first thing the next business day. For emergency temporary fence requirements in the KWC area, email us directly at info@vgfenceproducts.com for the fastest response." },
|
||||||
|
{ q: "Do you have a showroom or storefront I can visit?", a: "We are a B2B supplier operating from the Kitchener–Waterloo–Cambridge area. We do not operate a public retail storefront. All orders, quotes, and service bookings are handled by email and delivered directly to your job site or business location. Contact us at info@vgfenceproducts.com to arrange product pickup if needed." },
|
||||||
|
{ q: "What information should I include when requesting a quote?", a: "The more detail you provide, the faster and more accurate the quote. Ideal information to include: the product or service you need, your job site city or address, approximate quantity or linear footage, preferred delivery date, and your company name and contact details. For temporary fence rental, also include the number of gates needed, rental duration, and whether privacy screens are required." },
|
||||||
|
{ q: "Do you offer same-day temporary fence rental?", a: "Same-day temporary fence delivery and setup is available in the Kitchener–Waterloo–Cambridge area for urgent construction emergencies and emergency situations, subject to inventory and crew availability. Contact us directly by email as early as possible for same-day requests — we'll confirm availability immediately." }
|
||||||
|
].map((item, idx) => (
|
||||||
|
<div className={`faq-item ${openFaq === idx ? 'open' : ''}`} key={idx}>
|
||||||
|
<div className="faq-q" onClick={() => toggleFaq(idx)}>
|
||||||
|
{item.q} <span className="faq-icon">+</span>
|
||||||
|
</div>
|
||||||
|
<div className="faq-a">
|
||||||
|
{item.a}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="faq">
|
||||||
|
{[
|
||||||
|
{ q: "Do you deliver outside the KWC area?", a: "Yes. We deliver all fence products and materials to job sites and business locations across approximately 250km from our Kitchener–Waterloo base. This covers Guelph, Hamilton, Brantford, the GTA, London, Windsor, Niagara, Barrie, and everything in between. A delivery fee applies based on distance and order size. Contact us with your location and we'll confirm delivery availability and quote the cost." },
|
||||||
|
{ q: "How do I set up a contractor account?", a: "Email us at info@vgfenceproducts.com with your company name, your role (fence contractor, general contractor, builder, etc.), and the types of products you typically order. We'll set up your account, confirm your contractor pricing, and get you set up for streamlined ordering. Most contractor accounts are activated within 1 business day of application." },
|
||||||
|
{ q: "Do you supply to homeowners or only contractors?", a: "Our primary focus is the trade — fence contractors, general contractors, builders, and property managers. We do supply to homeowners on a case-by-case basis, particularly for Expert Stain & Seal products and Fence Armor post protection accessories. Minimum order quantities and pricing terms for homeowners may differ from contractor pricing. Contact us to discuss your specific project." },
|
||||||
|
{ q: "Can I order fence materials and a fence drawing at the same time?", a: "Yes — and we recommend it. When you order your fence layout drawing through us, we spec it to the exact products we carry. Once the drawing is approved, your material takeoff is built in and you can order materials in the same conversation. One supplier, one invoice, and materials spec'd to your layout with no gaps or guesswork." }
|
||||||
|
].map((item, idx) => {
|
||||||
|
const actualIdx = idx + 4;
|
||||||
|
return (
|
||||||
|
<div className={`faq-item ${openFaq === actualIdx ? 'open' : ''}`} key={actualIdx}>
|
||||||
|
<div className="faq-q" onClick={() => toggleFaq(actualIdx)}>
|
||||||
|
{item.q} <span className="faq-icon">+</span>
|
||||||
|
</div>
|
||||||
|
<div className="faq-a">
|
||||||
|
{item.a}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CTA SECTION */}
|
||||||
|
<section className="cta-section">
|
||||||
|
<div className="cta-inner">
|
||||||
|
<h2 className="cta-h2">READY TO ORDER?<br />EMAIL US NOW.</h2>
|
||||||
|
<p className="cta-sub">Contractor pricing · 250km Ontario delivery · Response within 2 business hours</p>
|
||||||
|
<a href="mailto:info@vgfenceproducts.com" className="cta-btn">
|
||||||
|
<span className="cta-btn-icon">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" />
|
||||||
|
<polyline points="22,6 12,13 2,6" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
info@vgfenceproducts.com
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* NEWSLETTER */}
|
||||||
|
<section className="newsletter-section">
|
||||||
|
<div className="nl-inner">
|
||||||
|
<div className="nl-eye">Stay in the loop</div>
|
||||||
|
<h2 className="nl-h2">Product updates &<br /><span>contractor deals.</span></h2>
|
||||||
|
<p className="nl-sub">New products, seasonal promotions, and industry tips — delivered to your inbox. No spam, unsubscribe anytime.</p>
|
||||||
|
<div className="nl-form">
|
||||||
|
<input className="nl-input" type="email" placeholder="Enter your email address" id="nl-input" />
|
||||||
|
<button className="nl-btn" onClick={() => {
|
||||||
|
const input = document.getElementById('nl-input') as HTMLInputElement;
|
||||||
|
if (input && input.value.includes('@')) {
|
||||||
|
const btn = document.querySelector('.nl-btn') as HTMLElement;
|
||||||
|
btn.textContent = 'Subscribed ✓';
|
||||||
|
btn.style.background = '#16A34A';
|
||||||
|
input.value = '';
|
||||||
|
input.placeholder = 'Thank you — you\'re subscribed!';
|
||||||
|
}
|
||||||
|
}}>Subscribe →</button>
|
||||||
|
</div>
|
||||||
|
<p className="nl-note">Join contractors and builders across Ontario · 1–6 emails per month</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import type { Metadata } from "next";
|
|||||||
import ContactClient from "./ContactClient";
|
import ContactClient from "./ContactClient";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Contact Us | VG Fence Products",
|
title: "Contact VG Fence Products | Kitchener–Waterloo Ontario",
|
||||||
description: "Get in touch with our expert team for quotes, product inquiries, or technical support. We are located in Ayr, Ontario and serve the entire province.",
|
description: "Contact VG Fence Products for contractor pricing, fence supply, temp fence rental, wood staining, and 2D drawing services. Email info@vgfenceproducts.com. Response within 2 business hours.",
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: "/contact"
|
canonical: "/contact"
|
||||||
}
|
}
|
||||||
|
|||||||
6251
app/globals.css
6251
app/globals.css
File diff suppressed because it is too large
Load Diff
@ -3,6 +3,8 @@ import { Barlow, Barlow_Condensed } from "next/font/google";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import Navbar from "@/components/Navbar";
|
import Navbar from "@/components/Navbar";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
|
import ConstructionPopup from "@/components/ConstructionPopup";
|
||||||
|
|
||||||
|
|
||||||
const barlow = Barlow({
|
const barlow = Barlow({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
@ -18,8 +20,8 @@ const barlowCondensed = Barlow_Condensed({
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL("https://vgfenceproducts.ca"),
|
metadataBase: new URL("https://vgfenceproducts.ca"),
|
||||||
title: "VG Fence Products — Ontario's B2B Fence Supply Partner",
|
title: "VG Fence Products | B2B Fence Supply Ontario — KWC",
|
||||||
description: "Supplying contractors, builders, and property managers across Ontario with chain link, ornamental, composite, glass railing, and stain products.",
|
description: "B2B fence supply Ontario. Chain link, ornamental, glass railing, Expert Stain & Seal, temp fence rental. Contractor pricing. 250km from Kitchener–Waterloo.",
|
||||||
icons: {
|
icons: {
|
||||||
icon: "/assets/favicon.webp",
|
icon: "/assets/favicon.webp",
|
||||||
},
|
},
|
||||||
@ -41,6 +43,8 @@ export default function RootLayout({
|
|||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
<ConstructionPopup />
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,18 +6,12 @@ import Image from 'next/image';
|
|||||||
export default function ManufacturingPage() {
|
export default function ManufacturingPage() {
|
||||||
return (
|
return (
|
||||||
<div className="about-page-wrapper">
|
<div className="about-page-wrapper">
|
||||||
{/* Inner Banner */}
|
<section className="inner-banner">
|
||||||
<section className="inner-banner fade-up">
|
<h1 className="inner-banner-title">Job Order <span>Manufacturing.</span></h1>
|
||||||
<div className="inner-banner-content">
|
<div className="inner-banner-breadcrumbs">
|
||||||
<h1 className="section-h2">Job <span>Order</span> Manufacturing</h1>
|
<Link href="/">Home</Link>
|
||||||
<div className="banner-breadcrumb" style={{ marginTop: '30px', marginBottom: '0' }}>
|
<span>/</span>
|
||||||
<Link href="/">
|
Manufacturing
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
|
||||||
Home
|
|
||||||
</Link>
|
|
||||||
<span className="separator">/</span>
|
|
||||||
<span>Manufacturing</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
34
app/page.tsx
34
app/page.tsx
@ -1,4 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
import Hero from "@/components/Hero";
|
import Hero from "@/components/Hero";
|
||||||
import TrustBar from "@/components/TrustBar";
|
import TrustBar from "@/components/TrustBar";
|
||||||
import Products from "@/components/Products";
|
import Products from "@/components/Products";
|
||||||
@ -7,26 +9,38 @@ import Territory from "@/components/Territory";
|
|||||||
import WhoWeServe from "@/components/WhoWeServe";
|
import WhoWeServe from "@/components/WhoWeServe";
|
||||||
import StainPromo from "@/components/StainPromo";
|
import StainPromo from "@/components/StainPromo";
|
||||||
import CTA from "@/components/CTA";
|
import CTA from "@/components/CTA";
|
||||||
|
import Newsletter from "@/components/Newsletter";
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: "VG Fence Products | Ontario's B2B Fence Supply Partner",
|
|
||||||
description: "Premier supplier of professional-grade fencing and railing materials for contractors, builders, and property managers across Ontario.",
|
|
||||||
alternates: {
|
|
||||||
canonical: "/"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
useEffect(() => {
|
||||||
|
const reveal = () => {
|
||||||
|
const reveals = document.querySelectorAll('.reveal');
|
||||||
|
for (let i = 0; i < reveals.length; i++) {
|
||||||
|
const windowheight = window.innerHeight;
|
||||||
|
const revealtop = reveals[i].getBoundingClientRect().top;
|
||||||
|
const revealpoint = 150;
|
||||||
|
if (revealtop < windowheight - revealpoint) {
|
||||||
|
reveals[i].classList.add('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', reveal);
|
||||||
|
reveal(); // Initial call
|
||||||
|
return () => window.removeEventListener('scroll', reveal);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Hero />
|
<Hero />
|
||||||
<TrustBar />
|
<TrustBar />
|
||||||
<Products />
|
<div id="products"><Products /></div>
|
||||||
<Services />
|
<Services />
|
||||||
<Territory />
|
<Territory />
|
||||||
<WhoWeServe />
|
<WhoWeServe />
|
||||||
<StainPromo />
|
<StainPromo />
|
||||||
<CTA />
|
<CTA />
|
||||||
|
<Newsletter />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
18
app/products/chain-link-fence/layout.tsx
Normal file
18
app/products/chain-link-fence/layout.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Chain Link Fence Supply | KWC Ontario | VG Fence Products",
|
||||||
|
description: "Chain link fence supply — galvanized and black vinyl, all gauges. Posts, gates, hardware in stock. Contractor pricing. 250km delivery from KWC Ontario.",
|
||||||
|
keywords: "chain link fence Ontario, galvanized fence supply, black vinyl chain link, fence posts Ontario, fence gates supply, contractor pricing fence, VG Fence Products",
|
||||||
|
alternates: {
|
||||||
|
canonical: "/products/chain-link-fence",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ChainLinkLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
55
app/products/chain-link-fence/page.tsx
Normal file
55
app/products/chain-link-fence/page.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import ChainLinkHero from '@/components/ChainLinkFence/ChainLinkHero';
|
||||||
|
import ChainLinkOverview from '@/components/ChainLinkFence/ChainLinkOverview';
|
||||||
|
import ChainLinkMaterials from '@/components/ChainLinkFence/ChainLinkMaterials';
|
||||||
|
import ChainLinkFinishes from '@/components/ChainLinkFence/ChainLinkFinishes';
|
||||||
|
import ChainLinkCompare from '@/components/ChainLinkFence/ChainLinkCompare';
|
||||||
|
import ChainLinkSeoContent from '@/components/ChainLinkFence/ChainLinkSeoContent';
|
||||||
|
import ChainLinkTerritory from '@/components/ChainLinkFence/ChainLinkTerritory';
|
||||||
|
import ChainLinkQuote from '@/components/ChainLinkFence/ChainLinkQuote';
|
||||||
|
|
||||||
|
export default function ChainLinkFencePage() {
|
||||||
|
useEffect(() => {
|
||||||
|
const reveal = () => {
|
||||||
|
const reveals = document.querySelectorAll('.reveal');
|
||||||
|
for (let i = 0; i < reveals.length; i++) {
|
||||||
|
const windowheight = window.innerHeight;
|
||||||
|
const revealtop = reveals[i].getBoundingClientRect().top;
|
||||||
|
const revealpoint = 150;
|
||||||
|
if (revealtop < windowheight - revealpoint) {
|
||||||
|
reveals[i].classList.add('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', reveal);
|
||||||
|
reveal(); // Initial call
|
||||||
|
return () => window.removeEventListener('scroll', reveal);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="chain-link-page">
|
||||||
|
{/* BREADCRUMB */}
|
||||||
|
<section className="inner-banner">
|
||||||
|
<h1 className="inner-banner-title">Chain Link <span>Fence.</span></h1>
|
||||||
|
<div className="inner-banner-breadcrumbs">
|
||||||
|
<Link href="/">Home</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<Link href="/products">Products</Link>
|
||||||
|
<span>/</span>
|
||||||
|
Chain Link
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<ChainLinkOverview />
|
||||||
|
<ChainLinkMaterials />
|
||||||
|
<ChainLinkFinishes />
|
||||||
|
<ChainLinkCompare />
|
||||||
|
<ChainLinkSeoContent />
|
||||||
|
<ChainLinkTerritory />
|
||||||
|
<ChainLinkQuote />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -9,6 +9,8 @@ export const metadata: Metadata = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default function ProductsPage() {
|
export default function ProductsPage() {
|
||||||
return <ProductsClient />;
|
redirect("/products/chain-link-fence");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,17 +28,12 @@ export default function RentalsPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="rentals-page">
|
<div className="rentals-page">
|
||||||
{/* ── INNER BANNER ── */}
|
{/* ── INNER BANNER ── */}
|
||||||
<section className="inner-banner fade-up">
|
<section className="inner-banner">
|
||||||
<div className="inner-banner-content">
|
<h1 className="inner-banner-title">Fence <span>Rentals.</span></h1>
|
||||||
<h1 className="section-h2">Fence <span>Rentals</span></h1>
|
<div className="inner-banner-breadcrumbs">
|
||||||
<div className="banner-breadcrumb" style={{ marginTop: '30px', marginBottom: '0' }}>
|
<Link href="/">Home</Link>
|
||||||
<Link href="/">
|
<span>/</span>
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
Rentals
|
||||||
Home
|
|
||||||
</Link>
|
|
||||||
<span className="separator">/</span>
|
|
||||||
<span>Rentals</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
18
app/services/layout.tsx
Normal file
18
app/services/layout.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Fence & Deck Staining Service | KWC Ontario | VG Fence",
|
||||||
|
description: "Professional fence staining, deck staining, and wood restoration using Expert Stain & Seal. Cedar, pressure treated, and all wood types. Serving KWC and surrounding Ontario regions.",
|
||||||
|
keywords: "fence staining Kitchener, deck staining Waterloo, wood staining service KWC, fence staining service Ontario, deck staining Cambridge",
|
||||||
|
alternates: {
|
||||||
|
canonical: "/services",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ServicesLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
200
app/services/page.tsx
Normal file
200
app/services/page.tsx
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import './services.css';
|
||||||
|
|
||||||
|
import StainingHero from '@/components/Staining/StainingHero';
|
||||||
|
import ModelNav from '@/components/Staining/ModelNav';
|
||||||
|
import StainingIntro from '@/components/Staining/StainingIntro';
|
||||||
|
import StainingServiceSection from '@/components/Staining/StainingServiceSection';
|
||||||
|
import StainingProcess from '@/components/Staining/StainingProcess';
|
||||||
|
import WoodConditionGuide from '@/components/Staining/WoodConditionGuide';
|
||||||
|
import StainingFAQ from '@/components/Staining/StainingFAQ';
|
||||||
|
import StainingTerritory from '@/components/Staining/StainingTerritory';
|
||||||
|
import StainingQuote from '@/components/Staining/StainingQuote';
|
||||||
|
|
||||||
|
export default function ServicesPage() {
|
||||||
|
useEffect(() => {
|
||||||
|
const reveal = () => {
|
||||||
|
const reveals = document.querySelectorAll('.reveal');
|
||||||
|
for (let i = 0; i < reveals.length; i++) {
|
||||||
|
const windowheight = window.innerHeight;
|
||||||
|
const revealtop = reveals[i].getBoundingClientRect().top;
|
||||||
|
const revealpoint = 150;
|
||||||
|
if (revealtop < windowheight - revealpoint) {
|
||||||
|
reveals[i].classList.add('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', reveal);
|
||||||
|
reveal(); // Initial call
|
||||||
|
return () => window.removeEventListener('scroll', reveal);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="services-page">
|
||||||
|
<section className="inner-banner">
|
||||||
|
<h1 className="inner-banner-title">Our <span>Services.</span></h1>
|
||||||
|
<div className="inner-banner-breadcrumbs">
|
||||||
|
<Link href="/">Home</Link>
|
||||||
|
<span>/</span>
|
||||||
|
Services
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<ModelNav />
|
||||||
|
<StainingIntro />
|
||||||
|
|
||||||
|
<StainingServiceSection
|
||||||
|
id="fence-staining"
|
||||||
|
serviceNum="01"
|
||||||
|
title="Fence<br>Staining"
|
||||||
|
tagline="Cedar, pressure treated & wood fence — new & existing"
|
||||||
|
description="A properly stained wood fence resists moisture penetration, UV fading, mould growth, and the surface cracking that makes wood look aged and rough in just a few seasons. We stain new fences before the grain seals up and grey sets in — and we restore older fences back to a rich, protected finish before applying fresh stain."
|
||||||
|
bgColor="bg-white"
|
||||||
|
image="/images/staining-hero.png" // Using hero image for fence as it's a fence
|
||||||
|
photoLabel="Fence staining — before & after"
|
||||||
|
photoSub1="Cedar fence — new stain"
|
||||||
|
photoSub2="Stain detail close-up"
|
||||||
|
specsTitle1="What's included"
|
||||||
|
specs1={[
|
||||||
|
{ key: "Pre-job prep inspection", val: "Always included", isOrange: true },
|
||||||
|
{ key: "Clean & Bright wash", val: "Included on all restoration work", isOrange: true },
|
||||||
|
{ key: "Masking of surrounding surfaces", val: "Gates, posts, plants, structures" },
|
||||||
|
{ key: "Expert Stain & Seal application", val: "1–2 coats as required" },
|
||||||
|
{ key: "Post-job walkthrough", val: "Always included" }
|
||||||
|
]}
|
||||||
|
specsTitle2="Wood types we stain"
|
||||||
|
specs2={[
|
||||||
|
{ key: "Cedar fence", val: "Most common — excellent absorption" },
|
||||||
|
{ key: "Pressure treated", val: "After adequate drying time" },
|
||||||
|
{ key: "Pine & spruce", val: "Privacy and picket fences" },
|
||||||
|
{ key: "Aged & weathered wood", val: "Restoration prep required" }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StainingServiceSection
|
||||||
|
id="deck-staining"
|
||||||
|
serviceNum="02"
|
||||||
|
title="Deck<br>Staining"
|
||||||
|
tagline="Boards, railings, stairs & fascia — new & restoration"
|
||||||
|
description="Your deck takes more sun, foot traffic, and weather exposure than almost any other outdoor wood surface. Without the right stain applied at the right time, deck boards grey out, check, and splinter faster than they should. We stain deck boards, railings, stairs, and fascia as a complete package — not just the flat boards — so the entire structure is uniformly protected."
|
||||||
|
bgColor="bg-gray"
|
||||||
|
image="/images/stained-deck.png"
|
||||||
|
reverse={true}
|
||||||
|
photoLabel="Deck staining — before & after"
|
||||||
|
photoSub1="Deck boards — new finish"
|
||||||
|
photoSub2="Railing & stairs detail"
|
||||||
|
specsTitle1="Deck staining scope"
|
||||||
|
specs1={[
|
||||||
|
{ key: "Deck boards", val: "Primary surface — full coverage", isOrange: true },
|
||||||
|
{ key: "Deck railings", val: "Posts, rails, balusters" },
|
||||||
|
{ key: "Stairs & stringers", val: "Treads and risers" },
|
||||||
|
{ key: "Fascia boards", val: "Perimeter framing" },
|
||||||
|
{ key: "Ledger & exposed framing", val: "On request" }
|
||||||
|
]}
|
||||||
|
specsTitle2="Product used"
|
||||||
|
specs2={[
|
||||||
|
{ key: "Prep product", val: "Expert Stain & Seal Clean & Bright", isOrange: true },
|
||||||
|
{ key: "Stain product", val: "Expert Stain & Seal — oil-based", isOrange: true },
|
||||||
|
{ key: "UV protection", val: "Included in stain" },
|
||||||
|
{ key: "Moisture sealing", val: "Included in stain" }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StainingServiceSection
|
||||||
|
id="structures"
|
||||||
|
serviceNum="03"
|
||||||
|
title="Pergolas &<br>Structures"
|
||||||
|
tagline="Pergolas, gazebos, log cabins & cedar siding"
|
||||||
|
description="Pergolas and outdoor structures are often the last thing on a homeowner's maintenance list — and the first thing that shows its age. Sun exposure degrades unprotected wood fast, especially on horizontal surfaces like pergola rafters that collect water and hold UV damage year-round. We stain and seal all outdoor wood structures to restore appearance and stop the deterioration cycle."
|
||||||
|
bgColor="bg-navy"
|
||||||
|
image="/images/stained-pergola.png"
|
||||||
|
isDark={true}
|
||||||
|
photoLabel="Pergola & structure staining"
|
||||||
|
photoSub1="Log cabin exterior"
|
||||||
|
photoSub2="Cedar siding detail"
|
||||||
|
specsTitle1="Structures we stain"
|
||||||
|
specs1={[
|
||||||
|
{ key: "Pergolas", val: "Rafters, posts, beams, cross members", isOrange: true },
|
||||||
|
{ key: "Gazebos", val: "All exposed wood surfaces" },
|
||||||
|
{ key: "Log cabins", val: "Exterior log and chink surfaces" },
|
||||||
|
{ key: "Cedar siding", val: "Horizontal and vertical board" },
|
||||||
|
{ key: "Garden structures", val: "Sheds, trellises, arbors" }
|
||||||
|
]}
|
||||||
|
specsTitle2="Specialist care"
|
||||||
|
specs2={[
|
||||||
|
{ key: "Checking assessment", val: "Log home cracks" },
|
||||||
|
{ key: "End grain sealing", val: "Critical for logs" },
|
||||||
|
{ key: "Chinking inspection", val: "Prior to application" }
|
||||||
|
]}
|
||||||
|
extraInfo={{
|
||||||
|
title: "Log cabins — specialist service",
|
||||||
|
desc: "Log and timber homes require particular care — checking (cracking), end grain exposure, and chinking gaps all need to be addressed before staining. We assess log home surfaces before quoting to ensure the stain application will hold and perform as it should in the Ontario climate."
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StainingServiceSection
|
||||||
|
id="pre-staining"
|
||||||
|
serviceNum="04"
|
||||||
|
title="Pre-Staining"
|
||||||
|
tagline="Stain before installation — every side, every board"
|
||||||
|
description="The smartest way to protect a new fence is to stain the boards before a single one goes into the ground. Pre-staining lets us coat all four sides of every picket and rail — including the back face, bottom end grain, and edges that are impossible to reach once the fence is built. End grain is where moisture enters first. Pre-staining seals it before it ever gets a chance."
|
||||||
|
bgColor="bg-cream"
|
||||||
|
reverse={true}
|
||||||
|
photoLabel="Pre-staining fence boards"
|
||||||
|
photoSub1="Board edge coating"
|
||||||
|
photoSub2="Pre-stained & ready to install"
|
||||||
|
specsTitle1="Why pre-stain?"
|
||||||
|
specs1={[
|
||||||
|
{ key: "All 4 sides coated", val: "Including back & end grain", isOrange: true },
|
||||||
|
{ key: "End grain sealed", val: "Primary moisture entry point", isOrange: true },
|
||||||
|
{ key: "Protection from day one", val: "Before weathering begins" },
|
||||||
|
{ key: "Longer service life", val: "Fewer recoat intervals" },
|
||||||
|
{ key: "Cleaner finish result", val: "No masking, no overspray risk" }
|
||||||
|
]}
|
||||||
|
specsTitle2="How it works"
|
||||||
|
specs2={[
|
||||||
|
{ key: "Step 1", val: "Boards delivered to us pre-install" },
|
||||||
|
{ key: "Step 2", val: "All sides stained and dried" },
|
||||||
|
{ key: "Step 3", val: "Boards returned for installation" },
|
||||||
|
{ key: "Touch-up", val: "Cut ends touched up post-install" }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StainingServiceSection
|
||||||
|
id="restoration"
|
||||||
|
serviceNum="05"
|
||||||
|
title="Wood<br>Restoration"
|
||||||
|
tagline="Weathered, grey & neglected wood brought back to life"
|
||||||
|
description="Grey wood isn't dead wood — it's wood that hasn't been maintained. In most cases, a proper clean and brightening treatment with Expert Stain & Seal Clean & Bright removes years of weathering, pulls the grey out of the surface, and restores the open grain that allows fresh stain to penetrate and bond. The result can be dramatic — wood that looked ready to replace looking rich and fresh again."
|
||||||
|
bgColor="bg-white"
|
||||||
|
photoLabel="Wood restoration — before & after"
|
||||||
|
photoSub1="Grey wood before Clean & Bright"
|
||||||
|
photoSub2="After restoration staining"
|
||||||
|
specsTitle1="Restoration process"
|
||||||
|
specs1={[
|
||||||
|
{ key: "Assessment", val: "Inspect for rot, structural damage", isOrange: true },
|
||||||
|
{ key: "Clean & Bright wash", val: "Remove grey, mildew, residue", isOrange: true },
|
||||||
|
{ key: "Dry & assess", val: "Confirm surface is stain-ready" },
|
||||||
|
{ key: "Expert Stain & Seal", val: "Applied to clean, open grain" },
|
||||||
|
{ key: "Final walkthrough", val: "Review with the client" }
|
||||||
|
]}
|
||||||
|
specsTitle2="Restoration candidates"
|
||||||
|
specs2={[
|
||||||
|
{ key: "Grey weathered fence boards", val: "Most common restoration job" },
|
||||||
|
{ key: "Mildewed deck boards", val: "Clean & Bright removes growth" },
|
||||||
|
{ key: "Previously stained surfaces", val: "Old finish removal included" },
|
||||||
|
{ key: "Neglected structures", val: "Pergolas, gazebos, sheds" }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StainingProcess />
|
||||||
|
<WoodConditionGuide />
|
||||||
|
<StainingFAQ />
|
||||||
|
<StainingTerritory />
|
||||||
|
<StainingQuote />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
958
app/services/services.css
Normal file
958
app/services/services.css
Normal file
@ -0,0 +1,958 @@
|
|||||||
|
/* SERVICES PAGE SPECIFIC STYLES */
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
background: var(--gray-100);
|
||||||
|
padding: 12px 80px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 64px;
|
||||||
|
border-bottom: 1px solid var(--gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb a {
|
||||||
|
color: var(--navy-mid);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb a:hover {
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb span {
|
||||||
|
color: var(--gray-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HERO */
|
||||||
|
.hero-staining {
|
||||||
|
background: var(--navy);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 72px 80px 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-grid-bg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: .035;
|
||||||
|
background-image: repeating-linear-gradient(0deg, transparent, transparent 39px, rgba(255, 255, 255, .6) 39px, rgba(255, 255, 255, .6) 40px), repeating-linear-gradient(90deg, transparent, transparent 39px, rgba(255, 255, 255, .6) 39px, rgba(255, 255, 255, .6) 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-wood-deco {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 42%;
|
||||||
|
opacity: .06;
|
||||||
|
background-image: repeating-linear-gradient(-10deg, transparent 0px, transparent 12px, rgba(255, 255, 255, .7) 12px, rgba(255, 255, 255, .7) 14px, transparent 14px, transparent 30px, rgba(255, 255, 255, .4) 30px, rgba(255, 255, 255, .4) 32px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-accent-circle {
|
||||||
|
position: absolute;
|
||||||
|
right: -80px;
|
||||||
|
bottom: -80px;
|
||||||
|
width: 360px;
|
||||||
|
height: 360px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 56px solid var(--orange);
|
||||||
|
opacity: .1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-inner {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
max-width: 760px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-eyebrow {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--orange);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-eyebrow::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 28px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-staining h1 {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: clamp(44px, 5.5vw, 80px);
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: .92;
|
||||||
|
color: var(--white);
|
||||||
|
letter-spacing: -.01em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-staining h1 em {
|
||||||
|
color: var(--orange);
|
||||||
|
font-style: normal;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-desc {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.75;
|
||||||
|
color: rgba(255, 255, 255, .65);
|
||||||
|
max-width: 600px;
|
||||||
|
margin-bottom: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-desc strong {
|
||||||
|
color: rgba(255, 255, 255, .9);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-badges {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .07em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 7px 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, .2);
|
||||||
|
color: rgba(255, 255, 255, .8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-fill {
|
||||||
|
background: var(--orange);
|
||||||
|
border-color: var(--orange);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-stats-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 48px;
|
||||||
|
padding-top: 32px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JUMP NAV */
|
||||||
|
.model-nav {
|
||||||
|
background: var(--white);
|
||||||
|
border-bottom: 2px solid var(--gray-200);
|
||||||
|
padding: 0 80px;
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 64px;
|
||||||
|
z-index: 90;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-nav-item {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .07em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--gray-600);
|
||||||
|
padding: 16px 22px;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color .2s, border-color .2s;
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-nav-item:hover {
|
||||||
|
color: var(--orange);
|
||||||
|
border-bottom-color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-nav-item.active {
|
||||||
|
color: var(--navy);
|
||||||
|
border-bottom-color: var(--navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* INTRO */
|
||||||
|
.intro {
|
||||||
|
padding: 72px 80px 64px;
|
||||||
|
background: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 64px;
|
||||||
|
align-items: start;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text p {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--gray-600);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-text p strong {
|
||||||
|
color: var(--gray-800);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-list {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-list li {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-list li::before {
|
||||||
|
content: '✓';
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(232, 87, 42, .12);
|
||||||
|
color: var(--orange);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-cards {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hcard {
|
||||||
|
background: var(--gray-100);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 18px 22px;
|
||||||
|
border-left: 3px solid var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hcard-title {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--navy);
|
||||||
|
letter-spacing: .02em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hcard-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SERVICE SECTIONS */
|
||||||
|
.model-section {
|
||||||
|
padding: 80px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-section.bg-white {
|
||||||
|
background: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-section.bg-gray {
|
||||||
|
background: var(--gray-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-section.bg-navy {
|
||||||
|
background: var(--navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-section.bg-cream {
|
||||||
|
background: var(--cream);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--orange);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-label::before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
width: 20px;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-label.dim {
|
||||||
|
color: rgba(255, 255, 255, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-label.dim::before {
|
||||||
|
background: rgba(255, 255, 255, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-name {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: clamp(36px, 5vw, 64px);
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--navy);
|
||||||
|
letter-spacing: -.02em;
|
||||||
|
line-height: .9;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-name.white {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-tagline {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .06em;
|
||||||
|
color: var(--orange);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-desc {
|
||||||
|
font-size: 15px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
line-height: 1.8;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
max-width: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-desc.white {
|
||||||
|
color: rgba(255, 255, 255, .65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-accent-strip {
|
||||||
|
height: 4px;
|
||||||
|
background: var(--orange);
|
||||||
|
border-radius: 2px;
|
||||||
|
width: 48px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-area {
|
||||||
|
background: var(--gray-200);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px dashed var(--gray-400);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 300px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-area.dark-placeholder {
|
||||||
|
background: rgba(255, 255, 255, .05);
|
||||||
|
border-color: rgba(255, 255, 255, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-icon {
|
||||||
|
opacity: .35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-label {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: .08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--gray-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-sub {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--gray-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-area.dark-placeholder .photo-label {
|
||||||
|
color: rgba(255, 255, 255, .35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-area.dark-placeholder .photo-sub {
|
||||||
|
color: rgba(255, 255, 255, .25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 48px;
|
||||||
|
align-items: start;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-layout.reverse {
|
||||||
|
direction: rtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-layout.reverse>* {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-row-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specs-block {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specs-title {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--gray-400);
|
||||||
|
margin-bottom: 14px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid var(--gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.specs-title.white {
|
||||||
|
color: rgba(255, 255, 255, .35);
|
||||||
|
border-bottom-color: rgba(255, 255, 255, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 0;
|
||||||
|
border-bottom: 1px solid var(--gray-100);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-row.white-row {
|
||||||
|
border-bottom-color: rgba(255, 255, 255, .08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-key {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-key.white {
|
||||||
|
color: rgba(255, 255, 255, .55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-val {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--navy);
|
||||||
|
letter-spacing: .02em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-val.white {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-val.orange {
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PROCESS */
|
||||||
|
.process-section {
|
||||||
|
padding: 80px;
|
||||||
|
background: var(--navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-num {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 52px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--orange);
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-title {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--white);
|
||||||
|
letter-spacing: .03em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(255, 255, 255, .55);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WOOD CONDITION CARDS */
|
||||||
|
.condition-section {
|
||||||
|
padding: 80px;
|
||||||
|
background: var(--cream);
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-card {
|
||||||
|
background: var(--white);
|
||||||
|
border: 1px solid var(--gray-200);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 24px 20px;
|
||||||
|
transition: border-color .2s, transform .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-card:hover {
|
||||||
|
border-color: var(--orange);
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-tag {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-new {
|
||||||
|
background: rgba(15, 36, 68, .08);
|
||||||
|
color: var(--navy-mid);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-recent {
|
||||||
|
background: rgba(42, 82, 152, .1);
|
||||||
|
color: var(--navy-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-aged {
|
||||||
|
background: rgba(232, 87, 42, .12);
|
||||||
|
color: var(--orange-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-damaged {
|
||||||
|
background: rgba(232, 87, 42, .2);
|
||||||
|
color: var(--orange-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-title {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--navy);
|
||||||
|
letter-spacing: .02em;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-desc {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
line-height: 1.65;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.condition-action {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .05em;
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FAQ + SEO */
|
||||||
|
.faq-section {
|
||||||
|
padding: 80px;
|
||||||
|
background: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-seo-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 64px;
|
||||||
|
margin-top: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item {
|
||||||
|
border-bottom: 1px solid var(--gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-q {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--navy);
|
||||||
|
letter-spacing: .02em;
|
||||||
|
padding: 18px 0;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
transition: color .2s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-q:hover {
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-icon {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(232, 87, 42, .1);
|
||||||
|
color: var(--orange);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: transform .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item.open .faq-icon {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-a {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
line-height: 1.75;
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height .35s ease, padding .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faq-item.open .faq-a {
|
||||||
|
max-height: 500px;
|
||||||
|
padding-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-block h3 {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--navy);
|
||||||
|
letter-spacing: .03em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-top: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-block h3:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-block p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
line-height: 1.8;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TERRITORY RE-STYLING FOR SERVICE PAGE */
|
||||||
|
.territory-service {
|
||||||
|
padding: 80px;
|
||||||
|
background: var(--navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.territory-service .sh-white {
|
||||||
|
color: var(--white);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.territory-intro {
|
||||||
|
font-size: 15px;
|
||||||
|
color: rgba(255, 255, 255, .6);
|
||||||
|
line-height: 1.7;
|
||||||
|
max-width: 600px;
|
||||||
|
margin-top: 14px;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-block {
|
||||||
|
background: rgba(255, 255, 255, .05);
|
||||||
|
border: 1px solid rgba(255, 255, 255, .1);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
transition: background .2s, border-color .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-block:hover {
|
||||||
|
background: rgba(232, 87, 42, .1);
|
||||||
|
border-color: rgba(232, 87, 42, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-name {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--orange);
|
||||||
|
letter-spacing: .06em;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-cities {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-cities li {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, .55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-cities li.primary {
|
||||||
|
color: rgba(255, 255, 255, .9);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-cities a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-cities a:hover {
|
||||||
|
color: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QUOTE CTA */
|
||||||
|
.quote-cta {
|
||||||
|
padding: 80px;
|
||||||
|
background: var(--orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-inner {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 64px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-left h2 {
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: clamp(38px, 4vw, 60px);
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--white);
|
||||||
|
line-height: .92;
|
||||||
|
letter-spacing: -.01em;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-left p {
|
||||||
|
font-size: 16px;
|
||||||
|
color: rgba(255, 255, 255, .8);
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quote-form-card {
|
||||||
|
background: rgba(255, 255, 255, .12);
|
||||||
|
border: 1px solid rgba(255, 255, 255, .2);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: .08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: rgba(255, 255, 255, .6);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qi {
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(255, 255, 255, .15);
|
||||||
|
border: 1px solid rgba(255, 255, 255, .25);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 11px 14px;
|
||||||
|
font-family: var(--fb);
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--white);
|
||||||
|
outline: none;
|
||||||
|
transition: border-color .2s;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qi::placeholder {
|
||||||
|
color: rgba(255, 255, 255, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qi:focus {
|
||||||
|
border-color: rgba(255, 255, 255, .7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qbtn {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--white);
|
||||||
|
color: var(--orange);
|
||||||
|
font-family: var(--fd);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 14px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 4px;
|
||||||
|
transition: background .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qbtn:hover {
|
||||||
|
background: var(--cream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width:900px) {
|
||||||
|
|
||||||
|
.breadcrumb,
|
||||||
|
.intro,
|
||||||
|
.model-section,
|
||||||
|
.process-section,
|
||||||
|
.condition-section,
|
||||||
|
.faq-section,
|
||||||
|
.territory-service,
|
||||||
|
.quote-cta {
|
||||||
|
padding-left: 24px;
|
||||||
|
padding-right: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-grid,
|
||||||
|
.model-layout,
|
||||||
|
.faq-seo-grid,
|
||||||
|
.quote-inner {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-grid,
|
||||||
|
.condition-grid,
|
||||||
|
.region-grid {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qrow {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-nav {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-row-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width:600px) {
|
||||||
|
|
||||||
|
.process-grid,
|
||||||
|
.condition-grid,
|
||||||
|
.region-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
app/who-we-serve/page.tsx
Normal file
45
app/who-we-serve/page.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import WhoWeServe from "@/components/WhoWeServe";
|
||||||
|
import CTA from "@/components/CTA";
|
||||||
|
|
||||||
|
export default function WhoWeServePage() {
|
||||||
|
useEffect(() => {
|
||||||
|
const reveal = () => {
|
||||||
|
const reveals = document.querySelectorAll('.reveal');
|
||||||
|
for (let i = 0; i < reveals.length; i++) {
|
||||||
|
const windowheight = window.innerHeight;
|
||||||
|
const revealtop = reveals[i].getBoundingClientRect().top;
|
||||||
|
const revealpoint = 150;
|
||||||
|
if (revealtop < windowheight - revealpoint) {
|
||||||
|
reveals[i].classList.add('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', reveal);
|
||||||
|
reveal(); // Initial call
|
||||||
|
return () => window.removeEventListener('scroll', reveal);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="who-we-serve-page">
|
||||||
|
<section className="inner-banner">
|
||||||
|
<h1 className="inner-banner-title">Who we <span>serve.</span></h1>
|
||||||
|
<div className="inner-banner-breadcrumbs">
|
||||||
|
<Link href="/">Home</Link>
|
||||||
|
<span>/</span>
|
||||||
|
Who We Serve
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<WhoWeServe />
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<CTA />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,11 +1,14 @@
|
|||||||
export default function CTA() {
|
export default function CTA() {
|
||||||
return (
|
return (
|
||||||
<section className="cta-section">
|
<section className="cta-section">
|
||||||
|
<div className="reveal">
|
||||||
<h2 className="cta-h2">Ready to place<br />your next order?</h2>
|
<h2 className="cta-h2">Ready to place<br />your next order?</h2>
|
||||||
<p className="cta-sub">Same-day delivery · Contractor pricing · 250km radius across Ontario</p>
|
<p className="cta-sub">Scheduled delivery · B2B contractor pricing · 250km radius across Ontario</p>
|
||||||
<div className="cta-btns">
|
<div className="cta-btns">
|
||||||
<a href="#quote" className="btn-white">Request a quote →</a>
|
<a href="/contact" className="btn-white">Request a quote →
|
||||||
<a href="tel:519-xxx-xxxx" className="btn-white-outline">Call us today</a>
|
</a>
|
||||||
|
<a href="mailto:info@vgfenceproducts.com" className="btn-white-outline">CONTACT US</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
48
components/ChainLinkFence/ChainLinkCompare.tsx
Normal file
48
components/ChainLinkFence/ChainLinkCompare.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const ChainLinkCompare = () => {
|
||||||
|
return (
|
||||||
|
<section className="compare">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">Residential vs commercial</div>
|
||||||
|
<h2 className="sh">Right spec for<br /><span>every project.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="compare-grid reveal">
|
||||||
|
<div className="compare-card">
|
||||||
|
<div className="compare-label">Residential</div>
|
||||||
|
<h3>Home & Property</h3>
|
||||||
|
<p>Lighter gauge materials designed for backyard fences, pool enclosures, pet containment, and property boundary fencing. Typically vinyl coated black for curb appeal.</p>
|
||||||
|
<table className="spec-table">
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Common heights</td><td>4ft, 5ft, 6ft</td></tr>
|
||||||
|
<tr><td>Typical gauge</td><td>11 or 11.5 gauge</td></tr>
|
||||||
|
<tr><td>Mesh opening</td><td>2" standard</td></tr>
|
||||||
|
<tr><td>Preferred finish</td><td>Black vinyl coated</td></tr>
|
||||||
|
<tr><td>Post diameter</td><td>1-3/8" to 2" line / 2-3/8" terminal</td></tr>
|
||||||
|
<tr><td>Best for</td><td>Backyards, pools, pets, gardens</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div className="compare-card">
|
||||||
|
<div className="compare-label">Commercial & Industrial</div>
|
||||||
|
<h3>Secure & Heavy-Duty</h3>
|
||||||
|
<p>Heavier gauge, larger diameter posts, and additional security features like barbed wire arms. Galvanized finish is standard for most commercial and industrial applications.</p>
|
||||||
|
<table className="spec-table">
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Common heights</td><td>6ft, 8ft, 10ft, 12ft</td></tr>
|
||||||
|
<tr><td>Typical gauge</td><td>9 or 11 gauge</td></tr>
|
||||||
|
<tr><td>Mesh opening</td><td>2" standard</td></tr>
|
||||||
|
<tr><td>Preferred finish</td><td>Galvanized (hot-dipped)</td></tr>
|
||||||
|
<tr><td>Post diameter</td><td>2-3/8" line / 3" or 4" terminal</td></tr>
|
||||||
|
<tr><td>Best for</td><td>Industrial, security, parking, utilities</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkCompare;
|
||||||
56
components/ChainLinkFence/ChainLinkFAQ.tsx
Normal file
56
components/ChainLinkFence/ChainLinkFAQ.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const faqData = [
|
||||||
|
{
|
||||||
|
q: "What chain link fence materials do I need for a complete installation?",
|
||||||
|
a: "A complete chain link fence installation requires: chain link mesh, terminal posts (end, corner, gate), line posts, top rail, tension bars, tension bands, brace bands, rail ends, post caps, fence ties, and concrete for post setting. For gates, add gate frames, hinges, and latches. Optional additions include privacy slats, windscreen, and barbed wire for commercial use."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "What's the difference between galvanized and vinyl coated chain link?",
|
||||||
|
a: "Galvanized chain link has a zinc coating for corrosion resistance and a silver-grey finish — it's the standard for commercial and industrial use due to its strength and lower cost. Vinyl coated (typically black) has a galvanized core with a PVC exterior coating, offering a cleaner appearance preferred for residential, school, and park applications."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Do you supply chain link fence materials across Ontario?",
|
||||||
|
a: "Yes. VG Fence Products delivers chain link fence materials across a 250km radius from our Kitchener–Waterloo base. We serve contractors and builders in Guelph, Hamilton, Brantford, Toronto/GTA, London, Windsor, Niagara, Barrie, and all communities in between. Contact us for scheduled job site delivery."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Can I get contractor pricing on bulk orders?",
|
||||||
|
a: "Absolutely. We offer contractor pricing for fence contractors, general contractors, builders, and property managers who order regularly. Contact us to set up a contractor account. Bulk orders on chain link mesh, posts, and hardware receive preferential pricing."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "What gauge of chain link is used for residential vs commercial?",
|
||||||
|
a: "Residential chain link is typically 11 or 11.5 gauge wire, which is lighter and more cost-effective for home use. Commercial and security applications typically use 9 gauge, which is heavier and stronger. Lower gauge numbers indicate thicker wire — 9 gauge is significantly stronger than 11.5 gauge."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Do you deliver to job sites across Waterloo Region?",
|
||||||
|
a: "Yes. We offer scheduled delivery to job sites in Kitchener, Waterloo, Cambridge, Ayr, Breslau, Elmira, St. Jacobs, New Hamburg, Baden, Wellesley and all surrounding communities. We can schedule delivery timing to suit your project timeline."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const ChainLinkFAQ = () => {
|
||||||
|
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const toggleFaq = (index: number) => {
|
||||||
|
setOpenIndex(openIndex === index ? null : index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="faq reveal">
|
||||||
|
{faqData.map((item, index) => (
|
||||||
|
<div key={index} className={`faq-item ${openIndex === index ? 'open' : ''}`}>
|
||||||
|
<div className="faq-q" onClick={() => toggleFaq(index)}>
|
||||||
|
{item.q}
|
||||||
|
<span className="faq-icon">+</span>
|
||||||
|
</div>
|
||||||
|
<div className="faq-a">
|
||||||
|
{item.a}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkFAQ;
|
||||||
44
components/ChainLinkFence/ChainLinkFinishes.tsx
Normal file
44
components/ChainLinkFence/ChainLinkFinishes.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const ChainLinkFinishes = () => {
|
||||||
|
return (
|
||||||
|
<section className="finishes">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">Material finishes</div>
|
||||||
|
<h2 className="sh">Galvanized or<br /><span>vinyl coated.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="finish-grid reveal">
|
||||||
|
<div className="finish-card galv">
|
||||||
|
<div className="finish-deco"></div>
|
||||||
|
<h3>Galvanized <span>Steel</span></h3>
|
||||||
|
<p>Hot-dipped galvanized chain link is the industry standard for durability and value. The zinc coating provides long-lasting corrosion resistance, making it ideal for commercial, industrial, and utilitarian applications where appearance is secondary to performance.</p>
|
||||||
|
<ul className="finish-list">
|
||||||
|
<li>Hot-dipped zinc coating for maximum protection</li>
|
||||||
|
<li>Industry-standard silver/grey finish</li>
|
||||||
|
<li>Higher tensile strength vs vinyl coated</li>
|
||||||
|
<li>Preferred for industrial & commercial security</li>
|
||||||
|
<li>Lower cost per linear foot</li>
|
||||||
|
<li>Long service life — 20+ years in most climates</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="finish-card vinyl">
|
||||||
|
<div className="finish-deco"></div>
|
||||||
|
<h3>Black Vinyl <span>Coated</span></h3>
|
||||||
|
<p>Vinyl coated chain link features a PVC coating over a galvanized core, adding a clean, finished appearance that blends into residential and commercial landscapes. The most popular choice for residential installations, schools, and parks.</p>
|
||||||
|
<ul className="finish-list">
|
||||||
|
<li>Galvanized core + PVC exterior coating</li>
|
||||||
|
<li>Clean black finish — blends into landscape</li>
|
||||||
|
<li>Preferred for residential & decorative applications</li>
|
||||||
|
<li>Schools, parks, playgrounds, pools</li>
|
||||||
|
<li>Slightly thicker than galvanized gauge</li>
|
||||||
|
<li>UV resistant coating for colour retention</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkFinishes;
|
||||||
45
components/ChainLinkFence/ChainLinkHero.tsx
Normal file
45
components/ChainLinkFence/ChainLinkHero.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const ChainLinkHero = () => {
|
||||||
|
return (
|
||||||
|
<section className="hero">
|
||||||
|
<div className="hero-grid-bg"></div>
|
||||||
|
<div className="hero-mesh"></div>
|
||||||
|
<div className="hero-accent"></div>
|
||||||
|
<div className="hero-inner">
|
||||||
|
<div className="hero-eyebrow">Chain link fence materials · KWC Ontario</div>
|
||||||
|
<h1>
|
||||||
|
Everything you need<br />
|
||||||
|
<em>to build.</em>
|
||||||
|
</h1>
|
||||||
|
<p className="hero-desc">
|
||||||
|
We stock a <strong>full range of chain link fence materials</strong> for both residential and commercial installation — mesh, posts, rails, gates, hardware and all accessories. Serving contractors and builders across a <strong>250km radius from Kitchener–Waterloo</strong>.
|
||||||
|
</p>
|
||||||
|
<div className="hero-badges">
|
||||||
|
<span className="badge badge-fill">Galvanized & Vinyl Coated</span>
|
||||||
|
<span className="badge">Residential & Commercial</span>
|
||||||
|
<span className="badge">Scheduled Job Site Delivery</span>
|
||||||
|
<span className="badge">Contractor Pricing</span>
|
||||||
|
</div>
|
||||||
|
<div className="hero-stats">
|
||||||
|
<div>
|
||||||
|
<div className="stat-val">17+</div>
|
||||||
|
<div className="stat-label">Material types in stock</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="stat-val">250km</div>
|
||||||
|
<div className="stat-label">Ontario delivery radius</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="stat-val">B2B</div>
|
||||||
|
<div className="stat-label">Contractor focus</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkHero;
|
||||||
236
components/ChainLinkFence/ChainLinkMaterials.tsx
Normal file
236
components/ChainLinkFence/ChainLinkMaterials.tsx
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const materialsData = [
|
||||||
|
{
|
||||||
|
num: "01",
|
||||||
|
cat: "mesh",
|
||||||
|
name: "Chain Link Mesh",
|
||||||
|
desc: "The primary fencing fabric — available in galvanized and vinyl coated black finishes. Multiple gauge and mesh sizes for residential and commercial use.",
|
||||||
|
specs: ["Galvanized", "Black vinyl coated", "Multiple gauges", "2\" mesh typical"],
|
||||||
|
img: "images/chainlink-mesh.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "02",
|
||||||
|
cat: "structure",
|
||||||
|
name: "Terminal Posts",
|
||||||
|
desc: "Heavy-duty end, corner, and gate posts that anchor the fence line. Larger diameter than line posts for maximum structural strength at key points.",
|
||||||
|
specs: ["End posts", "Corner posts", "Gate posts", "Multiple diameters"],
|
||||||
|
img: "images/chainlink-terminal-posts.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "03",
|
||||||
|
cat: "structure",
|
||||||
|
name: "Line Posts",
|
||||||
|
desc: "Intermediate posts set between terminal posts to support the mesh and top rail at regular intervals, typically every 10 feet.",
|
||||||
|
specs: ["Galvanized steel", "Multiple heights", "Multiple diameters"],
|
||||||
|
img: "images/chainlink-line-posts.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "04",
|
||||||
|
cat: "structure",
|
||||||
|
name: "Top Rail",
|
||||||
|
desc: "Horizontal pipe running along the top of the fence, threading through loop caps on line posts to stabilise the mesh and define the top edge.",
|
||||||
|
specs: ["Galvanized pipe", "21ft lengths", "Sleeve-joined"],
|
||||||
|
img: "images/chainlink-top-rail.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "05",
|
||||||
|
cat: "mesh",
|
||||||
|
name: "Bottom Tension Wire",
|
||||||
|
desc: "Heavy gauge galvanized wire run along the bottom of the mesh to keep the fence taut and prevent mesh lift. Also available as bottom rail pipe.",
|
||||||
|
specs: ["Galvanized wire", "Bottom rail option", "High tension"],
|
||||||
|
img: "images/chainlink-tension-wire.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "06",
|
||||||
|
cat: "hardware",
|
||||||
|
name: "Tension Bars",
|
||||||
|
desc: "Flat steel bars woven vertically through the end of the mesh fabric and secured with tension bands to the terminal post, pulling the mesh taut.",
|
||||||
|
specs: ["Galvanized steel", "Multiple lengths"],
|
||||||
|
img: "images/chainlink-tension-bars.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "07",
|
||||||
|
cat: "hardware",
|
||||||
|
name: "Tension Bands",
|
||||||
|
desc: "Clamp bands bolted around terminal posts to hold tension bars and rail ends securely in place. Spaced evenly along the post height.",
|
||||||
|
specs: ["Galvanized steel", "Various post sizes"],
|
||||||
|
img: "images/chainlink-tension-bands.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "08",
|
||||||
|
cat: "hardware",
|
||||||
|
name: "Brace Bands",
|
||||||
|
desc: "Used to attach rail ends to line posts, securing the top rail into position around the post perimeter. Essential for top rail alignment and stability.",
|
||||||
|
specs: ["Galvanized", "All post sizes"],
|
||||||
|
img: "images/chainlink-brace-bands.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "09",
|
||||||
|
cat: "hardware",
|
||||||
|
name: "Rail Ends",
|
||||||
|
desc: "Pressed steel fittings that attach the end of the top rail to a terminal post via a tension band. Provides a neat, secure termination point for the rail.",
|
||||||
|
specs: ["Pressed steel", "Galvanized"],
|
||||||
|
img: "images/chainlink-rail-ends.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "10",
|
||||||
|
cat: "hardware",
|
||||||
|
name: "Post Caps",
|
||||||
|
desc: "Loop caps thread the top rail through the line post for support. Dome caps seal the tops of terminal posts, preventing water ingress and corrosion.",
|
||||||
|
specs: ["Loop caps", "Dome caps", "All post sizes"],
|
||||||
|
img: "images/chainlink-post-caps.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "11",
|
||||||
|
cat: "hardware",
|
||||||
|
name: "Fence Ties",
|
||||||
|
desc: "Aluminum or steel wire ties that fasten the chain link mesh to line posts and top rail, securing the fabric in place throughout the fence run.",
|
||||||
|
specs: ["Aluminum", "Steel", "Bulk packs"],
|
||||||
|
img: "images/chainlink-fence-ties.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "12",
|
||||||
|
cat: "gates",
|
||||||
|
name: "Gates",
|
||||||
|
desc: "Chain link walk gates and double drive gates in standard and custom widths. Pre-built frames ready to hang, or materials for field-fabricated gates.",
|
||||||
|
specs: ["Single walk gates", "Double drive gates", "Custom widths"],
|
||||||
|
img: "images/chainlink-gates.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "13",
|
||||||
|
cat: "gates",
|
||||||
|
name: "Gate Hardware",
|
||||||
|
desc: "Heavy-duty hinges, fork latches, cane bolts, and gate stops. All hardware required for proper gate installation and long-term operation.",
|
||||||
|
specs: ["Hinges", "Latches", "Cane bolts", "Padlock eyes"],
|
||||||
|
img: "images/chainlink-gate-hardware.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "14",
|
||||||
|
cat: "structure",
|
||||||
|
name: "Concrete",
|
||||||
|
desc: "Fast-setting concrete mix for post setting. Properly set posts are critical to fence longevity — use concrete on every terminal post and every line post in loose or sandy soil.",
|
||||||
|
specs: ["Fast-setting", "Bags available", "Post setting mix"],
|
||||||
|
img: "images/chainlink-concrete.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "15",
|
||||||
|
cat: "extras",
|
||||||
|
name: "Privacy Slats",
|
||||||
|
desc: "Vertical or horizontal slats woven through chain link mesh to add privacy and visual screening. Available in multiple colours to complement any project.",
|
||||||
|
specs: ["Multiple colours", "Vertical weave", "UV resistant"],
|
||||||
|
img: "images/chainlink-privacy-slats.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "16",
|
||||||
|
cat: "extras",
|
||||||
|
name: "Windscreen / Privacy Mesh",
|
||||||
|
desc: "Woven or knitted privacy screen attached to the fence exterior. Popular for sports facilities, construction sites, and commercial properties requiring visual screening.",
|
||||||
|
specs: ["Woven fabric", "Commercial grade", "Custom sizes"],
|
||||||
|
img: "images/chainlink-windscreen.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "17",
|
||||||
|
cat: "extras",
|
||||||
|
name: "Barbed Wire",
|
||||||
|
desc: "Two-strand galvanized barbed wire for security enhancement on commercial and industrial fence installations. Attached at the top with barbed wire arms.",
|
||||||
|
specs: ["Commercial use", "Galvanized", "Barbed wire arms"],
|
||||||
|
img: "images/chainlink-barbed-wire.jpg"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const ChainLinkMaterials = () => {
|
||||||
|
const [activeCat, setActiveCat] = useState('all');
|
||||||
|
|
||||||
|
const filteredMaterials = activeCat === 'all'
|
||||||
|
? materialsData
|
||||||
|
: materialsData.filter(m => m.cat === activeCat);
|
||||||
|
|
||||||
|
const categories = [
|
||||||
|
{ id: 'all', label: 'All materials' },
|
||||||
|
{ id: 'structure', label: 'Structure & posts' },
|
||||||
|
{ id: 'mesh', label: 'Mesh & wire' },
|
||||||
|
{ id: 'hardware', label: 'Hardware & fittings' },
|
||||||
|
{ id: 'gates', label: 'Gates & access' },
|
||||||
|
{ id: 'extras', label: 'Extras & accessories' }
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="materials" id="materials">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="materials-intro">
|
||||||
|
<div>
|
||||||
|
<div className="section-eyebrow">Product catalogue</div>
|
||||||
|
<h2 className="sh">Chain link fence<br /><span>materials list.</span></h2>
|
||||||
|
</div>
|
||||||
|
<p>Every component you need for a complete chain link fence installation — all stocked and ready for delivery across Ontario.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="cat-tabs reveal">
|
||||||
|
{categories.map(cat => (
|
||||||
|
<button
|
||||||
|
key={cat.id}
|
||||||
|
className={`cat-tab ${activeCat === cat.id ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveCat(cat.id)}
|
||||||
|
>
|
||||||
|
{cat.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mat-grid">
|
||||||
|
{filteredMaterials.map((mat) => (
|
||||||
|
<div key={mat.num} className="mat-card">
|
||||||
|
<div className="mat-img-wrap">
|
||||||
|
<img src={mat.img} alt={mat.name} onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} />
|
||||||
|
<div className="mat-img-ph">
|
||||||
|
<svg width="28" height="28" viewBox="0 0 28 28" fill="none">
|
||||||
|
<rect x="2" y="4" width="24" height="16" rx="2" stroke="#B0ADA6" strokeWidth="1.2" fill="none" />
|
||||||
|
<circle cx="10" cy="11" r="3" stroke="#B0ADA6" strokeWidth="1" fill="none" />
|
||||||
|
<path d="M2 17 L9 11 L14 16 L19 11 L26 17" stroke="#B0ADA6" strokeWidth="1" fill="none" />
|
||||||
|
</svg>
|
||||||
|
<div className="mat-img-ph-lbl">Product {mat.num}</div>
|
||||||
|
<div className="mat-img-ph-sub">{mat.img}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mat-num">{mat.num}</div>
|
||||||
|
<div className="mat-icon">
|
||||||
|
{/* Simplified version of the icons since they are many, keeping a generic but nice look or using the specific ones from HTML if I can extract them easily.
|
||||||
|
Actually, I'll just use the SVG types from the HTML for each card. */}
|
||||||
|
{mat.num === "01" && <svg viewBox="0 0 20 20" fill="none"><path d="M2 2 L10 10 L18 2" stroke="white" strokeWidth="1.5" fill="none" strokeLinecap="round"/><path d="M2 10 L10 18 L18 10" stroke="white" strokeWidth="1.5" fill="none" strokeLinecap="round"/><path d="M2 6 L18 6" stroke="white" strokeWidth="1" opacity=".5"/><path d="M2 14 L18 14" stroke="white" strokeWidth="1" opacity=".5"/></svg>}
|
||||||
|
{mat.num === "02" && <svg viewBox="0 0 20 20" fill="none"><rect x="7" y="1" width="6" height="18" rx="1.5" fill="white" opacity=".9"/><rect x="4" y="1" width="3" height="4" rx="1" fill="white" opacity=".4"/><rect x="13" y="1" width="3" height="4" rx="1" fill="white" opacity=".4"/></svg>}
|
||||||
|
{mat.num === "03" && <svg viewBox="0 0 20 20" fill="none"><rect x="8" y="1" width="4" height="18" rx="1.5" fill="white"/><line x1="4" y1="7" x2="16" y2="7" stroke="white" strokeWidth="1" opacity=".5"/><line x1="4" y1="13" x2="16" y2="13" stroke="white" strokeWidth="1" opacity=".5"/></svg>}
|
||||||
|
{mat.num === "04" && <svg viewBox="0 0 20 20" fill="none"><rect x="1" y="8" width="18" height="4" rx="2" fill="white"/><rect x="4" y="2" width="2" height="6" rx="1" fill="white" opacity=".5"/><rect x="14" y="2" width="2" height="6" rx="1" fill="white" opacity=".5"/></svg>}
|
||||||
|
{mat.num === "05" && <svg viewBox="0 0 20 20" fill="none"><line x1="1" y1="10" x2="19" y2="10" stroke="white" strokeWidth="2.5" strokeLinecap="round"/><circle cx="5" cy="10" r="2" fill="white" opacity=".6"/><circle cx="10" cy="10" r="2" fill="white" opacity=".6"/><circle cx="15" cy="10" r="2" fill="white" opacity=".6"/></svg>}
|
||||||
|
{mat.num === "06" && <svg viewBox="0 0 20 20" fill="none"><rect x="8" y="1" width="4" height="18" rx="1" fill="white"/><rect x="5" y="4" width="10" height="2" rx="1" fill="white" opacity=".5"/><rect x="5" y="14" width="10" height="2" rx="1" fill="white" opacity=".5"/></svg>}
|
||||||
|
{mat.num === "07" && <svg viewBox="0 0 20 20" fill="none"><circle cx="10" cy="10" r="7" fill="none" stroke="white" strokeWidth="2"/><rect x="8" y="1" width="4" height="3" rx="1" fill="white" opacity=".7"/></svg>}
|
||||||
|
{mat.num === "08" && <svg viewBox="0 0 20 20" fill="none"><path d="M3 10 Q10 4 17 10" stroke="white" strokeWidth="2" fill="none" strokeLinecap="round"/><path d="M3 12 Q10 6 17 12" stroke="white" strokeWidth="1" fill="none" strokeLinecap="round" opacity=".4"/></svg>}
|
||||||
|
{mat.num === "09" && <svg viewBox="0 0 20 20" fill="none"><rect x="2" y="8" width="10" height="4" rx="1.5" fill="white"/><circle cx="16" cy="10" r="3" fill="white" opacity=".7"/></svg>}
|
||||||
|
{mat.num === "10" && <svg viewBox="0 0 20 20" fill="none"><circle cx="10" cy="6" r="4" fill="white" opacity=".9"/><rect x="8" y="10" width="4" height="9" rx="1" fill="white" opacity=".6"/></svg>}
|
||||||
|
{mat.num === "11" && <svg viewBox="0 0 20 20" fill="none"><path d="M5 10 C5 6 10 4 15 7" stroke="white" strokeWidth="2" fill="none" strokeLinecap="round"/><path d="M5 10 C5 14 10 16 15 13" stroke="white" strokeWidth="1" fill="none" strokeLinecap="round" opacity=".5"/></svg>}
|
||||||
|
{mat.num === "12" && <svg viewBox="0 0 20 20" fill="none"><rect x="1" y="3" width="7" height="14" rx="1" fill="white" opacity=".9"/><rect x="12" y="3" width="7" height="14" rx="1" fill="white" opacity=".9"/><line x1="8" y1="10" x2="12" y2="10" stroke="white" strokeWidth="1.5" strokeDasharray="2 1"/></svg>}
|
||||||
|
{mat.num === "13" && <svg viewBox="0 0 20 20" fill="none"><circle cx="6" cy="10" r="4" fill="none" stroke="white" strokeWidth="2"/><rect x="10" y="9" width="9" height="2" rx="1" fill="white"/><circle cx="6" cy="10" r="1.5" fill="white"/></svg>}
|
||||||
|
{mat.num === "14" && <svg viewBox="0 0 20 20" fill="none"><rect x="3" y="10" width="14" height="9" rx="1.5" fill="white" opacity=".8"/><path d="M5 10 L10 3 L15 10" fill="white" opacity=".5"/></svg>}
|
||||||
|
{mat.num === "15" && <svg viewBox="0 0 20 20" fill="none"><rect x="2" y="2" width="3" height="16" rx="1" fill="white"/><rect x="7" y="2" width="3" height="16" rx="1" fill="white" opacity=".7"/><rect x="12" y="2" width="3" height="16" rx="1" fill="white" opacity=".5"/><rect x="17" y="2" width="2" height="16" rx="1" fill="white" opacity=".3"/></svg>}
|
||||||
|
{mat.num === "16" && <svg viewBox="0 0 20 20" fill="none"><rect x="1" y="5" width="18" height="10" rx="2" fill="white" opacity=".3" stroke="white" strokeWidth="1.5"/><line x1="5" y1="5" x2="5" y2="15" stroke="white" strokeWidth="1" opacity=".5"/><line x1="10" y1="5" x2="10" y2="15" stroke="white" strokeWidth="1" opacity=".5"/><line x1="15" y1="5" x2="15" y2="15" stroke="white" strokeWidth="1" opacity=".5"/></svg>}
|
||||||
|
{mat.num === "17" && <svg viewBox="0 0 20 20" fill="none"><line x1="1" y1="10" x2="19" y2="10" stroke="white" strokeWidth="2" strokeLinecap="round"/><path d="M5 7 L5 13 M5 7 L7 9 M5 7 L3 9" stroke="white" strokeWidth="1.2" strokeLinecap="round"/><path d="M12 7 L12 13 M12 7 L14 9 M12 7 L10 9" stroke="white" strokeWidth="1.2" strokeLinecap="round"/></svg>}
|
||||||
|
</div>
|
||||||
|
<div className="mat-name">{mat.name}</div>
|
||||||
|
<div className="mat-desc">{mat.desc}</div>
|
||||||
|
<div className="mat-specs">
|
||||||
|
{mat.specs.map((spec, i) => (
|
||||||
|
<span key={i} className="mat-spec">{spec}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mat-arrow">→</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkMaterials;
|
||||||
65
components/ChainLinkFence/ChainLinkOverview.tsx
Normal file
65
components/ChainLinkFence/ChainLinkOverview.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const ChainLinkOverview = () => {
|
||||||
|
return (
|
||||||
|
<section className="overview">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">About this product line</div>
|
||||||
|
<h2 className="sh">Complete chain link<br /><span>fence supply.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="overview-grid">
|
||||||
|
<div className="overview-text reveal">
|
||||||
|
<p>
|
||||||
|
<strong>VG Fence Products is your dedicated chain link fence materials supplier in the Kitchener–Waterloo–Cambridge region.</strong> Our chain link fence product line includes every component required for a complete installation — from mesh and posts to gates and hardware.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Whether you're a fence contractor building a residential backyard fence or a commercial contractor securing a large industrial site, we carry the materials you need in both <strong>galvanized and vinyl coated finishes</strong>. Order in bulk with contractor pricing, or contact us for a custom quote on your specific project requirements.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We deliver directly to job sites across our 250km service radius — covering Waterloo Region, Guelph, Hamilton, Brantford, Toronto GTA, London, Windsor and everywhere in between.
|
||||||
|
</p>
|
||||||
|
<ul className="check-list">
|
||||||
|
<li>Full product range always in stock — no waiting on special orders</li>
|
||||||
|
<li>Galvanized and black vinyl coated options available</li>
|
||||||
|
<li>Bulk contractor pricing on all orders</li>
|
||||||
|
<li>Scheduled delivery direct to your job site</li>
|
||||||
|
<li>Knowledgeable team to help spec your project</li>
|
||||||
|
<li>Commercial and residential applications</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="use-cards reveal">
|
||||||
|
<div className="use-card">
|
||||||
|
<div className="use-card-title">Residential installations</div>
|
||||||
|
<div className="use-card-desc">Backyard fencing, pool enclosures, pet containment, garden borders, property boundaries. Standard residential heights from 3ft to 6ft.</div>
|
||||||
|
<div className="use-card-tags">
|
||||||
|
<span className="mini-tag">Black vinyl coated</span>
|
||||||
|
<span className="mini-tag">Galvanized</span>
|
||||||
|
<span className="mini-tag">Privacy slats</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="use-card">
|
||||||
|
<div className="use-card-title">Commercial & industrial</div>
|
||||||
|
<div className="use-card-desc">Security perimeters, facility enclosures, parking lots, industrial yards, warehouses. Heavy gauge commercial chain link with barbed wire options.</div>
|
||||||
|
<div className="use-card-tags">
|
||||||
|
<span className="mini-tag">Heavy gauge</span>
|
||||||
|
<span className="mini-tag">Barbed wire</span>
|
||||||
|
<span className="mini-tag">Security grade</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="use-card">
|
||||||
|
<div className="use-card-title">Construction & temporary</div>
|
||||||
|
<div className="use-card-desc">Site security, pedestrian barriers, crowd control, hazard enclosures. Available for temporary rental or permanent installation.</div>
|
||||||
|
<div className="use-card-tags">
|
||||||
|
<span className="mini-tag">Rental available</span>
|
||||||
|
<span className="mini-tag">Site security</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkOverview;
|
||||||
66
components/ChainLinkFence/ChainLinkQuote.tsx
Normal file
66
components/ChainLinkFence/ChainLinkQuote.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const ChainLinkQuote = () => {
|
||||||
|
return (
|
||||||
|
<section className="quote-cta">
|
||||||
|
<div className="quote-inner">
|
||||||
|
<div className="quote-left">
|
||||||
|
<h2>Get chain link fence materials pricing.</h2>
|
||||||
|
<p>Fill in the quick form and we'll come back to you with contractor pricing within 2 business hours. Bulk orders welcome — we supply fence contractors, builders, and property managers across Ontario.</p>
|
||||||
|
</div>
|
||||||
|
<div className="quote-form-card">
|
||||||
|
<div className="q-form-title">Request a quote</div>
|
||||||
|
<div className="q-form-sub">Response within 2 business hours</div>
|
||||||
|
<div className="qrow">
|
||||||
|
<div>
|
||||||
|
<label className="ql">Company name</label>
|
||||||
|
<input className="qi" type="text" placeholder="ABC Fence Co." />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="ql">Your name</label>
|
||||||
|
<input className="qi" type="text" placeholder="John Smith" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="qrow">
|
||||||
|
<div>
|
||||||
|
<label className="ql">Phone</label>
|
||||||
|
<input className="qi" type="tel" placeholder="519-xxx-xxxx" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="ql">Email</label>
|
||||||
|
<input className="qi" type="email" placeholder="you@company.com" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label className="ql">Material needed</label>
|
||||||
|
<select className="qi">
|
||||||
|
<option value="">Select material type...</option>
|
||||||
|
<option>Chain link mesh — galvanized</option>
|
||||||
|
<option>Chain link mesh — vinyl coated black</option>
|
||||||
|
<option>Posts (terminal & line)</option>
|
||||||
|
<option>Top rail</option>
|
||||||
|
<option>Gates & gate hardware</option>
|
||||||
|
<option>Hardware & fittings pack</option>
|
||||||
|
<option>Privacy slats</option>
|
||||||
|
<option>Barbed wire</option>
|
||||||
|
<option>Complete material package</option>
|
||||||
|
</select>
|
||||||
|
<div className="qrow">
|
||||||
|
<div>
|
||||||
|
<label className="ql">Job site city</label>
|
||||||
|
<input className="qi" type="text" placeholder="Kitchener, Guelph..." />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="ql">Linear footage</label>
|
||||||
|
<input className="qi" type="text" placeholder="e.g. 300 linear ft" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button className="qbtn">Send quote request →</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkQuote;
|
||||||
36
components/ChainLinkFence/ChainLinkSeoContent.tsx
Normal file
36
components/ChainLinkFence/ChainLinkSeoContent.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ChainLinkFAQ from './ChainLinkFAQ';
|
||||||
|
|
||||||
|
const ChainLinkSeoContent = () => {
|
||||||
|
return (
|
||||||
|
<section className="seo-content">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">Helpful information</div>
|
||||||
|
<h2 className="sh">Chain link fence<br /><span>knowledge base.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="seo-grid">
|
||||||
|
{/* FAQ */}
|
||||||
|
<ChainLinkFAQ />
|
||||||
|
|
||||||
|
{/* SEO TEXT */}
|
||||||
|
<div className="content-block reveal">
|
||||||
|
<h3>Chain link fence materials supplier — KWC & Ontario</h3>
|
||||||
|
<p>VG Fence Products is a dedicated <strong>chain link fence materials supplier</strong> serving the Kitchener–Waterloo–Cambridge region and contractors across Ontario. We stock a complete range of chain link fence supply — from mesh and structural components to gates, hardware, and accessories — so your team can get everything needed from a single source.</p>
|
||||||
|
|
||||||
|
<h3>Serving fence contractors across Southern Ontario</h3>
|
||||||
|
<p>We understand what fence contractors need: reliable stock, competitive pricing, and delivery that shows up when it's supposed to. Our <strong>chain link fence supply</strong> is available for bulk contractor orders with scheduled job site delivery across our 250km service radius. Set up a contractor account for streamlined ordering and preferential pricing.</p>
|
||||||
|
|
||||||
|
<h3>Commercial chain link fence materials</h3>
|
||||||
|
<p>Commercial chain link fence installations require heavier gauge materials, larger diameter posts, and additional hardware for security perimeters, industrial yards, and utility infrastructure. We stock <strong>commercial chain link fence</strong> materials including 9 gauge mesh, heavy terminal posts, and barbed wire for security-rated installations.</p>
|
||||||
|
|
||||||
|
<h3>Residential chain link fence supply</h3>
|
||||||
|
<p>For <strong>residential chain link fence</strong> projects, we carry the complete range of materials in both galvanized and black vinyl coated finishes. Whether you're installing a standard backyard fence, a pool enclosure, or a pet run, our team can help you spec the correct materials and quantities for your project.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkSeoContent;
|
||||||
138
components/ChainLinkFence/ChainLinkTerritory.tsx
Normal file
138
components/ChainLinkFence/ChainLinkTerritory.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
const regions = [
|
||||||
|
{
|
||||||
|
name: "Waterloo Region",
|
||||||
|
cities: [
|
||||||
|
{ name: "Kitchener", link: "https://vgfence.com/fence-staining-kitchener" },
|
||||||
|
{ name: "Waterloo", link: "https://vgfence.com/fence-staining-waterloo" },
|
||||||
|
{ name: "Cambridge", link: "https://vgfence.com/fence-staining-cambridge" },
|
||||||
|
{ name: "Ayr", link: "https://vgfence.com/fence-staining-ayr" },
|
||||||
|
{ name: "Breslau", link: "https://vgfence.com/fence-staining-breslau" },
|
||||||
|
{ name: "Elmira", link: "https://vgfence.com/fence-staining-elmira" },
|
||||||
|
{ name: "St. Jacobs", link: "https://vgfence.com/fence-staining-st-jacobs" },
|
||||||
|
{ name: "New Hamburg", link: "https://vgfence.com/fence-staining-new-hamburg" },
|
||||||
|
{ name: "Baden", link: "https://vgfence.com/fence-staining-baden" },
|
||||||
|
{ name: "Wellesley", link: "https://vgfence.com/fence-staining-wellesley" }
|
||||||
|
],
|
||||||
|
primary: [0, 1, 2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Guelph & Wellington",
|
||||||
|
cities: [
|
||||||
|
{ name: "Guelph", link: "https://vgfence.com/fence-staining-guelph" },
|
||||||
|
{ name: "Fergus", link: "https://vgfence.com/fence-staining-fergus" },
|
||||||
|
{ name: "Elora", link: "https://vgfence.com/fence-staining-elora" },
|
||||||
|
{ name: "Rockwood", link: "https://vgfence.com/fence-staining-rockwood" },
|
||||||
|
{ name: "Acton", link: "https://vgfence.com/fence-staining-acton" },
|
||||||
|
{ name: "Georgetown", link: "https://vgfence.com/fence-staining-georgetown" }
|
||||||
|
],
|
||||||
|
primary: [0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Halton & Hamilton",
|
||||||
|
cities: [
|
||||||
|
{ name: "Hamilton", link: "https://vgfence.com/fence-staining-hamilton" },
|
||||||
|
{ name: "Burlington", link: "https://vgfence.com/fence-staining-burlington" },
|
||||||
|
{ name: "Milton", link: "https://vgfence.com/fence-staining-milton" },
|
||||||
|
{ name: "Oakville", link: "https://vgfence.com/fence-staining-oakville" },
|
||||||
|
{ name: "Stoney Creek", link: "https://vgfence.com/fence-staining-stoney-creek" },
|
||||||
|
{ name: "Grimsby", link: "https://vgfence.com/fence-staining-grimsby" },
|
||||||
|
{ name: "Brantford", link: "https://vgfence.com/fence-staining-brantford" },
|
||||||
|
{ name: "Paris", link: "https://vgfence.com/fence-staining-paris" }
|
||||||
|
],
|
||||||
|
primary: [0, 1, 6]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GTA & Peel",
|
||||||
|
cities: [
|
||||||
|
{ name: "Mississauga", link: "https://vgfence.com/fence-staining-mississauga" },
|
||||||
|
{ name: "Brampton", link: "https://vgfence.com/fence-staining-brampton" },
|
||||||
|
{ name: "Toronto", link: null },
|
||||||
|
{ name: "Vaughan", link: "https://vgfence.com/fence-staining-vaughan" },
|
||||||
|
{ name: "Markham", link: "https://vgfence.com/fence-staining-markham" },
|
||||||
|
{ name: "Richmond Hill", link: "https://vgfence.com/fence-staining-richmond-hill" }
|
||||||
|
],
|
||||||
|
primary: [0, 1, 2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Oxford & Perth",
|
||||||
|
cities: [
|
||||||
|
{ name: "Woodstock", link: "https://vgfence.com/fence-staining-woodstock" },
|
||||||
|
{ name: "Stratford", link: "https://vgfence.com/fence-staining-stratford" },
|
||||||
|
{ name: "Ingersoll", link: null },
|
||||||
|
{ name: "Tillsonburg", link: null },
|
||||||
|
{ name: "St. Marys", link: null }
|
||||||
|
],
|
||||||
|
primary: [0, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "London & Elgin",
|
||||||
|
cities: [
|
||||||
|
{ name: "London", link: "https://vgfence.com/fence-staining-london-ontario" },
|
||||||
|
{ name: "St. Thomas", link: null },
|
||||||
|
{ name: "Strathroy", link: null },
|
||||||
|
{ name: "Komoka", link: null }
|
||||||
|
],
|
||||||
|
primary: [0]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Southwest Ontario",
|
||||||
|
cities: [
|
||||||
|
{ name: "Windsor", link: null },
|
||||||
|
{ name: "Chatham", link: null },
|
||||||
|
{ name: "Leamington", link: null },
|
||||||
|
{ name: "Sarnia", link: null },
|
||||||
|
{ name: "Petrolia", link: null }
|
||||||
|
],
|
||||||
|
primary: [0, 1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extended Service",
|
||||||
|
cities: [
|
||||||
|
{ name: "Niagara Falls", link: null },
|
||||||
|
{ name: "St. Catharines", link: null },
|
||||||
|
{ name: "Welland", link: null },
|
||||||
|
{ name: "Barrie", link: null },
|
||||||
|
{ name: "Owen Sound", link: null },
|
||||||
|
{ name: "Collingwood", link: null }
|
||||||
|
],
|
||||||
|
primary: [0, 3]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const ChainLinkTerritory = () => {
|
||||||
|
return (
|
||||||
|
<section className="territory" id="territory">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow text-white-50">Where we deliver</div>
|
||||||
|
<h2 className="sh sh-white">Chain link fence supply<br /><span>across Ontario.</span></h2>
|
||||||
|
<p className="territory-intro">We deliver chain link fence materials to contractors and builders across a 250km radius from our Kitchener–Waterloo base. Below are the communities we serve — if you don't see your town, contact us.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="region-grid reveal">
|
||||||
|
{regions.map((region, i) => (
|
||||||
|
<div key={i} className="region-block">
|
||||||
|
<div className="region-name">{region.name}</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
{region.cities.map((city, j) => (
|
||||||
|
<li key={j} className={region.primary.includes(j) ? 'primary' : ''}>
|
||||||
|
{city.link ? (
|
||||||
|
<Link href={city.link}>{city.name}</Link>
|
||||||
|
) : (
|
||||||
|
city.name
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChainLinkTerritory;
|
||||||
80
components/ConstructionPopup.tsx
Normal file
80
components/ConstructionPopup.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { IoClose } from "react-icons/io5";
|
||||||
|
|
||||||
|
export default function ConstructionPopup() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [shouldRender, setShouldRender] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if the popup has already been shown in this session
|
||||||
|
const hasBeenShown = sessionStorage.getItem("construction_popup_shown");
|
||||||
|
|
||||||
|
if (!hasBeenShown) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setShouldRender(true);
|
||||||
|
setIsVisible(true);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closePopup = () => {
|
||||||
|
setIsVisible(false);
|
||||||
|
// Add a delay to remove from DOM after fade out animation if needed
|
||||||
|
// For now, we'll just hide it and set session storage
|
||||||
|
sessionStorage.setItem("construction_popup_shown", "true");
|
||||||
|
setTimeout(() => setShouldRender(false), 400); // match animation duration
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!shouldRender) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`construction-overlay ${!isVisible ? 'fade-out' : ''}`}>
|
||||||
|
<div className="construction-modal">
|
||||||
|
<button
|
||||||
|
className="construction-close"
|
||||||
|
onClick={closePopup}
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<IoClose />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="construction-content">
|
||||||
|
<span className="construction-icon">🚧</span>
|
||||||
|
<h2 className="construction-title">Website Under Construction</h2>
|
||||||
|
|
||||||
|
<p className="construction-text">
|
||||||
|
We are currently updating our fencing product catalog to serve you better.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="construction-prices">
|
||||||
|
Prices may vary depending on materials, design, and quantity.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="construction-contact">
|
||||||
|
👉 For the latest pricing and customized quotes, please contact us.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button className="construction-btn" onClick={closePopup}>
|
||||||
|
Got it
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style jsx>{`
|
||||||
|
.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s ease-out;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.fade-out .construction-modal {
|
||||||
|
transform: scale(0.95);
|
||||||
|
transition: transform 0.4s ease-out;
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,60 +1,94 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer>
|
<footer>
|
||||||
<div className="footer-top">
|
<div className="footer-top">
|
||||||
|
{/* Brand */}
|
||||||
<div>
|
<div>
|
||||||
<div className="footer-brand-name">
|
<div className="footer-logo-wrap">
|
||||||
<img src="/assets/vg-fence.png" alt="VG Fence Products" style={{ height: '32px', width: 'auto', marginLeft: '-15px' }} />
|
<img
|
||||||
|
src="/assets/vg-fence.png"
|
||||||
|
alt="VG Fence Products"
|
||||||
|
style={{ height: '40px', width: 'auto'}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="footer-tagline">Ontario's B2B fence supply partner — KWC & beyond</div>
|
<div className="footer-tagline">Ontario's B2B fence supply partner — KWC & beyond</div>
|
||||||
<div className="footer-territory">📍 Serving 250km radius from Kitchener-Waterloo</div>
|
<div className="footer-territory">
|
||||||
<div className="footer-contact" style={{ marginTop: '16px' }}>
|
<span style={{ fontSize: '14px', filter: 'hue-rotate(320deg)' }}>📍</span>
|
||||||
|
Serving 250km from Kitchener–Waterloo
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="footer-contact">
|
||||||
<a href="mailto:info@vgfenceproducts.com">info@vgfenceproducts.com</a>
|
<a href="mailto:info@vgfenceproducts.com">info@vgfenceproducts.com</a>
|
||||||
<a href="https://vgfence.com" target="_blank" rel="noopener noreferrer">vgfence.com</a>
|
<a href="https://vgfence.com" target="_blank" rel="noopener noreferrer">vgfence.com</a>
|
||||||
|
<Link href="/contact">Contact us</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Products */}
|
||||||
<div>
|
<div>
|
||||||
<div className="footer-col-title">Products</div>
|
<div className="footer-col-title">Products</div>
|
||||||
<ul className="footer-links">
|
<ul className="footer-links">
|
||||||
<li><Link href="/products">Aluminum railing</Link></li>
|
<li><Link href="/products/aluminum-railing">Aluminum railing</Link></li>
|
||||||
<li><Link href="/products">Chain link fence</Link></li>
|
<li><Link href="/products/chain-link-fence">Chain link fence</Link></li>
|
||||||
<li><Link href="/products">Composite fences</Link></li>
|
<li><Link href="/products/composite-fence">Composite fences</Link></li>
|
||||||
<li><Link href="/products">Expert Stain & Seal</Link></li>
|
<li><Link href="/products/expert-stain-seal">Expert Stain & Seal</Link></li>
|
||||||
<li><Link href="/products">Fence armor</Link></li>
|
<li><Link href="/products/fence-armor">Fence armor</Link></li>
|
||||||
<li><Link href="/products">Glass railing</Link></li>
|
<li><Link href="/products/glass-railing">Glass railing</Link></li>
|
||||||
<li><Link href="/products">Ornamental fence</Link></li>
|
<li><Link href="/products/ornamental-fence">Ornamental fence</Link></li>
|
||||||
<li><Link href="/products">Temp fence rental</Link></li>
|
<li><Link href="/rentals">Temp fence rental</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Services + Cities */}
|
||||||
<div>
|
<div>
|
||||||
<div className="footer-col-title">Services</div>
|
<div className="footer-col-title">Services</div>
|
||||||
<ul className="footer-links">
|
<ul className="footer-links">
|
||||||
<li><Link href="/manufacturing">Custom Manufacturing</Link></li>
|
<li><Link href="/services">2D drawing services</Link></li>
|
||||||
<li><Link href="/#services">2D drawing services</Link></li>
|
<li><Link href="/services">Wood staining</Link></li>
|
||||||
<li><Link href="/#services">Wood staining</Link></li>
|
<li><Link href="/rentals">Temporary fence rental</Link></li>
|
||||||
<li><Link href="/#services">Site services</Link></li>
|
<li><Link href="/contact">Job site delivery</Link></li>
|
||||||
<li><Link href="/#services">Job site delivery</Link></li>
|
<li><Link href="/login">Contractor accounts</Link></li>
|
||||||
<li><Link href="/#services">Contractor accounts</Link></li>
|
|
||||||
<li><Link href="/about">About Us</Link></li>
|
|
||||||
<li><Link href="/contact">Contact Us</Link></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
<div className="footer-col-title" style={{ marginTop: '18px' }}>We serve</div>
|
||||||
<div>
|
|
||||||
<div className="footer-col-title">We serve</div>
|
|
||||||
<ul className="footer-links">
|
<ul className="footer-links">
|
||||||
<li><Link href="/#territory">Kitchener · Waterloo</Link></li>
|
<li><Link href="/locations">Kitchener · Waterloo</Link></li>
|
||||||
<li><Link href="/#territory">Cambridge · Guelph</Link></li>
|
<li><Link href="/locations">Cambridge · Guelph</Link></li>
|
||||||
<li><Link href="/#territory">Hamilton · Brantford</Link></li>
|
<li><Link href="/locations">Hamilton · Brantford</Link></li>
|
||||||
<li><Link href="/#territory">London · Toronto</Link></li>
|
<li><Link href="/locations">London · Toronto</Link></li>
|
||||||
<li><Link href="/#territory">Barrie · Owen Sound</Link></li>
|
<li><Link href="/locations">Windsor · Niagara</Link></li>
|
||||||
<li><Link href="/#territory">Niagara Region</Link></li>
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Legal column */}
|
||||||
|
<div>
|
||||||
|
<div className="footer-col-title">Legal & info</div>
|
||||||
|
<ul className="footer-links">
|
||||||
|
<li><Link href="/legal#terms">Terms & Conditions</Link></li>
|
||||||
|
<li><Link href="/legal#privacy">Privacy Policy</Link></li>
|
||||||
|
<li><Link href="/contact">Contact Us</Link></li>
|
||||||
|
<li><Link href="/login">Contractor Login</Link></li>
|
||||||
|
<li><Link href="/products-and-services">All Products & Services</Link></li>
|
||||||
|
</ul>
|
||||||
|
<div className="footer-col-title" style={{ marginTop: '18px' }}>Hours</div>
|
||||||
|
<ul className="footer-links" style={{ gap: '4px' }}>
|
||||||
|
<li style={{ fontSize: '12px', color: 'rgba(255,255,255,.4)' }}>Mon–Fri: 8:00am – 5:00pm</li>
|
||||||
|
<li style={{ fontSize: '12px', color: 'rgba(255,255,255,.4)' }}>Saturday: 9:00am – 2:00pm</li>
|
||||||
|
<li style={{ fontSize: '12px', color: 'rgba(255,255,255,.4)' }}>Sunday & holidays: Closed</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="footer-bottom">
|
<div className="footer-bottom">
|
||||||
<span>© Copyright {new Date().getFullYear()} VGFence Products Powered by <a href="https://metatroncubesolutions.com/" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--orange)', textDecoration: 'none' }}>MetatronCube</a>. All Right Reserved.</span>
|
<span>© {new Date().getFullYear()} VG Fence Products. All rights reserved. · Kitchener, Ontario, Canada</span>
|
||||||
|
<div className="footer-legal-links">
|
||||||
|
<Link href="/legal#terms">Terms & Conditions</Link>
|
||||||
|
<Link href="/legal#privacy">Privacy Policy</Link>
|
||||||
|
<span>vgfence.com</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,6 +5,30 @@ import ReCAPTCHA from "react-google-recaptcha";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
const slides = [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
eyebrow: "Based in KWC. Delivering everywhere.",
|
||||||
|
title: <>Ontario's B2B<br /><em>Fence Supply</em><br />Partner</>,
|
||||||
|
sub: <>Supplying contractors, builders, and property managers across Ontario with{' '}<strong>chain link, ornamental, composite, glass railing, aluminum railing, Expert Stain & Seal, Fence Armor, and temporary fence rental</strong> — with scheduled job site delivery across a 250km radius from KWC.</>,
|
||||||
|
productValue: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
eyebrow: "Premium Aluminum Railing Systems",
|
||||||
|
title: <>Durable & Stylish<br /><em>Aluminum</em><br />Railing</>,
|
||||||
|
sub: <>High-quality, easy-to-install aluminum railing systems for residential and commercial projects. Available in multiple styles and colors with fast job site delivery across our service area.</>,
|
||||||
|
productValue: "Aluminum railing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
eyebrow: "Low Maintenance Composite Fencing",
|
||||||
|
title: <>Modern & Lasting<br /><em>Composite</em><br />Fencing</>,
|
||||||
|
sub: <>Provide your clients with beautiful, weather-resistant composite fences. Easy installation, long lifespan, and premium aesthetics for any property.</>,
|
||||||
|
productValue: "Composite fence",
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
export default function Hero() {
|
export default function Hero() {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
company: "",
|
company: "",
|
||||||
@ -20,8 +44,27 @@ export default function Hero() {
|
|||||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
||||||
const [alert, setAlert] = useState({ show: false, type: "", message: "" });
|
const [alert, setAlert] = useState({ show: false, type: "", message: "" });
|
||||||
|
|
||||||
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
|
const [userSelectedProduct, setUserSelectedProduct] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setCurrentSlide((prev) => (prev + 1) % slides.length);
|
||||||
|
}, 6000);
|
||||||
|
return () => clearInterval(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!userSelectedProduct) {
|
||||||
|
setFormData((prev) => ({ ...prev, product: slides[currentSlide].productValue }));
|
||||||
|
}
|
||||||
|
}, [currentSlide, userSelectedProduct]);
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
|
if (name === "product") {
|
||||||
|
setUserSelectedProduct(true);
|
||||||
|
}
|
||||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,177 +144,135 @@ export default function Hero() {
|
|||||||
}, [alert.show, alert.type]);
|
}, [alert.show, alert.type]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="hero">
|
<section className="hero" id="home">
|
||||||
<div className="hero-pattern"></div>
|
<div className="hero-grid-bg"></div>
|
||||||
|
<div className="hero-fence-deco" aria-hidden="true">
|
||||||
|
<div className="hero-fence-rail" style={{ top: '32%' }}></div>
|
||||||
|
<div className="hero-fence-rail" style={{ top: '67%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '5%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '16%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '27%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '38%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '49%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '60%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '71%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '82%' }}></div>
|
||||||
|
<div className="hero-fence-post" style={{ left: '93%' }}></div>
|
||||||
|
</div>
|
||||||
<div className="hero-accent"></div>
|
<div className="hero-accent"></div>
|
||||||
<div className="hero-accent2"></div>
|
<div className="hero-accent2"></div>
|
||||||
|
|
||||||
<div className="hero-left">
|
<div className="hero-left">
|
||||||
<div className="hero-eyebrow">Kitchener–Waterloo Region · Ontario</div>
|
<div key={currentSlide} style={{ animation: "fadeUp 0.6s ease-out forwards" }}>
|
||||||
<h1 className="hero-h1">
|
<div className="hero-eyebrow">{slides[currentSlide].eyebrow}</div>
|
||||||
Ontario's B2B<br />
|
<h1 className="hero-h1">{slides[currentSlide].title}</h1>
|
||||||
<em>Fence Supply</em><br />
|
<p className="hero-sub">{slides[currentSlide].sub}</p>
|
||||||
Partner
|
|
||||||
</h1>
|
|
||||||
<p className="hero-sub">
|
|
||||||
Supplying contractors, builders, and property managers across Ontario with{' '}
|
|
||||||
<strong>chain link, ornamental, composite, glass railing, and stain products</strong> —
|
|
||||||
with same-day job site delivery across a 250km radius from KWC.
|
|
||||||
</p>
|
|
||||||
<div className="hero-btns">
|
<div className="hero-btns">
|
||||||
<Link href="#quote" className="btn-primary">Request contractor pricing</Link>
|
<Link href="/contact" className="btn-primary">Request contractor pricing</Link>
|
||||||
<Link href="/products" className="btn-secondary">View all products</Link>
|
<Link href="#products" className="btn-secondary">View all products</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="hero-stats">
|
|
||||||
<div>
|
|
||||||
<div className="stat-val">3+</div>
|
|
||||||
<div className="stat-label">Years serving KWC</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="stat-val">250km</div>
|
|
||||||
<div className="stat-label">Delivery radius</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="stat-val">9+</div>
|
|
||||||
<div className="stat-label">Product lines</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="stat-val">B2B</div>
|
|
||||||
<div className="stat-label">Contractor focus</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="hero-slider-dots" style={{ display: 'flex', gap: '8px', marginTop: '10px' }}>
|
||||||
|
{slides.map((_, i) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={i}
|
||||||
|
onClick={() => setCurrentSlide(i)}
|
||||||
|
className={`hero-slider-dot ${currentSlide === i ? 'active' : ''}`}
|
||||||
|
aria-label={`Go to slide ${i + 1}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hero-right" id="quote">
|
<div className="hero-right" id="quote">
|
||||||
<div className="quote-card">
|
<div className="quote-card">
|
||||||
<div className="quote-card-title">Request a quote</div>
|
<div className="qc-title">Request a quote</div>
|
||||||
<div className="quote-card-sub">Response within 2 business hours · Contractor pricing available</div>
|
<div className="qc-sub">Response within 2 business hours · Contractor pricing available</div>
|
||||||
|
|
||||||
{alert.show && (
|
{alert.show && (
|
||||||
<div className={`alert alert-${alert.type === 'danger' ? 'danger' : (alert.type === 'info' ? 'info' : 'success')} mb-4`} style={{
|
<div className={`hero-alert hero-alert-${alert.type === 'danger' ? 'danger' : (alert.type === 'info' ? 'info' : 'success')}`}>
|
||||||
padding: '12px 16px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
fontSize: '14px',
|
|
||||||
marginBottom: '20px',
|
|
||||||
background: alert.type === 'danger' ? '#fee2e2' : (alert.type === 'info' ? '#e0f2fe' : '#f0fdf4'),
|
|
||||||
color: alert.type === 'danger' ? '#991b1b' : (alert.type === 'info' ? '#075985' : '#166534'),
|
|
||||||
border: `1px solid ${alert.type === 'danger' ? '#fecaca' : (alert.type === 'info' ? '#bae6fd' : '#bbf7d0')}`
|
|
||||||
}}>
|
|
||||||
{alert.message}
|
{alert.message}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<form className="quote-form" onSubmit={handleSubmit}>
|
<form className="quote-form" onSubmit={handleSubmit}>
|
||||||
<div className="form-row">
|
<div className="frow">
|
||||||
<div className="form-group">
|
<div>
|
||||||
<label className="form-label">Company name</label>
|
<label className="fl">Company name</label>
|
||||||
<input
|
<input className="fi" type="text" name="company" placeholder="ABC Fence Co." value={formData.company} onChange={handleChange} />
|
||||||
className="form-input"
|
|
||||||
type="text"
|
|
||||||
name="company"
|
|
||||||
placeholder="ABC Fence Co."
|
|
||||||
value={formData.company}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div>
|
||||||
<label className="form-label">Your name</label>
|
<label className="fl">Your name</label>
|
||||||
<input
|
<input className={`fi ${formErrors.name ? 'border-danger' : ''}`} type="text" name="name" placeholder="John Smith" value={formData.name} onChange={handleChange} />
|
||||||
className="form-input"
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
placeholder="John Smith"
|
|
||||||
value={formData.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ borderColor: formErrors.name ? '#ef4444' : '' }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div className="frow">
|
||||||
<div className="form-group">
|
<div>
|
||||||
<label className="form-label">Phone</label>
|
<label className="fl">Phone</label>
|
||||||
<input
|
<input className={`fi ${formErrors.phone ? 'border-danger' : ''}`} type="tel" name="phone" placeholder="519-xxx-xxxx" value={formData.phone} onChange={handleChange} />
|
||||||
className="form-input"
|
|
||||||
type="tel"
|
|
||||||
name="phone"
|
|
||||||
placeholder="519-xxx-xxxx"
|
|
||||||
value={formData.phone}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ borderColor: formErrors.phone ? '#ef4444' : '' }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div>
|
||||||
<label className="form-label">Email</label>
|
<label className="fl">Email</label>
|
||||||
<input
|
<input className={`fi ${formErrors.email ? 'border-danger' : ''}`} type="email" name="email" placeholder="you@company.com" value={formData.email} onChange={handleChange} />
|
||||||
className="form-input"
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="you@company.com"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ borderColor: formErrors.email ? '#ef4444' : '' }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<label className="fl">Product needed</label>
|
||||||
<label className="form-label">Product needed</label>
|
<select className={`fi ${formErrors.product ? 'border-danger' : ''}`} name="product" value={formData.product} onChange={handleChange}>
|
||||||
<select
|
<option value="">Select a product or service...</option>
|
||||||
className="form-select"
|
<optgroup label="── Products ──">
|
||||||
name="product"
|
|
||||||
value={formData.product}
|
|
||||||
onChange={handleChange}
|
|
||||||
style={{ borderColor: formErrors.product ? '#ef4444' : '' }}
|
|
||||||
>
|
|
||||||
<option value="">Select a product...</option>
|
|
||||||
<option>Aluminum railing</option>
|
<option>Aluminum railing</option>
|
||||||
<option>Chain link fence — commercial</option>
|
<option>Chain link fence — commercial</option>
|
||||||
<option>Chain link fence — residential</option>
|
<option>Chain link fence — residential</option>
|
||||||
<option>Composite fence</option>
|
<option>Composite fence</option>
|
||||||
<option>Expert Stain & Seal products</option>
|
<option>Expert Stain & Seal products</option>
|
||||||
<option>Fence armor (post caps / guards)</option>
|
<option>Fence Armor (post caps / guards)</option>
|
||||||
<option>Glass railing</option>
|
<option>Glass railing</option>
|
||||||
<option>Ornamental / iron fence</option>
|
<option>Ornamental / iron fence</option>
|
||||||
<option>Temporary fence rental</option>
|
</optgroup>
|
||||||
|
<optgroup label="── Services ──">
|
||||||
|
<option>Temporary fence rental — construction</option>
|
||||||
|
<option>Temporary fence rental — event</option>
|
||||||
|
<option>Wood staining service</option>
|
||||||
|
<option>2D fence drawing services</option>
|
||||||
|
<option>Contractor account enquiry</option>
|
||||||
<option>Multiple products</option>
|
<option>Multiple products</option>
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
|
<div className="frow">
|
||||||
|
<div>
|
||||||
|
<label className="fl">Job site city</label>
|
||||||
|
<input className="fi" type="text" name="city" placeholder="Kitchener, Guelph..." value={formData.city} onChange={handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div>
|
||||||
<div className="form-group">
|
<label className="fl">Approx. quantity</label>
|
||||||
<label className="form-label">Job site city</label>
|
<input className="fi" type="text" name="quantity" placeholder="e.g. 200 linear ft" value={formData.quantity} onChange={handleChange} />
|
||||||
<input
|
|
||||||
className="form-input"
|
|
||||||
type="text"
|
|
||||||
name="city"
|
|
||||||
placeholder="Kitchener, Guelph..."
|
|
||||||
value={formData.city}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label">Approximate quantity</label>
|
|
||||||
<input
|
|
||||||
className="form-input"
|
|
||||||
type="text"
|
|
||||||
name="quantity"
|
|
||||||
placeholder="e.g. 200 linear ft"
|
|
||||||
value={formData.quantity}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="tc-row" id="tc-row-home">
|
||||||
|
<input type="checkbox" className="tc-check" id="tc-home" required />
|
||||||
|
<label className="tc-label" htmlFor="tc-home">
|
||||||
|
I have read and agree to VG Fence Products'
|
||||||
|
<a href="/legal#t1" target="_blank">Terms & Conditions</a>,
|
||||||
|
<a href="/legal#t3" target="_blank">Delivery Policy</a>, and
|
||||||
|
<a href="/legal#t7" target="_blank">Photo Verification (24hr) Policy</a>.
|
||||||
|
I understand that damage claims require photos submitted within 24 hours of delivery.
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group" style={{ marginTop: '16px' }}>
|
||||||
<ReCAPTCHA
|
<ReCAPTCHA
|
||||||
sitekey="6LekfpwrAAAAAOTwuP1d2gg-Fv9UEsAjE2gjOQJl"
|
sitekey="6LekfpwrAAAAAOTwuP1d2gg-Fv9UEsAjE2gjOQJl"
|
||||||
onChange={handleCaptchaChange}
|
onChange={handleCaptchaChange}
|
||||||
/>
|
/>
|
||||||
{formErrors.captcha && <small style={{ color: '#ef4444', fontSize: '11px', marginTop: '4px', display: 'block' }}>{formErrors.captcha}</small>}
|
{formErrors.captcha && <small className="text-danger" style={{ fontSize: '11px', marginTop: '4px', display: 'block' }}>{formErrors.captcha}</small>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" className="form-submit" disabled={alert.type === "info"}>
|
<button type="submit" className="form-submit" disabled={alert.type === "info"}>
|
||||||
{alert.type === "info" ? "Sending..." : "Send quote request →"}
|
{alert.type === "info" ? "Sending..." : "Send quote request →"}
|
||||||
</button>
|
</button>
|
||||||
<div className="form-note">Or call us directly · info@vgfenceproducts.com</div>
|
<div className="form-note">Or email us · info@vgfenceproducts.com</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { useRouter } from 'next/navigation';
|
|||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [mobileProductsOpen, setMobileProductsOpen] = useState(false);
|
||||||
const [isLogged, setIsLogged] = useState(false);
|
const [isLogged, setIsLogged] = useState(false);
|
||||||
const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);
|
const [showLogoutConfirm, setShowLogoutConfirm] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -40,97 +41,102 @@ export default function Navbar() {
|
|||||||
const toggleMenu = () => setIsOpen(!isOpen);
|
const toggleMenu = () => setIsOpen(!isOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={isOpen ? "nav-active" : ""}>
|
<>
|
||||||
|
<nav>
|
||||||
<Link href="/" className="nav-logo">
|
<Link href="/" className="nav-logo">
|
||||||
<img src="/assets/vg-fence.png" alt="VG Fence Products" style={{ height: '40px', width: 'auto' }} />
|
{/* <div className="lm">VG</div> */}
|
||||||
|
<img src="/assets/vg-fence.png" alt="VG Fence Products" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Hamburger Menu Toggle */}
|
<ul className="nav-links">
|
||||||
<button className="menu-toggle" onClick={toggleMenu} aria-label="Toggle Menu">
|
<li className="nav-dropdown-wrap">
|
||||||
<div className={isOpen ? "hamburger active" : "hamburger"}>
|
<Link href="#">Products <span className="chevron-down">⌵</span></Link>
|
||||||
<span></span>
|
<ul className="nav-dropdown">
|
||||||
<span></span>
|
<li><Link href="/products/chain-link-fence">Chain Link Fence</Link></li>
|
||||||
<span></span>
|
{/* <li><Link href="#">Aluminum Railing</Link></li>
|
||||||
</div>
|
<li><Link href="#">Composite Fences</Link></li>
|
||||||
</button>
|
<li><Link href="#">Glass Railing</Link></li>
|
||||||
|
<li><Link href="#">Ornamental Fence</Link></li>
|
||||||
<ul className={isOpen ? "nav-links active" : "nav-links"}>
|
<li><Link href="#">Expert Stain & Seal</Link></li>
|
||||||
<li><Link href="/" onClick={() => setIsOpen(false)}>Home</Link></li>
|
<li><Link href="#">Fence Armor</Link></li>
|
||||||
<li><Link href="/about" onClick={() => setIsOpen(false)}>About</Link></li>
|
<li><Link href="#">Temporary Fence</Link></li> */}
|
||||||
<li><Link href="/products" onClick={() => setIsOpen(false)}>Products</Link></li>
|
</ul>
|
||||||
<li><Link href="/manufacturing" onClick={() => setIsOpen(false)}>Manufacturing</Link></li>
|
|
||||||
<li><Link href="/rentals" onClick={() => setIsOpen(false)}>Rentals</Link></li>
|
|
||||||
<li><Link href="/blog" onClick={() => setIsOpen(false)}>Blog</Link></li>
|
|
||||||
<li><Link href="/contact" onClick={() => setIsOpen(false)}>Contact</Link></li>
|
|
||||||
<li className="mobile-cta-li">
|
|
||||||
{isLogged ? (
|
|
||||||
<button onClick={handleLogoutClick} className="nav-cta" style={{ width: '100%', justifyContent: 'center', appearance: 'none', border: 'none', cursor: 'pointer' }}>
|
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<Link href="/login" className="nav-cta" onClick={() => setIsOpen(false)} style={{ width: '100%', justifyContent: 'center' }}>
|
|
||||||
Login
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</li>
|
</li>
|
||||||
|
<li><Link href="/services">Services</Link></li>
|
||||||
|
<li><Link href="/who-we-serve">Who We Serve</Link></li>
|
||||||
|
<li><Link href="/contact">Contact</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div className="nav-right">
|
||||||
{isLogged ? (
|
{isLogged ? (
|
||||||
<button onClick={handleLogoutClick} className="nav-cta desktop-cta" style={{ appearance: 'none', border: 'none', cursor: 'pointer' }}>
|
<button onClick={handleLogoutClick} className="nav-login" style={{ background: 'none', cursor: 'pointer' }}>Logout</button>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
) : (
|
) : (
|
||||||
<Link href="/login" className="nav-cta desktop-cta">
|
<Link href="/login" className="nav-login">Contractor Login</Link>
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
|
||||||
Login
|
|
||||||
</Link>
|
|
||||||
)}
|
)}
|
||||||
|
<Link href="/contact" className="nav-cta">Get a Quote</Link>
|
||||||
|
<button className="nav-hamburger" onClick={toggleMenu}>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className={`mobile-menu ${isOpen ? 'open' : ''}`}>
|
||||||
|
<div className="mobile-dropdown-section">
|
||||||
|
<button
|
||||||
|
className="mobile-dropdown-btn"
|
||||||
|
onClick={() => setMobileProductsOpen(!mobileProductsOpen)}
|
||||||
|
>
|
||||||
|
Products
|
||||||
|
<span className={`chevron-down ${mobileProductsOpen ? 'rotated' : ''}`}>⌵</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className={`mobile-dropdown-content ${mobileProductsOpen ? 'show' : ''}`}>
|
||||||
|
<Link href="/products/chain-link-fence" onClick={() => setIsOpen(false)}>Chain Link Fence</Link>
|
||||||
|
{/* <Link href="#" onClick={() => setIsOpen(false)}>Aluminum Railing</Link>
|
||||||
|
<Link href="#" onClick={() => setIsOpen(false)}>Composite Fences</Link>
|
||||||
|
<Link href="#" onClick={() => setIsOpen(false)}>Glass Railing</Link>
|
||||||
|
<Link href="#" onClick={() => setIsOpen(false)}>Ornamental Fence</Link> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Link href="/services" onClick={() => setIsOpen(false)}>Services</Link>
|
||||||
|
<Link href="/who-we-serve" onClick={() => setIsOpen(false)}>Who We Serve</Link>
|
||||||
|
<Link href="/contact" onClick={() => setIsOpen(false)}>Contact</Link>
|
||||||
|
|
||||||
|
<div className="mobile-menu-actions">
|
||||||
|
{isLogged ? (
|
||||||
|
<button onClick={() => { performLogout(); setIsOpen(false); }} className="mobile-login-btn">Logout</button>
|
||||||
|
) : (
|
||||||
|
<Link href="/login" onClick={() => setIsOpen(false)} className="mobile-login-btn">Contractor Login</Link>
|
||||||
|
)}
|
||||||
|
<Link href="/contact" className="mobile-cta" onClick={() => setIsOpen(false)}>Get a Quote</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Logout Confirmation Modal */}
|
|
||||||
{showLogoutConfirm && (
|
{showLogoutConfirm && (
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'fixed',
|
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
|
||||||
top: 0, left: 0, right: 0, bottom: 0,
|
backgroundColor: 'rgba(0,10,30,0.7)', display: 'flex',
|
||||||
backgroundColor: 'rgba(0,10,30,0.7)',
|
justifyContent: 'center', alignItems: 'center', zIndex: 99999,
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
zIndex: 99999,
|
|
||||||
backdropFilter: 'blur(4px)'
|
backdropFilter: 'blur(4px)'
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
background: '#ffffff',
|
background: '#ffffff', padding: '32px', borderRadius: '12px',
|
||||||
padding: '32px',
|
width: '90%', maxWidth: '400px', boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||||
borderRadius: '12px',
|
|
||||||
width: '90%',
|
|
||||||
maxWidth: '400px',
|
|
||||||
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
}}>
|
}}>
|
||||||
<h3 style={{ fontSize: '24px', fontWeight: 800, color: 'var(--navy)', marginBottom: '12px', fontFamily: 'var(--font-display)' }}>Confirm Logout</h3>
|
<h3 style={{ fontSize: '24px', fontWeight: 800, color: 'var(--navy)', marginBottom: '12px', fontFamily: 'var(--fd)' }}>Confirm Logout</h3>
|
||||||
<p style={{ color: 'var(--gray-600)', marginBottom: '32px', fontSize: '15px', lineHeight: 1.5 }}>
|
<p style={{ color: 'var(--gray-600)', marginBottom: '32px', fontSize: '15px', lineHeight: 1.5 }}>
|
||||||
Are you sure you want to log out of your account?
|
Are you sure you want to log out of your account?
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: 'flex', gap: '16px', justifyContent: 'center' }}>
|
<div style={{ display: 'flex', gap: '16px', justifyContent: 'center' }}>
|
||||||
<button
|
<button onClick={() => setShowLogoutConfirm(false)} className="btn-secondary" style={{ flex: 1, padding: '12px', border: '1px solid var(--gray-300)', color: 'var(--gray-700)', borderRadius: '6px', fontWeight: 600, cursor: 'pointer' }}>Cancel</button>
|
||||||
onClick={() => setShowLogoutConfirm(false)}
|
<button onClick={performLogout} className="btn-primary" style={{ flex: 1, padding: '12px', background: 'var(--orange)', color: '#fff', border: 'none', borderRadius: '6px', fontWeight: 600, cursor: 'pointer' }}>Logout</button>
|
||||||
className="btn-secondary"
|
|
||||||
style={{ flex: 1, padding: '12px', border: '1px solid var(--gray-300)', color: 'var(--gray-700)', borderRadius: '6px', fontWeight: 600, cursor: 'pointer' }}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={performLogout}
|
|
||||||
className="btn-primary"
|
|
||||||
style={{ flex: 1, padding: '12px', background: 'var(--orange)', color: '#fff', border: 'none', borderRadius: '6px', fontWeight: 600, cursor: 'pointer' }}
|
|
||||||
>
|
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</nav>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
19
components/Newsletter.tsx
Normal file
19
components/Newsletter.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export default function Newsletter() {
|
||||||
|
return (
|
||||||
|
<section className="newsletter-section">
|
||||||
|
<div className="newsletter-inner reveal">
|
||||||
|
<div className="section-eyebrow center">Stay in the loop</div>
|
||||||
|
<h2 className="newsletter-h2">Product Updates &<br /><span>Contractor Deals.</span></h2>
|
||||||
|
<p className="newsletter-sub">
|
||||||
|
New product arrivals, seasonal promotions, and industry tips — delivered to your inbox.<br />
|
||||||
|
No spam, unsubscribe anytime.
|
||||||
|
</p>
|
||||||
|
<form className="newsletter-form" action="#" method="POST">
|
||||||
|
<input type="email" className="newsletter-input" placeholder="Enter your email address" required />
|
||||||
|
<button type="submit" className="newsletter-btn">Subscribe →</button>
|
||||||
|
</form>
|
||||||
|
<div className="newsletter-note">Join contractors and builders across Ontario · 1–6 emails per month</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -3,85 +3,118 @@ import Link from 'next/link';
|
|||||||
export default function Products() {
|
export default function Products() {
|
||||||
const products = [
|
const products = [
|
||||||
{
|
{
|
||||||
name: "Aluminum railing",
|
name: "Aluminum Railing",
|
||||||
desc: "Residential and commercial aluminum railing systems for decks, balconies, stairs, and pools.",
|
desc: "Residential and commercial aluminum railing for decks, balconies, stairs, and pools. Multiple profiles and finishes.",
|
||||||
tags: ["Residential", "Commercial"],
|
tags: ["Residential", "Commercial", "Pool-safe"],
|
||||||
|
badge: "Railing",
|
||||||
|
img: "/assets/images/aluminum-railing.jpg",
|
||||||
|
href: "#",
|
||||||
icon: (
|
icon: (
|
||||||
<svg viewBox="0 0 22 22" fill="none"><rect x="1" y="3" width="3" height="16" rx="1.5" fill="white" /><rect x="9" y="3" width="3" height="16" rx="1.5" fill="white" /><rect x="17" y="3" width="3" height="16" rx="1.5" fill="white" /><line x1="1" y1="8" x2="20" y2="8" stroke="white" strokeWidth="1.5" /><line x1="1" y1="13" x2="20" y2="13" stroke="white" strokeWidth="1.5" /></svg>
|
<svg viewBox="0 0 22 22" fill="none"><rect x="1" y="3" width="3" height="16" rx="1.5" fill="white"/><rect x="9" y="3" width="3" height="16" rx="1.5" fill="white"/><rect x="17" y="3" width="3" height="16" rx="1.5" fill="white"/><line x1="1" y1="8" x2="20" y2="8" stroke="white" strokeWidth="1.5"/><line x1="1" y1="13" x2="20" y2="13" stroke="white" strokeWidth="1.5"/></svg>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Chain link fence",
|
name: "Chain Link Fence",
|
||||||
desc: "Commercial and residential chain link in black and galvanized finishes. High volume, quick delivery.",
|
desc: "Commercial and residential chain link in black and galvanized finishes. All gauges, heights, posts, gates, and hardware in stock.",
|
||||||
tags: ["Black", "Galvanized", "Commercial", "Residential"],
|
tags: ["Black", "Galvanized", "Commercial", "Residential"],
|
||||||
|
badge: "Fencing",
|
||||||
|
img: "/assets/images/chain-link-fence.jpg",
|
||||||
|
href: "/products/chain-link-fence",
|
||||||
icon: (
|
icon: (
|
||||||
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="3" width="3" height="16" rx="1.5" fill="white" /><rect x="17" y="3" width="3" height="16" rx="1.5" fill="white" /><line x1="2" y1="8" x2="20" y2="8" stroke="white" strokeWidth="1.5" /><line x1="2" y1="13" x2="20" y2="13" stroke="white" strokeWidth="1.5" /><line x1="8" y1="3" x2="8" y2="19" stroke="white" strokeWidth="1.5" /><line x1="14" y1="3" x2="14" y2="19" stroke="white" strokeWidth="1.5" /></svg>
|
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="3" width="3" height="16" rx="1.5" fill="white"/><rect x="17" y="3" width="3" height="16" rx="1.5" fill="white"/><line x1="2" y1="8" x2="20" y2="8" stroke="white" strokeWidth="1.5"/><line x1="2" y1="13" x2="20" y2="13" stroke="white" strokeWidth="1.5"/><line x1="8" y1="3" x2="8" y2="19" stroke="white" strokeWidth="1.5"/><line x1="14" y1="3" x2="14" y2="19" stroke="white" strokeWidth="1.5"/></svg>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Composite fences",
|
name: "Composite Fences",
|
||||||
desc: "Low maintenance composite fence panels in three premium colours. Natural wood look, zero rot.",
|
desc: "Low-maintenance composite panels in three premium colours. Natural wood look, zero rot, no painting, no splitting.",
|
||||||
tags: ["Ancient Wood", "Golden Teak", "Anthracite Grey"],
|
tags: ["Ancient Wood", "Golden Teak", "Anthracite Grey"],
|
||||||
|
badge: "Fencing",
|
||||||
|
img: "/assets/images/composite-fence.jpg",
|
||||||
|
href: "#",
|
||||||
icon: (
|
icon: (
|
||||||
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="2" width="6" height="18" rx="2" fill="white" opacity=".7" /><rect x="14" y="2" width="6" height="18" rx="2" fill="white" opacity=".7" /><rect x="5" y="2" width="12" height="18" rx="2" fill="white" opacity=".35" /></svg>
|
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="2" width="6" height="18" rx="2" fill="white" opacity=".7"/><rect x="14" y="2" width="6" height="18" rx="2" fill="white" opacity=".7"/><rect x="5" y="2" width="12" height="18" rx="2" fill="white" opacity=".35"/></svg>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Glass railing",
|
name: "Glass Railing",
|
||||||
desc: "Premium tempered glass railing for pools, decks, balconies, and commercial applications.",
|
desc: "3 systems — post-mount, standoff/spigot, fascia-mount. 10mm & 12mm tempered glass. Pool-compliant configurations available.",
|
||||||
tags: ["Pool-safe", "Residential", "Commercial"],
|
tags: ["Pool-safe", "Residential", "Commercial"],
|
||||||
|
badge: "Railing",
|
||||||
|
img: "/assets/images/glass-railing.jpg",
|
||||||
|
href: "#",
|
||||||
icon: (
|
icon: (
|
||||||
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="6" width="18" height="11" rx="2" fill="white" opacity=".25" stroke="white" strokeWidth="1.5" /><rect x="4" y="2" width="2" height="18" rx="1" fill="white" /><rect x="16" y="2" width="2" height="18" rx="1" fill="white" /></svg>
|
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="6" width="18" height="11" rx="2" fill="white" opacity=".25" stroke="white" strokeWidth="1.5"/><rect x="4" y="2" width="2" height="18" rx="1" fill="white"/><rect x="16" y="2" width="2" height="18" rx="1" fill="white"/></svg>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Ornamental fence",
|
name: "Ornamental Fence",
|
||||||
desc: "Classic black ornamental iron fence for residential properties. Elegant, durable, and timeless.",
|
desc: "4 models — Tokio, Rio, Denver, Oslo. Rackable panels in 48″ and 60″ heights. Matching gates, posts, caps, and hardware.",
|
||||||
tags: ["Black", "Residential"],
|
tags: ["Tokio", "Rio", "Denver", "Oslo"],
|
||||||
|
badge: "Fencing",
|
||||||
|
img: "/assets/images/ornamental-fence.jpg",
|
||||||
|
href: "#",
|
||||||
icon: (
|
icon: (
|
||||||
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="3" width="3" height="16" rx="1.5" fill="white" /><rect x="17" y="3" width="3" height="16" rx="1.5" fill="white" /><line x1="2" y1="7" x2="20" y2="7" stroke="white" strokeWidth="1.5" /><line x1="2" y1="11" x2="20" y2="11" stroke="white" strokeWidth="1.5" /><line x1="2" y1="15" x2="20" y2="15" stroke="white" strokeWidth="1.5" /></svg>
|
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="3" width="3" height="16" rx="1.5" fill="white"/><rect x="17" y="3" width="3" height="16" rx="1.5" fill="white"/><line x1="2" y1="7" x2="20" y2="7" stroke="white" strokeWidth="1.5"/><line x1="2" y1="11" x2="20" y2="11" stroke="white" strokeWidth="1.5"/><line x1="2" y1="15" x2="20" y2="15" stroke="white" strokeWidth="1.5"/></svg>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Expert Stain & Seal",
|
name: "Expert Stain & Seal",
|
||||||
desc: "Professional-grade wood care products for fences, decks, pergolas, and outdoor structures.",
|
desc: "Professional-grade wood care products for fences, decks, pergolas, and outdoor structures. Contractor pricing and bulk ordering.",
|
||||||
tags: ["Stain & Seal", "Clean & Bright", "High margin"],
|
tags: ["Stain & Seal", "Clean & Bright", "High margin"],
|
||||||
|
badge: "Wood Care",
|
||||||
|
img: "/assets/images/expert-stain-seal.jpg",
|
||||||
|
href: "#",
|
||||||
icon: (
|
icon: (
|
||||||
<svg viewBox="0 0 22 22" fill="none"><path d="M4 19 L11 3 L18 19" stroke="white" strokeWidth="2" fill="none" strokeLinecap="round" /><circle cx="11" cy="11" r="3.5" fill="white" opacity=".5" /></svg>
|
<svg viewBox="0 0 22 22" fill="none"><path d="M4 19 L11 3 L18 19" stroke="white" strokeWidth="2" fill="none" strokeLinecap="round"/><circle cx="11" cy="11" r="3.5" fill="white" opacity=".5"/></svg>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Fence armor",
|
name: "Fence Armor",
|
||||||
desc: "Post protection accessories to extend the life of any fence installation. Post caps, guards, and rot barriers.",
|
desc: "Post caps (4×4, 6×6), post guards (11 sizes), rot barrier, RotGuard, PostSaver sleeves, and 7″ pro stain brush. Black steel.",
|
||||||
tags: ["Post Caps", "Post Guards", "Rot Barrier"],
|
tags: ["Post Caps", "Post Guards", "Rot Barrier"],
|
||||||
|
badge: "Accessories",
|
||||||
|
img: "/assets/images/fence-armor.jpg",
|
||||||
|
href: "#",
|
||||||
icon: (
|
icon: (
|
||||||
<svg viewBox="0 0 22 22" fill="none"><circle cx="7" cy="4" r="3" fill="white" /><rect x="5" y="7" width="4" height="11" rx="1" fill="white" /><rect x="3" y="11" width="8" height="2" rx="1" fill="white" opacity=".5" /></svg>
|
<svg viewBox="0 0 22 22" fill="none"><circle cx="7" cy="4" r="3" fill="white"/><rect x="5" y="7" width="4" height="11" rx="1" fill="white"/><rect x="3" y="11" width="8" height="2" rx="1" fill="white" opacity=".5"/></svg>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Temporary fence rentals",
|
name: "Temporary Fence Rentals",
|
||||||
desc: "Construction site fencing, crowd control, and event barriers available for short or long-term rental.",
|
desc: "Construction, demolition, events, beer gardens, festivals, and emergencies. 6×10 and 6×5 panels, gates, and screens. Same-day KWC.",
|
||||||
tags: ["Construction", "Events", "Rental"],
|
tags: ["Construction", "Events", "Rental"],
|
||||||
|
badge: "Rental",
|
||||||
|
img: "/assets/images/temporary-fence.jpg",
|
||||||
|
href: "#",
|
||||||
icon: (
|
icon: (
|
||||||
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="5" width="5" height="13" rx="1.5" fill="white" /><rect x="15" y="5" width="5" height="13" rx="1.5" fill="white" /><rect x="6" y="7" width="10" height="2" rx="1" fill="white" opacity=".7" /><rect x="6" y="13" width="10" height="2" rx="1" fill="white" opacity=".7" /></svg>
|
<svg viewBox="0 0 22 22" fill="none"><rect x="2" y="5" width="5" height="13" rx="1.5" fill="white"/><rect x="15" y="5" width="5" height="13" rx="1.5" fill="white"/><rect x="6" y="7" width="10" height="2" rx="1" fill="white" opacity=".7"/><rect x="6" y="13" width="10" height="2" rx="1" fill="white" opacity=".7"/></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="products-section" id="products">
|
<section className="products-section" id="products">
|
||||||
<div className="products-header">
|
<div className="products-header reveal">
|
||||||
<div>
|
<div>
|
||||||
<div className="section-eyebrow">Our products</div>
|
<div className="section-eyebrow">Our products</div>
|
||||||
<h2 className="section-h2">Everything you<br />need to <span>build.</span></h2>
|
<h2 className="sh">Everything you<br/>need to <span>build.</span></h2>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ fontSize: '15px', color: 'var(--gray-600)', maxWidth: '340px', lineHeight: '1.7' }}>
|
<p className="products-header-right">Eight product lines in stock, ready for scheduled delivery to your job site anywhere in our 250km Ontario radius.</p>
|
||||||
Nine product lines in stock, ready for same-day or scheduled delivery to your job site anywhere in our 250km radius.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="products-grid">
|
|
||||||
|
<div className="products-grid reveal">
|
||||||
{products.map((product, index) => (
|
{products.map((product, index) => (
|
||||||
<Link href="/products" key={index} className="product-card">
|
<Link href={product.href} key={index} className="product-card">
|
||||||
|
<div className="product-img-area">
|
||||||
|
<img src={product.img} alt={product.name} />
|
||||||
|
<div className="img-ph">
|
||||||
|
<svg width="36" height="36" viewBox="0 0 36 36" fill="none"><rect x="3" y="5" width="30" height="22" rx="2.5" stroke="#B0ADA6" strokeWidth="1.5" fill="none"/><circle cx="12" cy="13" r="4" stroke="#B0ADA6" strokeWidth="1.2" fill="none"/><path d="M3 22 L11 15 L18 21 L24 15 L33 22" stroke="#B0ADA6" strokeWidth="1.2" fill="none"/></svg>
|
||||||
|
<div className="img-ph-label">{product.name}</div>
|
||||||
|
<div className="img-ph-hint">{product.img} · 800×450px</div>
|
||||||
|
</div>
|
||||||
|
<div className="img-badge">{product.badge}</div>
|
||||||
|
</div>
|
||||||
|
<div className="product-card-body">
|
||||||
<div className="product-icon">
|
<div className="product-icon">
|
||||||
{product.icon}
|
{product.icon}
|
||||||
</div>
|
</div>
|
||||||
@ -93,17 +126,18 @@ export default function Products() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="product-arrow">→</div>
|
<div className="product-arrow">→</div>
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="product-card" style={{ background: 'var(--navy)' }}>
|
<div className="product-card product-cta-card">
|
||||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-between' }}>
|
<div className="product-card-body" style={{ justifyContent: 'space-between', height: '100%', minHeight: '200px' }}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontFamily: 'var(--font-display)', fontSize: '13px', fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--orange)', marginBottom: '16px' }}>Need a custom quote?</div>
|
<div style={{ fontFamily: 'var(--fd)', fontSize: '13px', fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', color: 'var(--orange)', marginBottom: '14px' }}>Need a custom quote?</div>
|
||||||
<div style={{ fontFamily: 'var(--font-display)', fontSize: '26px', fontWeight: 800, textTransform: 'uppercase', color: 'var(--white)', lineHeight: 1.1, marginBottom: '12px' }}>Talk to our<br />supply team.</div>
|
<div style={{ fontFamily: 'var(--fd)', fontSize: '24px', fontWeight: 800, textTransform: 'uppercase', color: 'var(--white)', lineHeight: 1.1, marginBottom: '10px' }}>Talk to our<br/>supply team.</div>
|
||||||
<div style={{ fontSize: '13px', color: 'rgba(255,255,255,.55)', lineHeight: 1.6 }}>Bulk orders · Contractor accounts · Scheduled deliveries · Job site planning</div>
|
<div style={{ fontSize: '12px', color: 'rgba(255,255,255,.5)', lineHeight: 1.6 }}>Bulk orders · Contractor accounts · Scheduled deliveries · Job site planning</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#quote" className="btn-primary" style={{ textAlign: 'center', marginTop: '24px', display: 'block' }}>Request pricing →</a>
|
<a href="/contact" className="btn-primary" style={{ textAlign: 'center', marginTop: '20px', display: 'block' }}>Request pricing →</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,74 +2,64 @@ export default function Services() {
|
|||||||
const services = [
|
const services = [
|
||||||
{
|
{
|
||||||
num: "01",
|
num: "01",
|
||||||
name: "2D Fence Drawing Services",
|
name: "2D Fence",
|
||||||
|
span: "Drawing Services",
|
||||||
desc: "CAD-based fence layout drawings for residential and commercial projects. Perfect for permits, planning, and contractor submissions.",
|
desc: "CAD-based fence layout drawings for residential and commercial projects. Perfect for permits, planning, and contractor submissions.",
|
||||||
list: [
|
bullets: [
|
||||||
"Residential & commercial layouts",
|
"Residential & commercial layouts", "Chain link, wood & aluminum drawings",
|
||||||
"Chain link, wood, aluminum drawings",
|
"Gate placement & access planning", "Material takeoff & quantity estimation",
|
||||||
"Gate placement & access planning",
|
"Permit support drawings", "Property boundary fence design"
|
||||||
"Material takeoff & quantity estimation",
|
|
||||||
"Permit support drawings",
|
|
||||||
"Property boundary fence design"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
num: "02",
|
num: "02",
|
||||||
name: "Wood Staining Services",
|
name: "Wood",
|
||||||
|
span: "Staining Services",
|
||||||
desc: "Professional application of Expert Stain & Seal products on fences, decks, pergolas, and outdoor structures.",
|
desc: "Professional application of Expert Stain & Seal products on fences, decks, pergolas, and outdoor structures.",
|
||||||
list: [
|
bullets: [
|
||||||
"Fence, deck & pergola staining",
|
"Fence, deck & pergola staining", "New wood preparation & staining",
|
||||||
"New wood preparation & staining",
|
"Old wood restoration & re-staining", "Cedar & pressure treated staining",
|
||||||
"Old wood restoration & re-staining",
|
"UV & waterproof sealing", "Color matching services"
|
||||||
"Cedar & pressure treated staining",
|
|
||||||
"UV & waterproof sealing",
|
|
||||||
"Color matching services"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
num: "03",
|
num: "03",
|
||||||
name: "Temporary & Site Services",
|
name: "Temporary &",
|
||||||
desc: "Complete temporary fencing solutions for construction sites, events, and emergency situations across Ontario.",
|
span: "Site Services",
|
||||||
list: [
|
desc: "Complete temporary fencing solutions for construction sites, events, and emergencies across Ontario.",
|
||||||
"Temporary fence rental setup",
|
bullets: [
|
||||||
"Construction site fence management",
|
"Temporary fence rental setup", "Construction site fence management",
|
||||||
"Site safety barrier installation",
|
"Beer gardens, festivals & events", "Emergency fence same-day KWC"
|
||||||
"Emergency fence repair service"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
num: "04",
|
num: "04",
|
||||||
name: "Delivery & Logistics",
|
name: "Delivery &",
|
||||||
desc: "Reliable material delivery across our 250km service radius. Same-day local, bulk job site, and scheduled contractor deliveries.",
|
span: "Logistics",
|
||||||
list: [
|
desc: "Reliable material delivery across our 250km service radius with contractor accounts for streamlined ordering.",
|
||||||
"Same-day delivery (local KWC)",
|
bullets: [
|
||||||
"Bulk job site delivery",
|
"Scheduled job site delivery", "Bulk job site delivery",
|
||||||
"Scheduled contractor deliveries",
|
"Contractor account ordering", "250km Ontario radius from KWC"
|
||||||
"Small order pickup support"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="services-section" id="services">
|
<section className="services-section" id="services">
|
||||||
<div className="section-eyebrow" style={{ color: 'rgba(255,255,255,.5)' }}>
|
<div className="reveal">
|
||||||
<span style={{ background: 'rgba(255,255,255,.1)', width: '24px', height: '2px', display: 'inline-block', verticalAlign: 'middle', marginRight: '8px' }}></span>
|
<div className="section-eyebrow">What we do</div>
|
||||||
What we do
|
<h2 className="sh white">More than just <span>supply.</span></h2>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="section-h2">More than just <span>supply.</span></h2>
|
|
||||||
<div className="services-grid">
|
<div className="services-grid reveal">
|
||||||
{services.map((service, index) => (
|
{services.map((service, index) => (
|
||||||
<div key={index} className="service-card">
|
<div key={index} className="service-card">
|
||||||
<div className="service-num">{service.num}</div>
|
<div className="service-num">{service.num}</div>
|
||||||
<div className="service-name">
|
<div className="service-name">{service.name} <span>{service.span}</span></div>
|
||||||
{service.name.split(' ').map((word, wIndex) => (
|
|
||||||
wIndex >= 1 ? <span key={wIndex}> {word}</span> : word
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="service-desc">{service.desc}</div>
|
<div className="service-desc">{service.desc}</div>
|
||||||
<ul className="service-list">
|
<ul className="service-list">
|
||||||
{service.list.map((item, lIndex) => (
|
{service.bullets.map((bullet, bIndex) => (
|
||||||
<li key={lIndex}>{item}</li>
|
<li key={bIndex}>{bullet}</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,17 +3,18 @@ export default function StainPromo() {
|
|||||||
<section className="stain-section">
|
<section className="stain-section">
|
||||||
<div className="stain-bg"></div>
|
<div className="stain-bg"></div>
|
||||||
<div className="stain-layout">
|
<div className="stain-layout">
|
||||||
<div>
|
<div className="reveal">
|
||||||
<div className="stain-badge">New — high margin product line</div>
|
<div className="stain-badge">High-Margin Product Line</div>
|
||||||
<h2 className="stain-h2">Expert Stain<br /><span>& Seal</span></h2>
|
<h2 className="stain-h2">Expert Stain<br /><span>& Seal</span></h2>
|
||||||
<p className="stain-desc">Professional-grade wood care products built for contractors. Two products, maximum results — now available through VG Fence Products with contractor pricing and bulk ordering.</p>
|
<p className="stain-desc">Professional-grade wood care products built for contractors. Maximum results with less labor — now available in Ontario through VG Fence Products with contractor pricing and bulk ordering.</p>
|
||||||
<div className="product-pills">
|
|
||||||
<div className="product-pill"><strong>Product 1</strong>Stain & Seal</div>
|
<div className="stain-pills">
|
||||||
<div className="product-pill"><strong>Product 2</strong>Clean & Bright</div>
|
<div className="stain-pill"><strong>Staining</strong>Stain & Seal</div>
|
||||||
|
<div className="stain-pill"><strong>Prep</strong>Clean & Bright</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div className="stain-targets">
|
<div className="stain-promo-eyebrow">WHO ORDERS FROM US</div>
|
||||||
<div style={{ fontFamily: 'var(--font-display)', fontSize: '14px', fontWeight: 700, letterSpacing: '.08em', textTransform: 'uppercase', color: 'rgba(255,255,255,.4)', marginBottom: '4px' }}>Who orders from us</div>
|
<div className="stain-target-list">
|
||||||
{[
|
{[
|
||||||
"Deck & fence staining contractors",
|
"Deck & fence staining contractors",
|
||||||
"Exterior painters & handymen",
|
"Exterior painters & handymen",
|
||||||
@ -27,7 +28,17 @@ export default function StainPromo() {
|
|||||||
<div className="stain-target-txt">{item}</div>
|
<div className="stain-target-txt">{item}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<a href="#quote" className="btn-primary" style={{ marginTop: '8px', textAlign: 'center', display: 'block' }}>Get stain product pricing →</a>
|
</div>
|
||||||
|
<a href="/contact" className="btn-primary mt-32 d-inline-block">GET STAIN PRODUCT PRICING →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="stain-img-wrap reveal">
|
||||||
|
<img src="/assets/images/stain-product.jpg" alt="Expert Stain & Seal Products" />
|
||||||
|
<div className="stain-img-ph" aria-hidden="true">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 36 36" fill="none"><rect x="3" y="5" width="30" height="22" rx="2.5" stroke="rgba(255,255,255,.15)" strokeWidth="1.5" fill="none"/><circle cx="12" cy="13" r="4" stroke="rgba(255,255,255,.15)" strokeWidth="1.2" fill="none"/><path d="M3 22 L11 15 L18 21 L24 15 L33 22" stroke="rgba(255,255,255,.15)" strokeWidth="1.2" fill="none"/></svg>
|
||||||
|
<div className="ph-label">Expert Stain & Seal</div>
|
||||||
|
<div className="ph-sub">/assets/images/stain-product.jpg<br />800×800px high-res</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
53
components/Staining/ModelNav.tsx
Normal file
53
components/Staining/ModelNav.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export default function ModelNav() {
|
||||||
|
const [activeHash, setActiveHash] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
const sections = ['fence-staining', 'deck-staining', 'structures', 'pre-staining', 'restoration', 'your-wood', 'quote-section'];
|
||||||
|
let current = '';
|
||||||
|
|
||||||
|
for (const section of sections) {
|
||||||
|
const el = document.getElementById(section);
|
||||||
|
if (el) {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
if (rect.top <= 120) {
|
||||||
|
current = section;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setActiveHash(current);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
handleScroll();
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ name: 'Fence Staining', href: '#fence-staining' },
|
||||||
|
{ name: 'Deck Staining', href: '#deck-staining' },
|
||||||
|
{ name: 'Pergolas & Structures', href: '#structures' },
|
||||||
|
{ name: 'Pre-Staining', href: '#pre-staining' },
|
||||||
|
{ name: 'Restoration', href: '#restoration' },
|
||||||
|
{ name: "Your wood's condition", href: '#your-wood' },
|
||||||
|
{ name: 'Get a quote', href: '#quote-section' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="model-nav">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item.href}
|
||||||
|
className={`model-nav-item ${activeHash === item.href.substring(1) ? 'active' : ''}`}
|
||||||
|
href={item.href}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
70
components/Staining/StainingFAQ.tsx
Normal file
70
components/Staining/StainingFAQ.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export default function StainingFAQ() {
|
||||||
|
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
{
|
||||||
|
q: "What stain products do you use?",
|
||||||
|
a: "We use Expert Stain & Seal products on all staining work — both the Stain & Seal product for application and Clean & Bright for surface preparation. Expert Stain & Seal is a professional-grade, deeply penetrating oil-based stain and sealer that we also supply to contractors across Ontario. Using the same product we sell means we know exactly how it performs on different wood types, in different conditions, and at different ages."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "How long will the stain last?",
|
||||||
|
a: "A properly applied Expert Stain & Seal finish on a well-prepped surface typically lasts 3–5 years on vertical surfaces like fences, and 2–4 years on horizontal surfaces like deck boards that take more direct sun, rain, and foot traffic. Pre-stained boards that have all four sides coated before installation often last longer than post-install staining because the end grain is sealed. We'll give you a realistic estimate for your specific project."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Can old, grey weathered wood be stained?",
|
||||||
|
a: "In most cases, yes — and the results can be striking. Grey weathering is a surface condition, not a structural one. Treating the wood with Expert Stain & Seal Clean & Bright strips out the grey layer, removes mildew, and re-opens the grain so fresh stain can penetrate properly. Many homeowners are genuinely surprised at how good their fence or deck looks after restoration. We assess each job before quoting to confirm it's a good restoration candidate."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "How long after installing a new fence should I wait to stain?",
|
||||||
|
a: "For pressure treated wood, we recommend waiting 4–8 weeks after installation to allow the preservative treatment to dry and the wood moisture content to drop to a level where stain will absorb properly. For new cedar or pine, staining can happen sooner — sometimes immediately if the wood was pre-dried. We test moisture levels before starting any job to confirm the timing is right. Pre-staining the boards before installation eliminates this waiting period entirely."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Do you do pre-staining of fence boards before installation?",
|
||||||
|
a: "Yes — and it's the approach we recommend for new fence projects whenever possible. Pre-staining allows us to coat all four sides of every picket and rail, including the back face and end grain, before installation. End grain is the primary point of moisture entry in a wood fence — sealing it before the fence goes up is significantly more effective than trying to reach it afterward. Contact us to arrange pre-staining alongside your fence material order."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Do you serve areas outside Kitchener–Waterloo?",
|
||||||
|
a: "Our staining services are based in the Kitchener–Waterloo–Cambridge area with coverage extending to Guelph, Cambridge, Elmira, New Hamburg, and surrounding Waterloo Region communities. For projects further afield — Hamilton, Brantford, and the surrounding area — contact us to discuss availability. Our Expert Stain & Seal product supply covers a full 250km delivery radius from KWC for contractors who want to apply it themselves."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="faq-section">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">Helpful information</div>
|
||||||
|
<h2 className="sh">Wood staining<br /><span>knowledge base.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="faq-seo-grid">
|
||||||
|
<div className="faq reveal">
|
||||||
|
{faqs.map((faq, index) => (
|
||||||
|
<div key={index} className={`faq-item ${openIndex === index ? 'open' : ''}`}>
|
||||||
|
<div
|
||||||
|
className="faq-q"
|
||||||
|
onClick={() => setOpenIndex(openIndex === index ? null : index)}
|
||||||
|
>
|
||||||
|
{faq.q}<span className="faq-icon">+</span>
|
||||||
|
</div>
|
||||||
|
<div className="faq-a">
|
||||||
|
{faq.a}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="content-block reveal">
|
||||||
|
<h3>Wood staining services — Kitchener, Waterloo & Cambridge</h3>
|
||||||
|
<p>VG Fence Products offers <strong>professional wood staining services</strong> for fences, decks, pergolas, and all outdoor wood structures across the Kitchener–Waterloo–Cambridge region. We use Expert Stain & Seal products — the professional-grade oil-based stain we also supply to contractors across Ontario — applied with proper prep, consistent technique, and a clean finished result.</p>
|
||||||
|
<h3>Fence staining service — Waterloo Region</h3>
|
||||||
|
<p>Cedar and wood fence staining is one of our core services across Waterloo Region. We stain new fences, re-stain existing fences, and restore grey weathered fences using Clean & Bright prep and Expert Stain & Seal application. Contractors who want to offer staining as part of their fence installation can also purchase product directly through us at contractor pricing.</p>
|
||||||
|
<h3>Deck staining — KWC & surrounding communities</h3>
|
||||||
|
<p><strong>Deck staining services</strong> covering deck boards, railings, stairs, and fascia boards are available across Kitchener, Waterloo, Cambridge, and surrounding communities. We handle new decks, restoration jobs, and re-staining of surfaces that have been maintained previously. Every deck job begins with a Clean & Bright prep wash — no stain goes on an unprepped surface.</p>
|
||||||
|
<h3>Pre-staining services — fence contractors & builders</h3>
|
||||||
|
<p>Pre-staining of fence boards before installation is available to <strong>fence contractors and builders</strong> ordering through VG Fence Products. Pre-staining is the most protective application method because it coats all four sides of every board — including back face and end grain — before installation. Contact us to add pre-staining to your material order.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
33
components/Staining/StainingHero.tsx
Normal file
33
components/Staining/StainingHero.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function StainingHero() {
|
||||||
|
return (
|
||||||
|
<section className="hero-staining">
|
||||||
|
<div className="hero-grid-bg"></div>
|
||||||
|
<div className="hero-wood-deco"></div>
|
||||||
|
<div className="hero-accent-circle"></div>
|
||||||
|
<div className="hero-inner">
|
||||||
|
<div className="hero-eyebrow">Wood Staining Services · KWC Ontario</div>
|
||||||
|
<h1>
|
||||||
|
Stained right.<br />
|
||||||
|
<em>Lasts longer.</em>
|
||||||
|
</h1>
|
||||||
|
<p className="hero-desc">
|
||||||
|
Professional wood staining services for <strong>fences, decks, pergolas, log cabins, and all outdoor wood structures</strong> across Waterloo Region and Ontario. We use Expert Stain & Seal products — the professional-grade oil-based stain built for the Canadian outdoor environment.
|
||||||
|
</p>
|
||||||
|
<div className="hero-badges">
|
||||||
|
<span className="badge badge-fill">Professional Staining</span>
|
||||||
|
<span className="badge">Fence & Deck</span>
|
||||||
|
<span className="badge">Expert Stain & Seal</span>
|
||||||
|
<span className="badge">Pre-Staining Available</span>
|
||||||
|
<span className="badge">KWC & Ontario</span>
|
||||||
|
</div>
|
||||||
|
<div className="hero-stats-row">
|
||||||
|
<div><div className="stat-val">5</div><div className="stat-label">Service types</div></div>
|
||||||
|
<div><div className="stat-val">Oil</div><div className="stat-label">Based stain — deep penetrating</div></div>
|
||||||
|
<div><div className="stat-val">250km</div><div className="stat-label">Ontario service radius</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
43
components/Staining/StainingIntro.tsx
Normal file
43
components/Staining/StainingIntro.tsx
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function StainingIntro() {
|
||||||
|
return (
|
||||||
|
<section className="intro">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">About this service</div>
|
||||||
|
<h2 className="sh">Wood that's stained right<br /><span>stays beautiful longer.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="intro-grid">
|
||||||
|
<div className="intro-text reveal">
|
||||||
|
<p><strong>VG Fence Products offers professional wood staining services</strong> for homeowners, fence contractors, builders, and property managers across Kitchener–Waterloo–Cambridge and the surrounding Ontario region. We apply Expert Stain & Seal products — the same professional-grade stain we sell to contractors — with the preparation, technique, and attention to detail that produces results you can actually see the difference in.</p>
|
||||||
|
<p>The stain matters. But so does <strong>everything that happens before the stain goes on</strong> — cleaning, brightening, drying, and prepping the wood surface properly. We don't skip steps. A proper prep job is what separates a stain that lasts two seasons from one that looks sharp for five or more years.</p>
|
||||||
|
<p>Whether your project is a brand new cedar fence that needs its first coat before the grain closes up, a greying deck that needs restoration, or a pergola that's never been sealed — we can assess, prep, and stain it properly.</p>
|
||||||
|
<ul className="check-list">
|
||||||
|
<li>Wood fence staining — cedar, pressure treated, pine, and spruce</li>
|
||||||
|
<li>Deck staining — deck boards, railings, stairs, and fascia</li>
|
||||||
|
<li>Pergola, gazebo, and outdoor structure staining</li>
|
||||||
|
<li>Log cabin and cedar siding staining</li>
|
||||||
|
<li>Pre-staining of fence boards before installation</li>
|
||||||
|
<li>Wood restoration — weathered, grey, and damaged wood</li>
|
||||||
|
<li>Expert Stain & Seal Clean & Bright prep on all restoration jobs</li>
|
||||||
|
<li>Serving KWC and surrounding Ontario communities</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="highlight-cards reveal">
|
||||||
|
<div className="hcard">
|
||||||
|
<div className="hcard-title">Professional-grade product — Expert Stain & Seal</div>
|
||||||
|
<div className="hcard-desc">We apply Expert Stain & Seal — a deep-penetrating, oil-based professional stain that bonds to wood grain rather than sitting on the surface. It carries UV protection and moisture sealing in one application, and it's the product we recommend and supply to contractors across Ontario.</div>
|
||||||
|
</div>
|
||||||
|
<div className="hcard">
|
||||||
|
<div className="hcard-title">Prep comes first — every single time</div>
|
||||||
|
<div className="hcard-desc">Every staining job we do starts with a proper clean and prep using Expert Stain & Seal Clean & Bright. This removes grey weathering, mildew, and any residue that would block the new stain from bonding to the wood. No stain goes on unprepped wood — ever.</div>
|
||||||
|
</div>
|
||||||
|
<div className="hcard">
|
||||||
|
<div className="hcard-title">We sell it and we apply it</div>
|
||||||
|
<div className="hcard-desc">As both the local supplier of Expert Stain & Seal products and the service team that applies them, we know this product better than anyone in the region. That means better results for your fence, deck, or structure — applied by people who use it every day.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
44
components/Staining/StainingProcess.tsx
Normal file
44
components/Staining/StainingProcess.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function StainingProcess() {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
num: "01",
|
||||||
|
title: "Quote & assess",
|
||||||
|
desc: "We review your fence, deck, or structure — in person or from photos — and provide a clear quote covering prep, product, labour, and timeline. No hidden steps added later."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "02",
|
||||||
|
title: "Clean & prep",
|
||||||
|
desc: "Every surface is cleaned and brightened with Expert Stain & Seal Clean & Bright before any stain is applied. Surrounding plants, surfaces, and structures are masked. The wood dries fully before we continue."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "03",
|
||||||
|
title: "Stain application",
|
||||||
|
desc: "Expert Stain & Seal is applied to the clean, open wood grain — one or two coats depending on wood age, condition, and absorption. We work methodically by section to ensure even coverage and no lap marks."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: "04",
|
||||||
|
title: "Walkthrough & done",
|
||||||
|
desc: "We walk the finished project with you before we leave — checking coverage, any areas of concern, and reviewing care instructions. You only sign off when you're satisfied with the result."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="process-section" id="process">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow text-white-50">How every job runs</div>
|
||||||
|
<h2 className="sh sh-white">Our staining process —<br /><span>no shortcuts.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="process-grid reveal">
|
||||||
|
{steps.map((step, index) => (
|
||||||
|
<div key={index} className="process-step">
|
||||||
|
<div className="step-num">{step.num}</div>
|
||||||
|
<div className="step-title">{step.title}</div>
|
||||||
|
<div className="step-desc">{step.desc}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
124
components/Staining/StainingQuote.tsx
Normal file
124
components/Staining/StainingQuote.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export default function StainingQuote() {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
projectType: 'fence',
|
||||||
|
message: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('Form submitted:', formData);
|
||||||
|
alert('Thank you for your request! We will contact you shortly.');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="quote-cta quote-cta-orange" id="quote-section">
|
||||||
|
<div className="quote-inner">
|
||||||
|
<div className="quote-left">
|
||||||
|
<h2 className="text-white">GET A WOOD STAINING QUOTE.</h2>
|
||||||
|
<p className="text-white">
|
||||||
|
Tell us what you need stained, its approximate size, and your location — we'll come back with a quote and timeline within 2 business hours.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="quote-form-card orange-card">
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="q-form-title">REQUEST A STAINING QUOTE</div>
|
||||||
|
<div className="q-form-sub text-white-70">Response within 2 business hours</div>
|
||||||
|
|
||||||
|
<div className="qrow">
|
||||||
|
<div>
|
||||||
|
<label className="ql text-white">YOUR NAME</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
className="qi orange-input"
|
||||||
|
placeholder="John Smith"
|
||||||
|
required
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="ql text-white">PHONE</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
name="phone"
|
||||||
|
className="qi orange-input"
|
||||||
|
placeholder="519-xxx-xxxx"
|
||||||
|
required
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="ql text-white">EMAIL</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
className="qi orange-input"
|
||||||
|
placeholder="you@email.com"
|
||||||
|
required
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label className="ql text-white">SERVICE NEEDED</label>
|
||||||
|
<select
|
||||||
|
name="projectType"
|
||||||
|
className="qi orange-input"
|
||||||
|
value={formData.projectType}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<option value="">Select service...</option>
|
||||||
|
<option>Fence staining — new wood</option>
|
||||||
|
<option>Fence staining — existing / restoration</option>
|
||||||
|
<option>Deck staining — new wood</option>
|
||||||
|
<option>Deck staining — restoration</option>
|
||||||
|
<option>Pergola / structure staining</option>
|
||||||
|
<option>Log cabin / cedar siding</option>
|
||||||
|
<option>Pre-staining — fence boards before install</option>
|
||||||
|
<option>Expert Stain & Seal product only — contractor purchase</option>
|
||||||
|
<option>Multiple services — describe below</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div className="qrow">
|
||||||
|
<div>
|
||||||
|
<label className="ql text-white">CITY / LOCATION</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="location"
|
||||||
|
className="qi orange-input"
|
||||||
|
placeholder="Kitchener, Guelph..."
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="ql text-white">APPROXIMATE SIZE</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="size"
|
||||||
|
className="qi orange-input"
|
||||||
|
placeholder="e.g. 150 ft fence / 400 sq ft deck"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" className="qbtn btn-white-orange">SEND QUOTE REQUEST →</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
122
components/Staining/StainingServiceSection.tsx
Normal file
122
components/Staining/StainingServiceSection.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
interface Spec {
|
||||||
|
key: string;
|
||||||
|
val: string;
|
||||||
|
isOrange?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StainingServiceSectionProps {
|
||||||
|
id: string;
|
||||||
|
serviceNum: string;
|
||||||
|
title: string;
|
||||||
|
tagline: string;
|
||||||
|
description: string;
|
||||||
|
specsTitle1: string;
|
||||||
|
specs1: Spec[];
|
||||||
|
specsTitle2: string;
|
||||||
|
specs2: Spec[];
|
||||||
|
bgColor: 'bg-white' | 'bg-gray' | 'bg-navy' | 'bg-cream';
|
||||||
|
reverse?: boolean;
|
||||||
|
photoLabel: string;
|
||||||
|
photoSub1: string;
|
||||||
|
photoSub2: string;
|
||||||
|
isDark?: boolean;
|
||||||
|
image?: string;
|
||||||
|
extraInfo?: {
|
||||||
|
title: string;
|
||||||
|
desc: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StainingServiceSection({
|
||||||
|
id,
|
||||||
|
serviceNum,
|
||||||
|
title,
|
||||||
|
tagline,
|
||||||
|
description,
|
||||||
|
specsTitle1,
|
||||||
|
specs1,
|
||||||
|
specsTitle2,
|
||||||
|
specs2,
|
||||||
|
bgColor,
|
||||||
|
reverse = false,
|
||||||
|
photoLabel,
|
||||||
|
photoSub1,
|
||||||
|
photoSub2,
|
||||||
|
isDark = false,
|
||||||
|
image,
|
||||||
|
extraInfo
|
||||||
|
}: StainingServiceSectionProps) {
|
||||||
|
const PhotoIcon = ({ size = 48, color = "#B0ADA6" }) => (
|
||||||
|
<svg className="photo-icon" width={size} height={size} viewBox="0 0 48 48" fill="none">
|
||||||
|
<rect x="4" y="10" width="40" height="30" rx="4" stroke={color} strokeWidth="2" />
|
||||||
|
<circle cx="17" cy="22" r="5" stroke={color} strokeWidth="2" />
|
||||||
|
<path d="M4 34 L14 24 L22 32 L30 22 L44 34" stroke={color} strokeWidth="2" fill="none" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={`model-section ${bgColor}`} id={id}>
|
||||||
|
<div className={`model-layout reveal ${reverse ? 'reverse' : ''}`}>
|
||||||
|
<div>
|
||||||
|
<div className="model-accent-strip"></div>
|
||||||
|
<div className={`model-label ${isDark ? 'dim' : ''}`}>Service {serviceNum}</div>
|
||||||
|
<div className={`model-name ${isDark ? 'white' : ''}`} dangerouslySetInnerHTML={{ __html: title }}></div>
|
||||||
|
<div className="model-tagline">{tagline}</div>
|
||||||
|
<p className={`model-desc ${isDark ? 'white' : ''}`}>{description}</p>
|
||||||
|
|
||||||
|
<div className="specs-block">
|
||||||
|
<div className={`specs-title ${isDark ? 'white' : ''}`}>{specsTitle1}</div>
|
||||||
|
{specs1.map((spec, i) => (
|
||||||
|
<div key={i} className={`spec-row ${isDark ? 'white-row' : ''}`}>
|
||||||
|
<span className={`spec-key ${isDark ? 'white' : ''}`}>{spec.key}</span>
|
||||||
|
<span className={`spec-val ${isDark ? 'white' : ''} ${spec.isOrange ? 'orange' : ''}`}>{spec.val}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="specs-block">
|
||||||
|
<div className={`specs-title ${isDark ? 'white' : ''}`}>{specsTitle2}</div>
|
||||||
|
{specs2.map((spec, i) => (
|
||||||
|
<div key={i} className={`spec-row ${isDark ? 'white-row' : ''}`}>
|
||||||
|
<span className={`spec-key ${isDark ? 'white' : ''}`}>{spec.key}</span>
|
||||||
|
<span className={`spec-val ${isDark ? 'white' : ''} ${spec.isOrange ? 'orange' : ''}`}>{spec.val}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{extraInfo && (
|
||||||
|
<div className="model-extra-info">
|
||||||
|
<div className="model-extra-info-title">{extraInfo.title}</div>
|
||||||
|
<div className={`model-extra-info-desc ${isDark ? 'dark' : 'light'}`}>{extraInfo.desc}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className={`photo-area photo-area-large ${isDark ? 'dark-placeholder' : ''}`}>
|
||||||
|
{image ? (
|
||||||
|
<img src={image} alt={photoLabel} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<PhotoIcon color={isDark ? "rgba(255,255,255,.35)" : "#B0ADA6"} />
|
||||||
|
<div className="photo-label">{photoLabel}</div>
|
||||||
|
<div className="photo-sub">Upload project photo here</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="photo-row-grid">
|
||||||
|
<div className={`photo-area photo-area-small ${isDark ? 'dark-placeholder' : ''}`}>
|
||||||
|
<PhotoIcon size={32} color={isDark ? "rgba(255,255,255,.35)" : "#B0ADA6"} />
|
||||||
|
<div className="photo-sub photo-sub-small">{photoSub1}</div>
|
||||||
|
</div>
|
||||||
|
<div className={`photo-area photo-area-small ${isDark ? 'dark-placeholder' : ''}`}>
|
||||||
|
<PhotoIcon size={32} color={isDark ? "rgba(255,255,255,.35)" : "#B0ADA6"} />
|
||||||
|
<div className="photo-sub photo-sub-small">{photoSub2}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
125
components/Staining/StainingTerritory.tsx
Normal file
125
components/Staining/StainingTerritory.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export default function StainingTerritory() {
|
||||||
|
const regions = [
|
||||||
|
{
|
||||||
|
name: "Waterloo Region",
|
||||||
|
cities: [
|
||||||
|
{ name: "Kitchener", primary: true, href: "https://vgfence.com/fence-staining-kitchener" },
|
||||||
|
{ name: "Waterloo", primary: true, href: "https://vgfence.com/fence-staining-waterloo" },
|
||||||
|
{ name: "Cambridge", primary: true, href: "https://vgfence.com/fence-staining-cambridge" },
|
||||||
|
{ name: "Ayr", href: "https://vgfence.com/fence-staining-ayr" },
|
||||||
|
{ name: "Breslau", href: "https://vgfence.com/fence-staining-breslau" },
|
||||||
|
{ name: "Elmira", href: "https://vgfence.com/fence-staining-elmira" },
|
||||||
|
{ name: "St. Jacobs", href: "https://vgfence.com/fence-staining-st-jacobs" },
|
||||||
|
{ name: "New Hamburg", href: "https://vgfence.com/fence-staining-new-hamburg" },
|
||||||
|
{ name: "Baden", href: "https://vgfence.com/fence-staining-baden" },
|
||||||
|
{ name: "Wellesley", href: "https://vgfence.com/fence-staining-wellesley" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Guelph & Wellington",
|
||||||
|
cities: [
|
||||||
|
{ name: "Guelph", primary: true, href: "https://vgfence.com/fence-staining-guelph" },
|
||||||
|
{ name: "Fergus", href: "https://vgfence.com/fence-staining-fergus" },
|
||||||
|
{ name: "Elora", href: "https://vgfence.com/fence-staining-elora" },
|
||||||
|
{ name: "Rockwood", href: "https://vgfence.com/fence-staining-rockwood" },
|
||||||
|
{ name: "Acton", href: "https://vgfence.com/fence-staining-acton" },
|
||||||
|
{ name: "Georgetown", href: "https://vgfence.com/fence-staining-georgetown" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Halton & Hamilton",
|
||||||
|
cities: [
|
||||||
|
{ name: "Hamilton", primary: true, href: "https://vgfence.com/fence-staining-hamilton" },
|
||||||
|
{ name: "Burlington", primary: true, href: "https://vgfence.com/fence-staining-burlington" },
|
||||||
|
{ name: "Milton", href: "https://vgfence.com/fence-staining-milton" },
|
||||||
|
{ name: "Oakville", href: "https://vgfence.com/fence-staining-oakville" },
|
||||||
|
{ name: "Stoney Creek", href: "https://vgfence.com/fence-staining-stoney-creek" },
|
||||||
|
{ name: "Grimsby", href: "https://vgfence.com/fence-staining-grimsby" },
|
||||||
|
{ name: "Brantford", href: "https://vgfence.com/fence-staining-brantford" },
|
||||||
|
{ name: "Paris", href: "https://vgfence.com/fence-staining-paris" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GTA & Peel",
|
||||||
|
cities: [
|
||||||
|
{ name: "Mississauga", primary: true, href: "https://vgfence.com/fence-staining-mississauga" },
|
||||||
|
{ name: "Brampton", primary: true, href: "https://vgfence.com/fence-staining-brampton" },
|
||||||
|
{ name: "Vaughan", href: "https://vgfence.com/fence-staining-vaughan" },
|
||||||
|
{ name: "Markham", href: "https://vgfence.com/fence-staining-markham" },
|
||||||
|
{ name: "Richmond Hill", href: "https://vgfence.com/fence-staining-richmond-hill" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Oxford & Perth",
|
||||||
|
cities: [
|
||||||
|
{ name: "Woodstock", primary: true, href: "https://vgfence.com/fence-staining-woodstock" },
|
||||||
|
{ name: "Stratford", primary: true, href: "https://vgfence.com/fence-staining-stratford" },
|
||||||
|
{ name: "Ingersoll" },
|
||||||
|
{ name: "Tillsonburg" },
|
||||||
|
{ name: "St. Marys" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "London & Elgin",
|
||||||
|
cities: [
|
||||||
|
{ name: "London", primary: true, href: "https://vgfence.com/fence-staining-london-ontario" },
|
||||||
|
{ name: "St. Thomas" },
|
||||||
|
{ name: "Strathroy" },
|
||||||
|
{ name: "Komoka" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Southwest Ontario",
|
||||||
|
cities: [
|
||||||
|
{ name: "Windsor", primary: true },
|
||||||
|
{ name: "Chatham", primary: true },
|
||||||
|
{ name: "Leamington" },
|
||||||
|
{ name: "Sarnia" },
|
||||||
|
{ name: "Petrolia" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Extended Service",
|
||||||
|
cities: [
|
||||||
|
{ name: "Niagara Falls", primary: true },
|
||||||
|
{ name: "St. Catharines",},
|
||||||
|
{ name: "Welland" },
|
||||||
|
{ name: "Barrie", primary: true },
|
||||||
|
{ name: "Owen Sound" },
|
||||||
|
{ name: "Collingwood" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="territory-service" id="territory">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow text-white-50">Where we serve</div>
|
||||||
|
<h2 className="sh sh-white">Wood staining services<br /><span>across Ontario.</span></h2>
|
||||||
|
<p className="territory-intro">Staining services are based in KWC with coverage across Waterloo Region and surrounding communities. Expert Stain & Seal product supply covers 250km across Ontario.</p>
|
||||||
|
</div>
|
||||||
|
<div className="region-grid reveal">
|
||||||
|
{regions.map((region, index) => (
|
||||||
|
<div key={index} className="region-block">
|
||||||
|
<div className="region-name">{region.name}</div>
|
||||||
|
<ul className="region-cities">
|
||||||
|
{region.cities.map((city, cIdx) => (
|
||||||
|
<li key={cIdx} className={city.primary ? 'primary' : ''}>
|
||||||
|
{city.href ? (
|
||||||
|
<Link href={city.href} title={city.name}>{city.name}</Link>
|
||||||
|
) : (
|
||||||
|
city.name
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
53
components/Staining/WoodConditionGuide.tsx
Normal file
53
components/Staining/WoodConditionGuide.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export default function WoodConditionGuide() {
|
||||||
|
const conditions = [
|
||||||
|
{
|
||||||
|
tag: "Brand new",
|
||||||
|
tagClass: "tag-new",
|
||||||
|
title: "New or uninstalled wood",
|
||||||
|
desc: "Fresh wood with open grain and no weathering. Ideal for pre-staining before installation — all four sides coated, end grain sealed, maximum protection from day one.",
|
||||||
|
action: "→ Pre-staining recommended"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "Recently installed",
|
||||||
|
tagClass: "tag-recent",
|
||||||
|
title: "Installed within 6 months",
|
||||||
|
desc: "Newly installed wood is still drying and stabilizing. We check moisture content to confirm it's within the acceptable range before applying stain — timing matters here.",
|
||||||
|
action: "→ Moisture check + first coat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "Weathered",
|
||||||
|
tagClass: "tag-aged",
|
||||||
|
title: "Grey or weathered wood",
|
||||||
|
desc: "Wood that has greyed and lost its surface. Needs Clean & Bright to strip the weathered layer and re-open the grain before stain can penetrate properly. Often surprises clients with how good it looks.",
|
||||||
|
action: "→ Clean & Bright prep + stain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: "Neglected",
|
||||||
|
tagClass: "tag-damaged",
|
||||||
|
title: "Heavily neglected or damaged",
|
||||||
|
desc: "Old stain, mildew, surface damage, or cracking. We assess whether restoration staining is the right solution or whether board replacement is needed first. We're honest about what will and won't work.",
|
||||||
|
action: "→ Full restoration assessment"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="condition-section" id="your-wood">
|
||||||
|
<div className="reveal">
|
||||||
|
<div className="section-eyebrow">What stage is your wood in?</div>
|
||||||
|
<h2 className="sh">We work with wood<br /><span>at every stage.</span></h2>
|
||||||
|
</div>
|
||||||
|
<div className="condition-grid reveal">
|
||||||
|
{conditions.map((card, index) => (
|
||||||
|
<div key={index} className="condition-card">
|
||||||
|
<span className={`condition-tag ${card.tagClass}`}>{card.tag}</span>
|
||||||
|
<div className="condition-title">{card.title}</div>
|
||||||
|
<div className="condition-desc">{card.desc}</div>
|
||||||
|
<div className="condition-action">{card.action}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,10 +1,27 @@
|
|||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
export default function Territory() {
|
export default function Territory() {
|
||||||
|
const cities = [
|
||||||
|
{ type: 'home', name: 'Kitchener · Waterloo · Cambridge', sub: 'Home base — priority service area', dist: '0km', link: null },
|
||||||
|
{ type: 'near', name: 'Guelph', sub: 'High residential & commercial growth', dist: '~30km', link: "https://vgfence.com/fence-supply-guelph" },
|
||||||
|
{ type: 'near', name: 'Brantford', sub: 'Contractors, builders, industrial', dist: '~50km', link: "https://vgfence.com/fence-supply-brantford" },
|
||||||
|
{ type: 'near', name: 'Hamilton', sub: 'Industrial, commercial, property managers', dist: '~75km', link: "https://vgfence.com/fence-supply-hamilton" },
|
||||||
|
{ type: 'home', name: 'Toronto / GTA', sub: 'Commercial builders, developers, high volume', dist: '~110km', link: "https://vgfence.com/fence-supply-toronto" },
|
||||||
|
{ type: 'home', name: 'London', sub: 'Large market, residential & commercial', dist: '~120km', link: "https://vgfence.com/fence-supply-london-ontario" },
|
||||||
|
{ type: 'far', name: 'Windsor', sub: 'Southwest Ontario hub', dist: '~230km', link: "https://vgfence.com/fence-supply-windsor" },
|
||||||
|
{ type: 'far', name: 'Barrie', sub: 'Extended service area', dist: '~160km', link: "https://vgfence.com/fence-supply-barrie" },
|
||||||
|
{ type: 'far', name: 'Niagara · Owen Sound', sub: 'Delivery available', dist: '~150–175km', link: null },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="territory-section" id="territory">
|
<section className="territory-section" id="territory">
|
||||||
|
<div className="reveal">
|
||||||
<div className="section-eyebrow">Service territory</div>
|
<div className="section-eyebrow">Service territory</div>
|
||||||
<h2 className="section-h2">Based in <span>KWC.</span><br />Delivering everywhere.</h2>
|
<h2 className="sh">Based in <span>KWC.</span><br />Delivering everywhere.</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="territory-layout">
|
<div className="territory-layout">
|
||||||
<div className="territory-map-wrap">
|
<div className="map-card reveal">
|
||||||
<div className="radius-label">250km radius</div>
|
<div className="radius-label">250km radius</div>
|
||||||
<svg viewBox="0 0 500 360" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ width: '100%' }}>
|
<svg viewBox="0 0 500 360" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ width: '100%' }}>
|
||||||
<rect width="500" height="360" fill="#EEF3F8" rx="8" />
|
<rect width="500" height="360" fill="#EEF3F8" rx="8" />
|
||||||
@ -52,6 +69,10 @@ export default function Territory() {
|
|||||||
<text x="385" y="232" fontSize="10" fontWeight="600" fill="#1B3A6B" fontFamily="sans-serif">Niagara</text>
|
<text x="385" y="232" fontSize="10" fontWeight="600" fill="#1B3A6B" fontFamily="sans-serif">Niagara</text>
|
||||||
<text x="385" y="243" fontSize="9" fill="#888" fontFamily="sans-serif">~150km</text>
|
<text x="385" y="243" fontSize="9" fill="#888" fontFamily="sans-serif">~150km</text>
|
||||||
|
|
||||||
|
<circle cx="88" cy="190" r="6" fill="#E8572A" opacity=".8" />
|
||||||
|
<text x="22" y="186" fontSize="10" fontWeight="600" fill="#1B3A6B" fontFamily="sans-serif">Windsor</text>
|
||||||
|
<text x="22" y="197" fontSize="9" fill="#888" fontFamily="sans-serif">~230km</text>
|
||||||
|
|
||||||
<circle cx="30" cy="330" r="5" fill="#E8572A" />
|
<circle cx="30" cy="330" r="5" fill="#E8572A" />
|
||||||
<text x="40" y="334" fontSize="9" fill="#666" fontFamily="sans-serif">Major hub</text>
|
<text x="40" y="334" fontSize="9" fill="#666" fontFamily="sans-serif">Major hub</text>
|
||||||
<circle cx="105" cy="330" r="4" fill="#1B3A6B" opacity=".7" />
|
<circle cx="105" cy="330" r="4" fill="#1B3A6B" opacity=".7" />
|
||||||
@ -61,19 +82,12 @@ export default function Territory() {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="city-list reveal">
|
||||||
<div className="footer-territory" style={{ marginBottom: '24px' }}>📍 Home base: Kitchener · Waterloo · Cambridge</div>
|
<div className="home-base-pill">📍 Home base: Kitchener · Waterloo · Cambridge</div>
|
||||||
<div className="city-list">
|
{cities.map((city, index) => (
|
||||||
{[
|
city.link ? (
|
||||||
{ type: 'home', name: 'Kitchener · Waterloo · Cambridge', sub: 'Home base — priority service area', dist: '0km' },
|
<Link key={index} href={city.link}>
|
||||||
{ type: 'near', name: 'Guelph', sub: 'High residential & commercial growth', dist: '~30km' },
|
<div className="city-item">
|
||||||
{ type: 'near', name: 'Brantford', sub: 'Contractors, builders, industrial', dist: '~50km' },
|
|
||||||
{ type: 'near', name: 'Hamilton', sub: 'Industrial, commercial, property managers', dist: '~75km' },
|
|
||||||
{ type: 'home', name: 'Toronto / GTA', sub: 'Commercial builders, developers, high volume', dist: '~110km' },
|
|
||||||
{ type: 'home', name: 'London', sub: 'Large market, residential & commercial', dist: '~120km' },
|
|
||||||
{ type: 'far', name: 'Niagara · Barrie · Owen Sound', sub: 'Extended delivery available', dist: '~150–175km' },
|
|
||||||
].map((city, index) => (
|
|
||||||
<div key={index} className="city-item">
|
|
||||||
<div className={`city-dot ${city.type}`}></div>
|
<div className={`city-dot ${city.type}`}></div>
|
||||||
<div className="city-info">
|
<div className="city-info">
|
||||||
<div className="city-name-txt">{city.name}</div>
|
<div className="city-name-txt">{city.name}</div>
|
||||||
@ -81,8 +95,18 @@ export default function Territory() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="city-dist-badge">{city.dist}</div>
|
<div className="city-dist-badge">{city.dist}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div key={index} className="city-item no-link">
|
||||||
|
<div className={`city-dot ${city.type}`}></div>
|
||||||
|
<div className="city-info">
|
||||||
|
<div className="city-name-txt">{city.name}</div>
|
||||||
|
<div className="city-sub-txt">{city.sub}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="city-dist-badge">{city.dist}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
export default function TrustBar() {
|
export default function TrustBar() {
|
||||||
const trustItems = [
|
const trustItems = [
|
||||||
{ icon: '🚚', text: 'Same-day job site delivery' },
|
{ icon: '🚚', text: 'Scheduled job site delivery' },
|
||||||
{ icon: '📐', text: '2D fence drawing services' },
|
{ icon: '📐', text: '2D fence drawing services' },
|
||||||
{ icon: '🏗️', text: 'Contractor accounts available' },
|
{ icon: '🏗️', text: 'Contractor accounts available' },
|
||||||
{ icon: '📍', text: 'Serving 250km across Ontario' },
|
{ icon: '📍', text: 'Serving 250km across Ontario' },
|
||||||
{ icon: '⭐', text: '3+ years serving KWC' },
|
{ icon: '⭐', text: "KWC's B2B fence partner" },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,25 +1,26 @@
|
|||||||
export default function WhoWeServe() {
|
export default function WhoWeServe() {
|
||||||
const targets = [
|
const targets = [
|
||||||
{ priority: 1, name: "Fence contractors", examples: "Residential · Commercial · Chain link specialists · Ornamental installers" },
|
{ p: "p1", label: "■ PRIORITY 1", name: "FENCE CONTRACTORS", examples: "Residential · Commercial · Chain link specialists · Ornamental installers" },
|
||||||
{ priority: 1, name: "General contractors", examples: "Landscaping · Deck builders · Pool contractors · Renovation" },
|
{ p: "p1", label: "■ PRIORITY 1", name: "GENERAL CONTRACTORS", examples: "Landscaping · Deck builders · Pool contractors · Renovation" },
|
||||||
{ priority: 1, name: "Builders & developers", examples: "Home builders · Commercial builders · Site developers · Real estate developers" },
|
{ p: "p1", label: "■ PRIORITY 1", name: "BUILDERS & DEVELOPERS", examples: "Home builders · Commercial builders · Site developers · Real estate developers" },
|
||||||
{ priority: 2, name: "Property managers", examples: "Apartments · Commercial properties · Industrial facilities · Storage" },
|
{ p: "p2", label: "■ PRIORITY 2", name: "PROPERTY MANAGERS", examples: "Apartments · Commercial properties · Industrial facilities · Storage" },
|
||||||
{ priority: 2, name: "Staining contractors", examples: "Deck staining · Fence staining · Painters · Handyman services · Pressure washing" },
|
{ p: "p2", label: "■ PRIORITY 2", name: "STAINING CONTRACTORS", examples: "Deck staining · Fence staining · Painters · Handyman · Pressure washing" },
|
||||||
{ priority: 2, name: "Event & rental companies", examples: "Event management · Temp fence rental · Crowd control · Festival organizers" },
|
{ p: "p2", label: "■ PRIORITY 2", name: "EVENT & RENTAL COMPANIES", examples: "Event management · Temp fence rental · Crowd control · Festival organizers" },
|
||||||
{ priority: 3, name: "Government & institutions", examples: "Municipalities · Schools · Parks & rec · Public infrastructure · Government contractors", type: "Specialty" },
|
{ p: "p3", label: "□ SPECIALTY", name: "GOVERNMENT & INSTITUTIONS", examples: "Municipalities · Schools · Parks & rec · Public infrastructure" },
|
||||||
{ priority: 3, name: "Agriculture & rural", examples: "Farms · Ranches · Livestock owners · Agricultural businesses", type: "Specialty" },
|
{ p: "p3", label: "□ SPECIALTY", name: "AGRICULTURE & RURAL", examples: "Farms · Ranches · Livestock owners · Agricultural businesses" },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="targets-section" id="targets">
|
<section className="targets-section" id="targets">
|
||||||
|
<div className="reveal">
|
||||||
<div className="section-eyebrow">Who we serve</div>
|
<div className="section-eyebrow">Who we serve</div>
|
||||||
<h2 className="section-h2">Built for the<br />people who <span>build.</span></h2>
|
<h2 className="sh">BUILT FOR THE<br />PEOPLE WHO <span>BUILD.</span></h2>
|
||||||
<div className="targets-grid">
|
</div>
|
||||||
|
|
||||||
|
<div className="targets-grid reveal">
|
||||||
{targets.map((target, index) => (
|
{targets.map((target, index) => (
|
||||||
<div key={index} className="target-card">
|
<div key={index} className="target-card">
|
||||||
<div className={`target-priority priority-${target.priority}`}>
|
<div className={`target-priority ${target.p}`}>{target.label}</div>
|
||||||
{target.priority === 1 ? '⬛ Priority 1' : target.priority === 2 ? '◼ Priority 2' : `◻ ${target.type}`}
|
|
||||||
</div>
|
|
||||||
<div className="target-name">{target.name}</div>
|
<div className="target-name">{target.name}</div>
|
||||||
<div className="target-examples">{target.examples}</div>
|
<div className="target-examples">{target.examples}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 433 KiB |
BIN
public/images/chain-link-hero.png
Normal file
BIN
public/images/chain-link-hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
public/images/stained-deck.png
Normal file
BIN
public/images/stained-deck.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 872 KiB |
BIN
public/images/stained-pergola.png
Normal file
BIN
public/images/stained-pergola.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
public/images/staining-hero.png
Normal file
BIN
public/images/staining-hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 812 KiB |
Loading…
x
Reference in New Issue
Block a user