/* VA Classes, homepage UI kit · reusable components */
/* ── Lead endpoint fallback — set on every page via components.jsx ── */
if (!window.VA_LEAD_ENDPOINT) {
window.VA_LEAD_ENDPOINT = "https://script.google.com/macros/s/AKfycbxUjyJHhLAqiyh8A4SzB7tl7Zc_sgmHiTIHaynfftoqcBO_mvUJP37ZHqfVu7YknWNnTw/exec";
}
/* =====================================================================
LINKS, single place for all external URLs.
▶ Replace the "#" placeholders with the real URLs:
- login: your student/parent portal LOGIN url (login only, no public sign-up)
- facebook / instagram / trustpilot: your social & review pages
===================================================================== */
window.VA_LINKS = window.VA_LINKS || {
login: "https://merithub.com/login",
facebook: "#facebook",
instagram: "https://www.instagram.com/va__classes",
trustpilot: "https://www.trustpilot.com/review/vaclasses.live",
};
const LINKS = window.VA_LINKS;
const NAV_SUBJECTS = [
{ name: "Math", icon: "calculator", page: "math.html", grades: "Grades 1 to 10" },
{ name: "Science", icon: "atom", page: "science.html", grades: "Grades 1 to 10" },
{ name: "English", icon: "book-open", page: "english.html", grades: "Grades 1 to 10" },
{ name: "Coding", icon: "code-xml", page: "coding.html", grades: "Grades 3 to 10" },
];
function Icon({ name, size = 18, style }) {
return ;
}
function useLucide() {
React.useEffect(() => { if (window.lucide) window.lucide.createIcons(); });
}
function Logo({ light = false, boxed = false }) {
if (boxed) {
return (
VA Classes
);
}
const markSrc = light ? "assets/logo-mark-light.png" : "assets/logo-mark.png";
return (
VA Classes
);
}
function Button({ variant = "primary", size, children, onClick, icon, iconRight }) {
const cls = ["btn", `btn-${variant}`, size && `btn-${size}`].filter(Boolean).join(" ");
return (
{icon && }{children}{iconRight && }
);
}
// Star rating row, green (#00B67A) like Trustpilot
function Stars({ rating = 5, size = 14 }) {
return (
{[0, 1, 2, 3, 4].map(i => (
))}
);
}
function PhotoPlaceholder({ caption, glyph = "users", radius = "var(--r-xl)", minHeight = 380 }) {
return (
{caption}
);
}
function SubjectCard({ s }) {
return (
{s.name}
{s.desc}
Explore {s.name}
);
}
function TutorAvatar({ t }) {
const palette = ["#FF6B6B", "#FFC700", "#4ECDC4", "#5B8DEF", "#C77DFF", "#FF9F1C", "#1F8A4C", "#2A9DEF"];
const color = palette[(t.name ? t.name.charCodeAt(0) : 0) % palette.length];
if (t.photo) return ;
return {t.name ? t.name[0] : "?"}
;
}
function TutorCard({ t, onBook }) {
return (
{t.name}
{t.rating.toFixed(1)} · {t.reviews} reviews
{t.subject}
{t.experience}+ yrs
{t.bio &&
{t.bio}
}
{t.education}
{t.location}
onBook && onBook(t)}>Book a trial with {t.name.split(" ")[0]}
);
}
function TestimonialCard({ q, feat = false }) {
return (
{q.flag &&
}
“{q.quote}”
{q.name ? q.name[0] : ""}
);
}
function PriceTier({ tier, cycle, currency }) {
const cur = currency || "cad";
const price = tier.price[cur][cycle];
const prefix = cur === "cad" ? "CA$" : "US$";
const label = cur === "cad" ? "CAD" : "USD";
return (
{tier.popular &&
Most popular }
{tier.name}
{prefix}{price}
/{cycle === "quarter" ? "quarter" : "month"}
{tier.sub} Billed in {label}
{tier.feats.map((f, i) => {f} )}
{tier.cta}
);
}
// ---- Shared chrome (used by every page) ----
function LoginLink({ small = true }) {
return (
Log in
);
}
function SubjectsMenu() {
const [open, setOpen] = React.useState(false);
const ref = React.useRef(null);
React.useEffect(() => {
const close = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
document.addEventListener("click", close);
return () => document.removeEventListener("click", close);
}, []);
React.useEffect(() => {
if (open && window.lucide) window.lucide.createIcons();
}, [open]);
return (
setOpen(o => !o)} aria-expanded={open}>
Subjects
{open && (
)}
);
}
function Nav({ onTrial }) {
useLucide();
const [mOpen, setMOpen] = React.useState(false);
const navRef = React.useRef(null);
// Add shadow when page is scrolled
React.useEffect(() => {
const onScroll = () => {
if (navRef.current) {
navRef.current.classList.toggle("is-scrolled", window.scrollY > 10);
}
};
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
React.useEffect(() => {
if (!mOpen) return;
const close = () => setMOpen(false);
document.addEventListener("click", close);
return () => document.removeEventListener("click", close);
}, [mOpen]);
return (
Book free class
{ e.stopPropagation(); setMOpen(o => !o); }} aria-label="Menu">
{mOpen && (
)}
);
}
function SocialIcon({ kind }) {
if (kind === "facebook") return (
);
if (kind === "instagram") return (
);
return (
);
}
function Footer() {
const cols = [
{ h: "Subjects", links: [["Math", "math.html"], ["Science", "science.html"], ["English", "english.html"], ["Coding", "coding.html"]] },
{ h: "Company", links: [["About us", "about.html"], ["Pricing", "pricing.html"], ["Contact us", "contact.html"], ["Teach with us", "tutor.html"]] },
{ h: "For families", links: [["Book a free class", "index.html#top"], ["Log in", LINKS.login], ["Reviews on Trustpilot", LINKS.trustpilot], ["Privacy policy", "privacy.html"], ["Terms of service", "terms.html"]] },
];
const socials = [
{ kind: "facebook", label: "Facebook", href: LINKS.facebook, bg: "#1877F2" },
{ kind: "instagram", label: "Instagram", href: LINKS.instagram, bg: "linear-gradient(135deg,#f09433,#e6683c,#dc2743,#cc2366,#bc1888)" },
{ kind: "trustpilot", label: "Trustpilot", href: LINKS.trustpilot, bg: "#00B67A" },
];
useLucide();
return (
);
}
const ALL_SUBJECTS = ["Math", "Science", "English", "Coding"];
function SubjectPicker({ value, onChange }) {
const toggle = (s) => onChange(value.includes(s) ? value.filter(x => x !== s) : [...value, s]);
return (
{ALL_SUBJECTS.map(s => (
toggle(s)}>
{value.includes(s) && }{s}
))}
);
}
function TrialModal({ onClose }) {
const [step, setStep] = React.useState(1);
const [done, setDone] = React.useState(false);
const [sending, setSending] = React.useState(false);
const [f, setF] = React.useState({
parent: "", email: "", phone: "", province: "Ontario",
child: "", grade: "5", subjects: ["Math"], schedule: "Weekday evenings", goal: "", heard: "",
});
const set = (k, v) => setF(prev => ({ ...prev, [k]: v }));
useLucide();
// Sends the lead to your Google Sheet + Gmail (see LEAD_ENDPOINT in components.jsx).
const submitLead = () => {
setSending(true);
const payload = {
...f,
subjects: f.subjects.join(", "),
source: "Detailed form",
submittedAt: new Date().toLocaleString("en-CA"),
page: location.href,
};
// Always show success to the parent; lead is sent in the background.
const finish = () => { setSending(false); setDone(true); };
if (window.VA_LEAD_ENDPOINT && window.VA_LEAD_ENDPOINT.startsWith("http")) {
fetch(window.VA_LEAD_ENDPOINT, {
method: "POST",
mode: "no-cors",
headers: { "Content-Type": "text/plain;charset=utf-8" },
body: JSON.stringify(payload),
}).then(finish).catch(finish);
} else {
console.warn("VA_LEAD_ENDPOINT not set — lead not sent:", payload);
finish();
}
};
const canNext = f.parent.trim() && f.email.trim();
const total = 2;
return (
e.stopPropagation()}>
{!done ? (
Book your free class
Step {step} of {total} · {step === 1 ? "About you" : "About your child"}
{step === 1 ? (
Parent's full name
set("parent", e.target.value)} />
Your province
set("province", e.target.value)}>
{["Ontario","British Columbia","Alberta","Quebec","Manitoba","Saskatchewan","Nova Scotia","New Brunswick","Newfoundland & Labrador","Prince Edward Island","Other / Outside Canada"].map(p => {p} )}
canNext && setStep(2)}>
Continue
No card required · 100% free trial class
) : (
Which subjects? pick one or more
set("subjects", v)} />
Preferred class time
set("schedule", e.target.value)}>
{["Weekday mornings", "Weekday afternoons", "Weekday evenings", "Weekends", "Flexible / not sure"].map(o => {o} )}
What would you like help with? optional
setStep(1)}> Back
{sending ? "Sending…" : "Book my free class"}
)}
) : (
You're booked{f.child ? `, ${f.parent.split(" ")[0]}!` : "!"}
We'll email {f.email || "you"} within a few hours to confirm {f.child || "your child"}'s
free {f.subjects.join(" & ")} trial. Talk soon!
Done
)}
);
}
function FAQ({ items }) {
const [open, setOpen] = React.useState(0);
return (
{items.map((it, i) => (
setOpen(open === i ? -1 : i)}>
{it.q}
{open === i &&
{it.a}
}
))}
);
}
function WhyDifferent({ subjectName }) {
const C = window.VA_COMPARE;
const cell = (v) => {
if (v === true) return ;
if (v === "some") return Sometimes ;
return ;
};
useLucide();
return (
Why we're different
{subjectName ? `Why families choose VA Classes for ${subjectName}` : "What sets VA Classes apart"}
Studies are consistent: one-on-one tutoring is the most effective way to learn, because lessons adapt to your child, feedback is instant, and there's no crowd to hide behind. Here's how the three options really compare.
{/* header row */}
VA Classes
Group online classes
Self-study apps
{/* data rows */}
{C.rows.map((r, i) => (
{r.f}
{cell(r.va)}
{cell(r.group)}
{cell(r.app)}
))}
);
}
function IncludedGrid() {
useLucide();
return (
Included in every plan
Every plan comes with all of this
No hidden tiers for the things that matter. These come standard, whichever plan you pick.
{window.VA_INCLUDED.map(it => (
))}
);
}
Object.assign(window, { Icon, useLucide, Logo, Button, Stars, PhotoPlaceholder, SubjectCard, TutorCard, TestimonialCard, PriceTier, Nav, Footer, TrialModal, LoginLink, SubjectsMenu, FAQ, SubjectPicker, SocialIcon, WhyDifferent, IncludedGrid });