---
title: "Contact Us"
slug: "contact-us"
date: 2026-02-18
modified: 2026-09-06
author: "Dumi"
description: "About you School & Requirements Next Steps Takes 2 min to fill out Next Steps Thank you for your Enquiry  Here’s what will happen next You’ll be contacted by: Jenny Rollings Business Development Manager Katie Gardener Business Development Executive To"
canonical_url: https://holroydhowe.com/contact-us/
source: https://holroydhowe.com/contact-us/
word_count: 139
reading_time: "1 min read"
schema_type: "WebPage"
robots: "index, follow"
lang: en-US
---

# Contact Us

[!\[\](https://holroydhowe.com/wp-content/uploads/2026/02/HH_Logo_Outlined-01.svg)](https://holroydhowe.com)

 About you

 School & Requirements

 Next Steps

'; var wRect = w.getBoundingClientRect(); var hRect = host.getBoundingClientRect(); overlay.style.top = (wRect.top - hRect.top) + 'px'; overlay.style.left = (wRect.left - hRect.left) + 'px'; overlay.style.width = wRect.width + 'px'; overlay.style.height = wRect.height + 'px'; host.appendChild(overlay); spinnerEl = overlay; return overlay; } function showSpinner() { var s = ensureSpinner(); if (!s) return; var w = q('[data-id="' + WID + '"]'); if (w) { var host = s.parentNode; var wRect = w.getBoundingClientRect(); var hRect = host.getBoundingClientRect(); s.style.top = (wRect.top - hRect.top) + 'px'; s.style.left = (wRect.left - hRect.left) + 'px'; s.style.width = wRect.width + 'px'; s.style.height = wRect.height + 'px'; } void s.offsetHeight; s.classList.add('show'); } function hideSpinner() { if (!spinnerEl) return; spinnerEl.classList.remove('show'); } /* ───────────────────────────────────────────────────────────────── * hideFormNow / restoreFormVis * * hideFormNow() is called the instant the submit button is clicked * (capture phase, before Elementor even sees the event). * opacity:0 + no transition = zero visible flash even if Elementor * resets the form to Step 1 while processing the AJAX request. * * restoreFormVis() is called if Elementor reports validation errors, * so the user can see & fix them. Because MutationObserver callbacks * run as microtasks (before the browser paints), the hide→restore * cycle is invisible when validation fails. * ───────────────────────────────────────────────────────────────── */ function hideFormNow() { if (done) return; var w = q('[data-id="' + WID + '"]'); if (!w) return; submitHidden = true; w.style.transition = 'none'; w.style.opacity = '0'; w.style.pointerEvents = 'none'; showSpinner(); /* Safety net: restore after 8 s if neither success nor error fires */ if (restoreTimer) clearTimeout(restoreTimer); restoreTimer = setTimeout(restoreFormVis, 8000); } function restoreFormVis() { if (done) return; var w = q('[data-id="' + WID + '"]'); if (!w) return; submitHidden = false; if (restoreTimer) { clearTimeout(restoreTimer); restoreTimer = null; } hideSpinner(); w.style.transition = 'opacity 0.25s ease'; w.style.opacity = '1'; w.style.pointerEvents = ''; } /* ── Timeline animation ─────────────────────────────────────── */ function runTimeline() { var fa = q('.form-after'); if (!fa) return; var n1 = fa.querySelector('.next1'); var n2 = fa.querySelector('.next2'); var n3 = fa.querySelector('.next3'); var sc = fa.querySelector('.next-sidecard') || q('.next-sidecard'); if (!n1 || !n2 || !n3) return; [n1, n2, n3].forEach(function(el) { el.style.transition = 'none'; el.style.opacity = '0'; el.style.transform = 'translateY(15px)'; }); if (sc) { sc.style.transition = 'none'; sc.style.opacity = '0'; } if (getComputedStyle(fa).position === 'static') fa.style.position = 'relative'; var tl = document.createElement('div'); tl.id = 'cfsTL'; tl.innerHTML = '

' + '

' + '

' + '

' + '

'; fa.appendChild(tl); setTimeout(function() { var pr = fa.getBoundingClientRect(); var r1 = n1.getBoundingClientRect(); var r2 = n2.getBoundingClientRect(); var r3 = n3.getBoundingClientRect(); var y1 = r1.top - pr.top + fa.scrollTop; var y2 = r2.top - pr.top + fa.scrollTop; var y3 = r3.top - pr.top + fa.scrollTop; var xL = r1.left - pr.left - 24; tl.style.left = xL + 'px'; tl.style.top = (y1 - 16) + 'px'; var d1 = gi('cfsTDot1'), d2 = gi('cfsTDot2'), d3 = gi('cfsTDot3'); var s1 = gi('cfsTSeg1'), s2 = gi('cfsTSeg2'); d1.style.top = '0px'; d2.style.top = (y2 - y1) + 'px'; d3.style.top = (y3 - y1) + 'px'; s1.style.top = '6px'; s2.style.top = (y2 - y1 + 6) + 'px'; var h1 = y2 - y1; var h2 = y3 - y2; var SEG = 550, FADE = 380; d1.classList.add('show'); setTimeout(function() { s1.style.transition = 'height ' + SEG + 'ms ease'; s1.style.height = h1 + 'px'; setTimeout(function() { n1.style.transition = 'opacity ' + FADE + 'ms ease, transform ' + FADE + 'ms ease'; n1.style.opacity = '1'; n1.style.transform = 'translateY(0)'; }, 80); }, 150); setTimeout(function() { d2.classList.add('show'); setTimeout(function() { s2.style.transition = 'height ' + SEG + 'ms ease'; s2.style.height = h2 + 'px'; setTimeout(function() { n2.style.transition = 'opacity ' + FADE + 'ms ease, transform ' + FADE + 'ms ease'; n2.style.opacity = '1'; n2.style.transform = 'translateY(0)'; }, 80); }, 80); }, 150 + SEG); setTimeout(function() { d3.classList.add('show'); n3.style.transition = 'opacity ' + FADE + 'ms ease, transform ' + FADE + 'ms ease'; n3.style.opacity = '1'; n3.style.transform = 'translateY(0)'; setTimeout(function() { if (sc) { sc.style.transition = 'opacity 0.5s ease'; sc.style.opacity = '1'; } }, FADE + 150); }, 150 + SEG + 80 + SEG); }, 80); } /* ── Form success ───────────────────────────────────────────── */ function succeed() { if (done) return; done = true; if (restoreTimer) { clearTimeout(restoreTimer); restoreTimer = null; } hideSpinner(); step = 3; render(); var w = q('[data-id="' + WID + '"]'); var fa = q('.form-after'); if (!w || !fa) return; /* * The form wrapper is already opacity:0 from hideFormNow(). * Just remove it from layout and reveal form-after immediately — * no additional fade needed (it was already invisible). */ w.style.transition = 'opacity 150ms ease-out'; w.style.opacity = '0'; w.style.pointerEvents = 'none'; w.style.display = 'none'; fa.style.display = 'flex'; void fa.offsetHeight; runTimeline(); } /* ── Dropdown ───────────────────────────────────────────────── */ function initDropdown() { var caret = q('.elementor-field-group-field_3dfb9a3 .select-caret-down-wrapper'); if (!caret) return; caret.querySelectorAll('svg').forEach(function(s) { s.remove(); }); var span = document.createElement('span'); span.className = 'cfs-arrow'; span.innerHTML = ''; caret.appendChild(span); var sel = gi(SEL_ID); var wrap = sel && sel.closest('.elementor-select-wrapper'); if (!sel || !wrap) return; sel.addEventListener('focus', function() { wrap.classList.add('is-open'); }); sel.addEventListener('change', function() { wrap.classList.remove('is-open'); }); sel.addEventListener('blur', function() { setTimeout(function() { wrap.classList.remove('is-open'); }, 150); }); } /* ── Field listeners ────────────────────────────────────────── */ function initFields() { S1.concat(S2_TEXT, [DATE_ID]).forEach(function(fid) { var el = gi(fid); if (!el) return; el.addEventListener('input', render); el.addEventListener('change', render); }); var vd = q('.elementor-field-group-field_6bdf607 input[type="date"]:not([id])'); if (vd) { vd.addEventListener('input', render); vd.addEventListener('change', render); } } /* ── Step detection ─────────────────────────────────────────── */ function isOnStep2() { var g = q('.elementor-field-group-field_3dfb9a3'); if (g && g.offsetParent !== null) return true; var seps = qq('[data-id="' + WID + '"] .e-form__step'); return seps.length >= 2 && !seps[1].classList.contains('elementor-hidden'); } function checkStep() { if (done) return; var s = isOnStep2() ? 2 : 1; if (s !== step) { step = s; render(); } } function initStepDetection() { var form = q('[data-id="' + WID + '"] .elementor-form'); if (!form) return; new MutationObserver(checkStep).observe(form, { attributes: true, childList: true, subtree: true, attributeFilter: ['class', 'style'] }); document.addEventListener('click', function(e) { if (!e.target.closest) return; var btn = e.target.closest( '.e-form__buttons__wrapper__button-next,' + '.e-form__buttons__wrapper__button-previous' ); if (!btn) return; setTimeout(function() { if (btn.classList.contains('e-form__buttons__wrapper__button-previous')) { step = 1; render(); } else { checkStep(); } }, 80); }); } /* ───────────────────────────────────────────────────────────────── * initSubmitInterception * * Runs in the CAPTURE phase of the click event, meaning it fires * before Elementor's handlers. The form is hidden synchronously * (opacity:0, no CSS transition) so no matter what Elementor does * internally afterwards (reset to Step 1, AJAX, etc.) the user * sees nothing until we deliberately reveal form-after. * * A second MutationObserver watches for Elementor adding error * classes / messages to the form. Because MutationObserver callbacks * are microtasks they run before the browser paints, so if * validation fails the form is restored invisibly in the same frame. * ───────────────────────────────────────────────────────────────── */ function initSubmitInterception() { document.addEventListener('click', function(e) { if (done || !e.target.closest) return; var btn = e.target.closest( '[data-id="' + WID + '"] button[type="submit"],' + '[data-id="' + WID + '"] input[type="submit"]' ); if (btn && step === 2) hideFormNow(); }, true /* capture */); var form = q('[data-id="' + WID + '"] .elementor-form'); if (!form) return; new MutationObserver(function(muts) { if (done || !submitHidden) return; var err = false; muts.forEach(function(m) { /* Attribute mutation: field group gets 'has-error' */ if (m.type === 'attributes' && m.target.classList) { if (m.target.classList.contains('has-error') || m.target.classList.contains('elementor-error')) { err = true; } } /* Node insertion: inline error messages */ m.addedNodes.forEach(function(n) { if (n.nodeType !== 1) return; var cls = (typeof n.className === 'string') ? n.className : ''; if (cls.indexOf('message-danger') > -1 || cls.indexOf('elementor-error') > -1 || cls.indexOf('field-required') > -1) { err = true; } if (!err && n.querySelector && n.querySelector('.elementor-message-danger, .elementor-error')) { err = true; } }); }); if (err) restoreFormVis(); }).observe(form, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); } /* ── Success detection ──────────────────────────────────────── */ function initSuccess() { if (typeof jQuery !== 'undefined') { jQuery(document).on('submit_success', '[data-id="' + WID + '"] .elementor-form', succeed); } var w = q('[data-id="' + WID + '"]'); if (!w) return; new MutationObserver(function(muts) { muts.forEach(function(m) { m.addedNodes.forEach(function(n) { if (n.nodeType !== 1) return; if (n.classList.contains('elementor-message-success') || (n.querySelector && n.querySelector('.elementor-message-success'))) { succeed(); } }); }); }).observe(w, { childList: true, subtree: true }); } /* ───────────────────────────────────────────────────────────────── * initEmailDomainCheck * * Client-side mirror of the PHP elementor_pro/forms/validation/email * rule. Validates when the user tries to advance from Step 1 — either * by clicking Continue OR by pressing Enter inside any Step 1 field. * If the email domain is on the blocklist, navigation to Step 2 is * blocked and an inline error is shown under the email field. * * PHP remains the authoritative security boundary; this is UX only. * The blocklist MUST stay in sync with the PHP $blocked_domains array. * ───────────────────────────────────────────────────────────────── */ function initEmailDomainCheck() { var BLOCKED = [ 'gmail.com', 'googlemail.com', 'yahoo.com', 'yahoo.co.uk', 'yahoo.fr', 'yahoo.de', 'yahoo.es', 'yahoo.it', 'ymail.com', 'rocketmail.com', 'hotmail.com', 'hotmail.co.uk', 'hotmail.fr', 'hotmail.de', 'outlook.com', 'outlook.co.uk', 'live.com', 'live.co.uk', 'msn.com', 'aol.com', 'icloud.com', 'me.com', 'mac.com', 'protonmail.com', 'proton.me', 'pm.me', 'gmx.com', 'gmx.co.uk', 'gmx.de', 'gmx.net', 'yandex.com', 'yandex.ru', 'mail.com', 'mail.ru', 'zoho.com', 'fastmail.com', 'fastmail.fm', 'tutanota.com', 'tuta.io', 'inbox.com', 'rediffmail.com' ]; var ERR_MSG = 'Please use your business email address'; var emailEl = gi('form-field-email'); if (!emailEl) return; var group = emailEl.closest('.elementor-field-group-email'); if (!group) return; function isBlocked(value) { var v = (value || '').toLowerCase().trim(); var at = v.lastIndexOf('@'); if (at === -1) return false; var dom = v.substring(at + 1); if (dom.indexOf('.') === -1) return false; return BLOCKED.indexOf(dom) !== -1; } function showError() { group.classList.add('has-error'); var existing = group.querySelector('.cfs-email-error'); if (existing) { existing.textContent = ERR_MSG; return; } var msg = document.createElement('div'); msg.className = 'cfs-email-error'; msg.setAttribute('role', 'alert'); msg.textContent = ERR_MSG; group.appendChild(msg); } function clearError() { group.classList.remove('has-error'); var existing = group.querySelector('.cfs-email-error'); if (existing) existing.remove(); } /* Clear error as the user fixes it — purely cosmetic, no validation */ emailEl.addEventListener('input', function() { if (group.classList.contains('has-error') && !isBlocked(emailEl.value)) { clearError(); } }); /* Hard gate on Continue click. Capture phase runs before Elementor's next-step handler, so we can preventDefault to stop navigation. */ document.addEventListener('click', function(e) { if (done || !e.target.closest) return; if (step !== 1) return; var btn = e.target.closest( '[data-id="' + WID + '"] .e-form__buttons__wrapper__button-next' ); if (!btn) return; if (isBlocked(emailEl.value)) { e.preventDefault(); e.stopPropagation(); showError(); emailEl.focus(); } }, true /* capture */); /* Hard gate on Enter key. Pressing Enter in any Step 1 input can trigger Elementor to advance the step (or implicit form submit). We catch it in the capture phase and block if the email is on the blocklist. Textareas are excluded so Enter can still create newlines inside multiline fields. */ document.addEventListener('keydown', function(e) { if (done) return; if (step !== 1) return; if (e.key !== 'Enter' && e.keyCode !== 13) return; var t = e.target; if (!t || !t.closest) return; /* Only act on inputs inside our form, and never on textareas */ var inForm = t.closest('[data-id="' + WID + '"]'); if (!inForm) return; var tag = (t.tagName || '').toLowerCase(); if (tag === 'textarea') return; if (isBlocked(emailEl.value)) { e.preventDefault(); e.stopPropagation(); showError(); emailEl.focus(); } }, true /* capture */); } /* ── Bootstrap ──────────────────────────────────────────────── */ onReady(function() { var fa = q('.form-after'); if (fa) fa.style.display = 'none'; initDropdown(); initFields(); initStepDetection(); initSubmitInterception(); initSuccess(); initEmailDomainCheck(); render(); }); }());

Takes 2 min to fill out

Next Steps

![](https://holroydhowe.com/wp-content/uploads/2026/03/Gemini_Generated_Image_ooplasooplasoopl-3-1.avif)

### Thank you for your Enquiry
 *Here’s what will happen next*

You’ll be contacted by:

![](https://holroydhowe.com/wp-content/uploads/2026/02/dd1746a0478cac881aeb9d0ddc86445188459794-2.avif)

Jenny Rollings
 *Business Development Manager*

![](https://holroydhowe.com/wp-content/uploads/2026/02/d0a3d38e0f9775694ddd89d5cbac5b9dd85db4e1-2.avif)

Katie Gardener
 *Business Development Executive*

To discuss your enquiry

We will arrange a first appointment, depending on your needs this could be a teams call or a site visit.

You'll receive a tailored proposal built around your school's specific requirements.

Can’t wait to talk to us?

Fancy a look at our Capabilities Deck

DOWNLOAD

- Capabilities Deck
- Sample Menu
