RFQ
Custom Metal Foil & Laser Cutting Quote
Request pricing and engineering review for custom metal foil, thin sheet, samples, small-batch prototypes, micro holes and ultrafast laser cutting.
(function () {
const form = document.querySelector(“#ffqForm”);
if (!form) return;
const summaryNodes = document.querySelectorAll(“[data-ffq-summary]”);
const priceNodes = document.querySelectorAll(“[data-ffq-price]”);
const fileInput = document.querySelector(“#ffqDrawingFile”);
const fileList = document.querySelector(“#ffqFileList”);
const uploadZone = document.querySelector(“#ffqUploadZone”);
const status = document.querySelector(“#ffqSubmitStatus”);
const preview = document.querySelector(“#ffqMailPreview”);
const previewText = document.querySelector(“#ffqMailPreviewText”);
const currency = new Intl.NumberFormat(“en-US”, { style: “currency”, currency: “USD”, minimumFractionDigits: 2 });
const checkedValue = (name) => form.querySelector(`input[name=”${name}”]:checked`)?.value || “”;
const fieldValue = (name) => form.querySelector(`[name=”${name}”]`)?.value?.trim() || “”;
const checkedList = (name) => Array.from(form.querySelectorAll(`input[name=”${name}”]:checked`)).map((item) => item.value);
function getData() {
const quantity = Math.max(Number(fieldValue(“quantity”)) || 1, 1);
const budget = Number(fieldValue(“targetBudget”)) || 0;
return {
projectName: fieldValue(“projectName”) || “Unnamed RFQ”,
material: checkedValue(“material”),
length: Number(fieldValue(“length”)) || 0,
width: Number(fieldValue(“width”)) || 0,
thickness: fieldValue(“thickness”),
cutLength: fieldValue(“cutLength”),
features: Number(fieldValue(“features”)) || 0,
quantity,
process: checkedValue(“process”),
tolerance: checkedValue(“tolerance”),
unit: checkedValue(“unit”),
edge: checkedValue(“edge”),
documents: checkedList(“documents”),
leadTime: fieldValue(“leadTime”),
budget,
remark: fieldValue(“remark”),
contactEmail: fieldValue(“contactEmail”),
contactName: fieldValue(“contactName”),
company: fieldValue(“company”),
country: fieldValue(“country”),
};
}
function setSummary(key, value) {
const node = Array.from(summaryNodes).find((item) => item.dataset.ffqSummary === key);
if (node) node.textContent = value;
}
function setPrice(key, value) {
const node = Array.from(priceNodes).find((item) => item.dataset.ffqPrice === key);
if (node) node.textContent = value;
}
function updateSummary() {
const data = getData();
const docs = data.documents.length ? data.documents.join(“, “) : “None selected”;
setSummary(“material”, data.material);
setSummary(“size”, `${data.length || 0} mm x ${data.width || 0} mm`);
setSummary(“thickness”, data.thickness || “Not specified”);
setSummary(“process”, data.process);
setSummary(“features”, `${data.features} holes / slots`);
setSummary(“quantity”, `${data.quantity} pcs`);
setSummary(“documents”, docs);
setSummary(“tolerance”, data.tolerance);
setPrice(“process”, data.process === “Material supply only” ? “Material review” : “Laser review”);
setPrice(“documents”, data.documents.length ? “Required” : “Optional”);
setPrice(“budget”, currency.format(data.budget));
setPrice(“unit”, data.budget ? `Unit target: ${currency.format(data.budget / data.quantity)} / pc` : “Unit target: not set”);
}
function formatFileSize(size) {
if (size < 1024) return `${size} B`;
if (size {
const item = document.createElement(“li”);
const name = document.createElement(“span”);
const size = document.createElement(“small”);
name.textContent = file.name;
size.textContent = formatFileSize(file.size);
item.append(name, size);
fileList.append(item);
});
}
function setStatus(message, type) {
status.textContent = message;
status.className = `ffq-submit-status${type ? ` is-${type}` : “”}`;
}
function isValidEmail(value) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
function buildPreview(data) {
const files = Array.from(fileInput.files || []).map((file) => file.name).join(“, “) || “No files selected; drawing link may be included in notes.”;
const docs = data.documents.length ? data.documents.join(“, “) : “None selected”;
return [
`Subject: Finalfoil RFQ – ${data.projectName}`,
“”,
`Name: ${data.contactName || “Not provided”}`,
`Email: ${data.contactEmail}`,
`Company: ${data.company || “Not provided”}`,
`Delivery country: ${data.country || “Not provided”}`,
“”,
“Material and geometry:”,
`- Material: ${data.material}`,
`- Part size: ${data.length || 0} mm x ${data.width || 0} mm`,
`- Thickness: ${data.thickness || “Not specified”}`,
`- Cut length: ${data.cutLength || “Not specified”} m`,
`- Feature count: ${data.features}`,
`- Quantity: ${data.quantity} pcs`,
“”,
“Processing:”,
`- Process route: ${data.process}`,
`- Tolerance: ${data.tolerance}`,
`- Drawing unit: ${data.unit}`,
`- Edge / surface: ${data.edge}`,
`- Lead time: ${data.leadTime}`,
“”,
`Documents: ${docs}`,
`Target budget: ${currency.format(data.budget)}`,
`Drawing files: ${files}`,
“”,
`Requirement notes: ${data.remark || “None”}`,
].join(“\n”);
}
async function handleSubmit(event) {
event.preventDefault();
let data = getData();
form.querySelectorAll(“.is-invalid”).forEach((node) => node.classList.remove(“is-invalid”));
if (!isValidEmail(data.contactEmail) && window.FinalfoilLeads?.captureEmail) {
try {
const capturedEmail = await window.FinalfoilLeads.captureEmail(“custom_quote”);
if (capturedEmail) {
form.elements.contactEmail.value = capturedEmail;
data = getData();
}
} catch (error) {
preview.hidden = true;
setStatus(error.message || “Please enter a valid email address.”, “error”);
return;
}
}
if (!isValidEmail(data.contactEmail)) {
form.elements.contactEmail.classList.add(“is-invalid”);
preview.hidden = true;
setStatus(“Please enter a valid email address.”, “error”);
return;
}
const button = form.querySelector(“.ffq-primary-action”);
const text = buildPreview(data);
const drawingFiles = Array.from(fileInput.files || []).map((file) => ({
name: file.name,
size: file.size,
type: file.type || “”,
}));
previewText.textContent = text;
preview.hidden = false;
setStatus(“Submitting RFQ package…”, “”);
if (button) button.disabled = true;
try {
if (!window.FinalfoilLeads?.submitLead) {
throw new Error(“Lead capture is not available.”);
}
await window.FinalfoilLeads.submitLead({
source: “custom_quote”,
stage: “full_quote”,
email: data.contactEmail,
payload: {
…data,
drawingFiles,
emailPreview: text,
page: window.location.href,
},
});
window.sessionStorage?.setItem(“finalfoilLeadEmail”, data.contactEmail);
setStatus(“RFQ submitted. Finalfoil will review your request.”, “success”);
} catch (error) {
setStatus(error.message || “RFQ submission failed.”, “error”);
} finally {
if (button) button.disabled = false;
}
}
document.querySelectorAll(“.ffq-section-heading”).forEach((button) => {
button.addEventListener(“click”, () => {
const body = button.nextElementSibling;
const expanded = button.getAttribute(“aria-expanded”) === “true”;
button.setAttribute(“aria-expanded”, String(!expanded));
body.classList.toggle(“is-collapsed”, expanded);
});
});
document.querySelectorAll(“.ffq-stepper”).forEach((stepper) => {
const input = stepper.querySelector(“input”);
stepper.querySelectorAll(“button”).forEach((button) => {
button.addEventListener(“click”, () => {
const current = Number(input.value) || 1;
input.value = button.dataset.action === “plus” ? current + 1 : Math.max(current – 1, 1);
updateSummary();
});
});
});
fileInput.addEventListener(“change”, updateFileList);
[“dragenter”, “dragover”].forEach((eventName) => uploadZone.addEventListener(eventName, (event) => {
event.preventDefault();
uploadZone.classList.add(“is-dragover”);
}));
[“dragleave”, “drop”].forEach((eventName) => uploadZone.addEventListener(eventName, (event) => {
event.preventDefault();
uploadZone.classList.remove(“is-dragover”);
}));
uploadZone.addEventListener(“drop”, (event) => {
if (event.dataTransfer?.files?.length) {
fileInput.files = event.dataTransfer.files;
updateFileList();
}
});
form.addEventListener(“input”, updateSummary);
form.addEventListener(“change”, updateSummary);
form.addEventListener(“submit”, handleSubmit);
updateSummary();
})();
.ff-lead-modal{position:fixed;inset:0;z-index:99999;display:none;place-items:center;background:rgba(9,17,24,.58);padding:22px}.ff-lead-modal.is-open{display:grid}.ff-lead-dialog{width:min(420px,100%);padding:24px;border-radius:8px;background:#fff;box-shadow:0 30px 90px rgba(0,0,0,.28);color:#101923}.ff-lead-dialog h2{margin:0;color:#101923;font-size:24px;line-height:1.15}.ff-lead-dialog p{margin:10px 0 0;color:#5f6f7b;line-height:1.6}.ff-lead-dialog label{display:grid;gap:8px;margin-top:18px;color:#101923;font-size:13px;font-weight:850}.ff-lead-dialog input{min-height:46px;padding:0 13px;border:1px solid rgba(20,37,50,.16);border-radius:7px;font:inherit}.ff-lead-dialog button{width:100%;min-height:46px;margin-top:16px;border:0;border-radius:7px;background:#101923;color:#fff;font-weight:900}.ff-lead-error,.ff-lead-status{margin:10px 0 0;color:#b2382b;font-size:13px;line-height:1.5}.ff-lead-status.is-success{color:#0b6e3f}
(function(){var ep=”https://www.finalfoil.com/wp-json/finalfoil/v1/lead”,key=”finalfoilLeadEmail”,re=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;function ok(v){return re.test(String(v||””).trim())}function send(d){return fetch(ep,{method:”POST”,credentials:”same-origin”,headers:{“Content-Type”:”application/json”},body:JSON.stringify(d)}).then(function(r){return r.json().catch(function(){return{}}).then(function(j){if(!r.ok||!j.ok)throw new Error(j.message||”Lead submission failed.”);return j})})}function obj(f){var d={};new FormData(f).forEach(function(v,k){d[k]=d[k]?Array.isArray(d[k])?d[k].concat(v):[d[k],v]:v});return d}function modal(){var n=document.querySelector(“#ffLeadModal”);if(n)return n;n=document.createElement(“div”);n.id=”ffLeadModal”;n.className=”ff-lead-modal”;n.innerHTML=’
Continue with your email Enter your email so Finalfoil can keep this RFQ and follow up with engineering feedback.
Email Continue
‘;document.body.appendChild(n);return n}function cap(src){var saved=sessionStorage.getItem(key);if(ok(saved))return Promise.resolve(saved.trim());var n=modal(),i=n.querySelector(“#ffLeadEmailInput”),b=n.querySelector(“#ffLeadEmailSubmit”),e=n.querySelector(“#ffLeadEmailError”);n.classList.add(“is-open”);i.value=””;e.textContent=””;setTimeout(function(){i.focus()},30);return new Promise(function(res,rej){function done(){var mail=i.value.trim();if(!ok(mail)){e.textContent=”Please enter a valid email address.”;return}b.disabled=true;e.textContent=””;send({source:src||”custom_quote”,stage:”email_capture”,email:mail,payload:{email:mail,page:location.href,event:”first_interaction”}}).then(function(){sessionStorage.setItem(key,mail);n.classList.remove(“is-open”);res(mail)}).catch(function(err){e.textContent=err.message||”Lead submission failed.”;rej(err)}).finally(function(){b.disabled=false})}b.onclick=done;i.onkeydown=function(ev){if(ev.key===”Enter”){ev.preventDefault();done()}}})}function stat(f,m,t){var s=f.querySelector(“.ff-lead-status”);if(!s){s=document.createElement(“p”);s.className=”ff-lead-status”;s.setAttribute(“role”,”status”);f.appendChild(s)}s.textContent=m;s.className=”ff-lead-status”+(t?” is-“+t:””)}function home(){var f=document.querySelector(“#ffHomeLeadForm,[data-finalfoil-lead-form=’home’]”);if(!f)return;f.addEventListener(“submit”,function(ev){ev.preventDefault();var d=obj(f),mail=String(d.email||””).trim(),btn=f.querySelector(“button[type=’submit’]”);if(!ok(mail)){stat(f,”Please enter a valid email address.”,”error”);return}if(btn)btn.disabled=true;stat(f,”Submitting RFQ…”,””);send({source:”home”,stage:”home_submit”,email:mail,payload:d}).then(function(){sessionStorage.setItem(key,mail);stat(f,”RFQ submitted. Finalfoil will review your request.”,”success”);f.reset()}).catch(function(err){stat(f,err.message||”Submission failed.”,”error”)}).finally(function(){if(btn)btn.disabled=false})})}function quote(){var f=document.querySelector(“#ffqForm”);if(!f)return;var started=false;function trg(ev){if(started)return;if(ev.target.closest(“.ffq-primary-action,#ffqContactEmail”))return;if(!ev.target.closest(“input,select,textarea,button,label,.ffq-options span”))return;started=true;cap(“custom_quote”).then(function(mail){var ef=f.querySelector(“#ffqContactEmail”);if(ef&&!ef.value){ef.value=mail;ef.dispatchEvent(new Event(“input”,{bubbles:true}))}}).catch(function(){started=false})}f.addEventListener(“pointerdown”,trg,true);f.addEventListener(“focusin”,trg,true);f.addEventListener(“change”,trg,true)}window.FinalfoilLeads={submitLead:send,captureEmail:cap,storedEmail:function(){return sessionStorage.getItem(key)||””}};document.addEventListener(“DOMContentLoaded”,function(){home();quote()})})();