Fenvynvyn Studios logo Fenvynvyn Studios

Sign in

No account?

Create account

Contact Fenvynvyn Studios

We typically reply within 15 minutes during business hours. For urgent studio bookings, call our Toronto desk.

Send us a message

Fill out the form and we’ll get back to you shortly.

0/2000

Avg. response in 15:00

Message sent

Thank you! We’ve received your request and will get back to you shortly.

Ticket ID: FV-0000

Submission error

Please review the following and try again.

    Privacy Policy

    We collect your name, email, phone number, and message to process your request. We do not sell or rent your data. Data may be stored in Canada and processed by our support tools.

    By submitting the form, you consent to being contacted by Fenvynvyn Studios. You can request data removal at any time by emailing [email protected].

    Cookies are used to remember your preferences and analyze basic anonymous usage. You can change preferences below.

    We use cookies to improve your experience. You can accept or manage preferences.

    Fenvynvyn Studios logo Fenvynvyn Studios

    Sign in

    No account?

    Create account

    `; } }catch(e){ el.innerHTML = `
    Fenvynvyn Studios logo Fenvynvyn Studios

    Sign in

    No account?

    Create account

    `; } }; const mountFooter = async () => { const el = document.getElementById('footer-mount'); try{ const r = await fetch('./footer.html',{credentials:'same-origin'}); if(r.ok){ const html = await r.text(); el.innerHTML = html; }else{ el.innerHTML = ` `; } }catch(e){ el.innerHTML = ` `; } }; const themeToggleBtn = document.getElementById('themeToggle'); themeToggleBtn.addEventListener('click', () => { const html = document.documentElement; const isDark = html.classList.toggle('dark'); try{ localStorage.setItem('fv_theme', isDark ? 'dark' : 'light'); }catch(e){} const icon = document.getElementById('themeIcon'); if(isDark){ icon.innerHTML = ''; }else{ icon.innerHTML = ''; } }); const cookieBanner = document.getElementById('cookieBanner'); const acceptCookies = document.getElementById('cookiesAccept'); const declineCookies = document.getElementById('cookiesDecline'); function hideCookieBanner(){ cookieBanner.classList.add('hidden'); } function showCookieBanner(){ cookieBanner.classList.remove('hidden'); } (function initCookies(){ try{ const pref = localStorage.getItem('fv_cookies'); if(pref){ hideCookieBanner(); } else { showCookieBanner(); } }catch(e){ showCookieBanner(); } })(); acceptCookies.addEventListener('click', () => { try{ localStorage.setItem('fv_cookies', JSON.stringify({status:'accepted', ts:Date.now()})); }catch(e){} hideCookieBanner(); }); declineCookies.addEventListener('click', () => { try{ localStorage.setItem('fv_cookies', JSON.stringify({status:'declined', ts:Date.now()})); }catch(e){} hideCookieBanner(); }); const slaSpan = document.getElementById('slaTimer'); let slaRem = 15*60; function fmt(n){ const m=Math.floor(n/60), s=n%60; return `${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`; } slaSpan.textContent = fmt(slaRem); const slaInt = setInterval(()=>{ slaRem = Math.max(0, slaRem-1); slaSpan.textContent = fmt(slaRem); if(slaRem===0) clearInterval(slaInt); },1000); function openDialog(d){ const c = d.querySelector('.q0y8m'); d.showModal(); requestAnimationFrame(()=>{ c.classList.remove('u3w7e'); c.classList.add('r7g1c'); }); } function closeDialog(d){ const c = d.querySelector('.q0y8m'); c.classList.remove('r7g1c'); c.classList.add('u3w7e'); setTimeout(()=>d.close(),150); } const successModal = document.getElementById('successModal'); const errorModal = document.getElementById('errorModal'); const policyModal = document.getElementById('policyModal'); document.getElementById('successClose').addEventListener('click', ()=>closeDialog(successModal)); document.getElementById('errorClose').addEventListener('click', ()=>closeDialog(errorModal)); document.getElementById('policyClose').addEventListener('click', ()=>closeDialog(policyModal)); document.getElementById('openPolicy').addEventListener('click', ()=>openDialog(policyModal)); [successModal,errorModal,policyModal].forEach(d=>{ d.addEventListener('cancel', (e)=>{ e.preventDefault(); closeDialog(d); }); d.addEventListener('click', (e)=>{ const r = d.getBoundingClientRect(); if(!(e.clientX>=r.left && e.clientX<=r.right && e.clientY>=r.top && e.clientY<=r.bottom)){ closeDialog(d); } }); }); const form = document.getElementById('contactForm'); const submitBtn = document.getElementById('submitBtn'); const resetBtn = document.getElementById('resetBtn'); const charCount = document.getElementById('charCount'); const ticketIdEl = document.getElementById('ticketId'); const errorList = document.getElementById('errorList'); const fields = { name: form.querySelector('#name'), email: form.querySelector('#email'), phone: form.querySelector('#phone'), topic: form.querySelector('#topic'), message: form.querySelector('#message'), consent: form.querySelector('#consent'), website: form.querySelector('#fv_website') }; fields.message.addEventListener('input', ()=>{ charCount.textContent = fields.message.value.length; }); function setError(name, msg){ const p = form.querySelector(`[data-error-for="${name}"]`); if(p){ p.textContent = msg || ''; p.classList.toggle('hidden', !msg); } } function validate(){ let ok = true; errorList.innerHTML = ''; Object.keys(fields).forEach(k=>setError(k,'')); // honeypot if(fields.website.value.trim()!==''){ ok=false; const li=document.createElement('li'); li.textContent='Unexpected form value detected.'; errorList.appendChild(li); } // name const nameVal = fields.name.value.trim(); if(!nameVal){ ok=false; setError('name','Name is required.'); } else if(!/^[A-Za-zÀ-ÿ'’ -]{2,80}$/.test(nameVal)){ ok=false; setError('name','Use letters, spaces, apostrophes (2–80 chars).'); } // email const emailVal = fields.email.value.trim(); if(!emailVal){ ok=false; setError('email','Email is required.'); } else if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailVal)){ ok=false; setError('email','Enter a valid email address.'); } // phone const phoneVal = fields.phone.value.trim(); if(phoneVal && !/^(\+?\d{1,2}\s?)?(\(?\d{3}\)?[\s.-]?)?\d{3}[\s.-]?\d{4}$/.test(phoneVal)){ ok=false; setError('phone','Enter a valid phone number.'); } // topic if(!fields.topic.value){ ok=false; setError('topic','Please choose a topic.'); } // message const msgVal = fields.message.value.trim(); if(msgVal.length<20){ ok=false; setError('message','Message must be at least 20 characters.'); } // consent if(!fields.consent.checked){ ok=false; setError('consent','You must accept the Privacy Policy.'); } if(!ok){ // Collect visible errors ['name','email','phone','topic','message','consent'].forEach(k=>{ const p = form.querySelector(`[data-error-for="${k}"]`); if(p && !p.classList.contains('hidden')){ const li=document.createElement('li'); li.textContent=p.textContent; errorList.appendChild(li); } }); } return ok; } async function submitForm(){ if(!validate()){ openDialog(errorModal); return; } submitBtn.disabled = true; submitBtn.classList.add('opacity-60','cursor-not-allowed'); submitBtn.innerHTML = ` Sending...`; const data = new FormData(form); data.append('ts', String(Date.now())); const controller = new AbortController(); const timeout = setTimeout(()=>controller.abort(), 15000); try{ const res = await fetch(form.getAttribute('action') || './send.php', { method: 'POST', body: data, signal: controller.signal, headers: {'Accept':'application/json, text/plain, */*'} }); clearTimeout(timeout); let ok = res.ok; let ticket = 'FV-' + Math.floor(100000 + Math.random()*900000); try{ const ct = res.headers.get('content-type') || ''; if(ct.includes('application/json')){ const j = await res.json(); if(typeof j.success==='boolean') ok = j.success; if(j.ticket) ticket = String(j.ticket); }else{ const text = await res.text(); if(/success/i.test(text)) ok = true; } }catch(e){} if(ok){ ticketIdEl.textContent = ticket; openDialog(successModal); form.reset(); charCount.textContent = '0'; }else{ errorList.innerHTML = ''; const li = document.createElement('li'); li.textContent = 'Server did not accept the submission. Please try again later.'; errorList.appendChild(li); openDialog(errorModal); } }catch(err){ clearTimeout(timeout); errorList.innerHTML = ''; const li = document.createElement('li'); li.textContent = 'Network error or timeout. Please check your connection and try again.'; errorList.appendChild(li); openDialog(errorModal); }finally{ submitBtn.disabled = false; submitBtn.classList.remove('opacity-60','cursor-not-allowed'); submitBtn.innerHTML = ` Send message `; } } form.addEventListener('submit', (e)=>{ e.preventDefault(); submitForm(); }); resetBtn.addEventListener('click', ()=>{ form.reset(); Object.keys(fields).forEach(k=>setError(k,'')); charCount.textContent='0'; }); document.addEventListener('keydown',(e)=>{ if(e.key==='Escape'){ [successModal,errorModal,policyModal].forEach(d=>{ if(d.open) closeDialog(d); }); } }); mountHeader(); mountFooter();