Instant Oil Change Quote

Use your VIN or enter your vehicle details to get a recommended oil type & estimated price.
We quote standard gasoline U.S. passenger vehicles. Lexus & Acura are supported (including hybrids).
No EVs, diesels, most luxury/high-end brands, or fleet/commercial vehicles.

Option 1: VIN Search



Option 2: No VIN?

Enter your vehicle info (gasoline passenger vehicles only).







EV, diesel, most luxury/high-end brands, and fleet/commercial vehicles are not quoted online.


<script>
(function() {
  const BOOKING_URL = "/schedule";

  // ==== YOUR MARGINS (unchanged) ====
  const COST_PER_QUART = {
    conventional:   5.50,
    syntheticBlend: 6.40,
    fullSynthetic:  7.25,
    highMileage:    8.50
  };
  const OIL_FILTER_COST_DEFAULT    = 15;
  const CABIN_FILTER_COST_DEFAULT  = 15;
  const ENGINE_FILTER_COST_DEFAULT = 15;
  const WIPER_BLADE_COST_DEFAULT   = 40;
  const OIL_CHANGE_PROFIT   = 40;
  const ADD_ON_PROFIT       = 10;
  const TIRE_ROTATION_PRICE = 10;
  const HIGH_MILEAGE_THRESHOLD = 75000;

  const OIL_LABELS = {
    conventional:   "Conventional",
    syntheticBlend: "Synthetic Blend",
    fullSynthetic:  "Full Synthetic",
    highMileage:    "High Mileage Full Synthetic"
  };

  // Exclusions (unchanged)
  const EXCLUDED_MAKES = [
    "TESLA","LUCID","RIVIAN","POLESTAR","VOLVO",
    "MERCEDES-BENZ","MERCEDES","BMW","AUDI","PORSCHE","JAGUAR",
    "LAND ROVER","RANGE ROVER","MASERATI","BENTLEY","ROLLS-ROYCE",
    "FERRARI","LAMBORGHINI","ASTON MARTIN","MCLAREN","MAYBACH",
    "ALFA ROMEO","GENESIS","LEXUS","INFINITI","ACURA","CADILLAC","LINCOLN"
  ];

  function isExcluded(make, fuelType) {
    const m = (make || "").toUpperCase().trim();
    const f = (fuelType || "").toLowerCase();
    if (EXCLUDED_MAKES.includes(m)) return true;
    if (f.includes("electric") || f.includes("ev") || f.includes("hybrid") || f.includes("diesel")) return true;
    return false;
  }

  function parseMileage(raw) {
    if (!raw) return null;
    const miles = parseInt(raw.replace(/,/g, "").trim(), 10);
    return isNaN(miles) ? null : miles;
  }

  // === Local safety net: known overrides when you want to guarantee accuracy ===
  // Keyed by VIN OR by a coarse YMME signature.
  const CAPACITY_OVERRIDES = {
    // VIN-specific override (your example)
    "VIN:1HGCM82633A004352": { quarts: 4.6, note: "Local override: 2003 Accord 3.0 V6" },
    // Example signature fallback: "YEAR|MAKE|MODEL|DISPLACEMENT_L"
    "2003|HONDA|ACCORD|3.0": { quarts: 4.6, note: "Local override: 2003 Accord 3.0 V6" }
  };

  function signatureKey({year, make, model, displacementL}) {
    const y = String(year||"").trim();
    const mk = String(make||"").toUpperCase().trim();
    const md = String(model||"").toUpperCase().trim();
    const dl = displacementL ? String(displacementL).trim() : "";
    return `${y}|${mk}|${md}|${dl}`;
  }

  // === CALL YOUR SERVERLESS PROXY to fetch accurate oil capacity by VIN/YMM ===
  // Implement a backend at /api/oil-capacity that accepts VIN or (year,make,model,engine)
  // and returns { quarts: Number, viscosity: "0W-20" } when available.
  async function fetchOilCapacity({ vin, year, make, model, engineCyl, displacementL }) {
    // 1) Check strict VIN override
    if (vin && CAPACITY_OVERRIDES["VIN:"+vin]) return CAPACITY_OVERRIDES["VIN:"+vin];

    // 2) Try serverless proxy (add your endpoint URL below)
    const url = new URL("/api/oil-capacity", window.location.origin);
    if (vin) url.searchParams.set("vin", vin);
    if (year) url.searchParams.set("year", year);
    if (make) url.searchParams.set("make", make);
    if (model) url.searchParams.set("model", model);
    if (engineCyl) url.searchParams.set("engineCyl", engineCyl);
    if (displacementL) url.searchParams.set("displacementL", displacementL);

    try {
      const res = await fetch(url.toString(), { method: "GET" });
      if (res.ok) {
        const data = await res.json();
        if (data && typeof data.quarts === "number" && data.quarts > 0) {
          return { quarts: data.quarts, note: "API capacity", viscosity: data.viscosity || null };
        }
      }
    } catch(e) {
      // swallow and fallback
    }

    // 3) Check coarse signature override
    const sig = signatureKey({year, make, model, displacementL});
    if (CAPACITY_OVERRIDES[sig]) return CAPACITY_OVERRIDES[sig];

    // 4) Last resort: estimate by cylinder count (your current heuristic)
    let est;
    const cyl = parseInt(engineCyl || 4, 10);
    if (cyl <= 4) est = 5;
    else if (cyl <= 6) est = 6;   // note: many V6s are ~4.5–6.0, this is only a fallback
    else if (cyl <= 8) est = 7;
    else est = 6;
    return { quarts: est, note: "Estimated capacity (fallback)" };
  }

  function determineRecommendedOilType(fuelType, year, mileage) {
    const yr   = parseInt(year, 10) || 2015;
    const fuel = (fuelType || "gasoline").toLowerCase();
    let oilTypeKey = "syntheticBlend";
    if (yr >= 2018) oilTypeKey = "fullSynthetic";
    if (mileage && mileage >= HIGH_MILEAGE_THRESHOLD) oilTypeKey = "highMileage";
    if (fuel.includes("diesel")) oilTypeKey = "fullSynthetic";
    return oilTypeKey;
  }

  function calculateOilChangePrice(quarts, oilTypeKey, oilFilterCost) {
    const costPerQt = COST_PER_QUART[oilTypeKey] || COST_PER_QUART.syntheticBlend;
    const oilCost   = quarts * costPerQt;
    const partsCost = oilCost + oilFilterCost;
    const customerPrice = partsCost + OIL_CHANGE_PROFIT;
    return { partsCost, customerPrice };
  }

  function getAddOns(cabinCost, engineCost, wiperCost) {
    return [
      { id: "tireRotation", label: "Tire Rotation", displayPrice: TIRE_ROTATION_PRICE },
      { id: "cabinFilter", label: "Cabin Air Filter Replacement", displayPrice: cabinCost + ADD_ON_PROFIT },
      { id: "engineFilter", label: "Engine Air Filter Replacement", displayPrice: engineCost + ADD_ON_PROFIT },
      { id: "wipers", label: "Windshield Wiper Blade Replacement (pair)", displayPrice: wiperCost + ADD_ON_PROFIT }
    ];
  }

  // (optional) Hidden "Instant Quote Log" submission (unchanged from your last version)
  function findInstantQuoteLogForm() {
    const contexts = document.querySelectorAll(".sqs-form-block-context");
    for (const script of contexts) {
      try {
        const cfg = JSON.parse(script.textContent.trim());
        if (cfg.formName === "Instant Quote Log") {
          const block = script.closest(".sqs-block-form, .sqs-block-website-component");
          if (!block) continue;
          const form = block.querySelector("form");
          if (!form) continue;
          let quoteFieldId = null;
          if (Array.isArray(cfg.formFields)) {
            const fd = cfg.formFields.find(f => f.title === "Quote Details");
            if (fd && fd.id) quoteFieldId = fd.id + "-field";
          }
          return { form, quoteFieldId };
        }
      } catch(e){}
    }
    return null;
  }
  function fillAndSubmitInstantQuoteLog(quoteSummary) {
    const info = findInstantQuoteLogForm();
    if (!info) return false;
    let quoteField = info.quoteFieldId ? document.getElementById(info.quoteFieldId) : null;
    if (!quoteField) {
      const items = info.form.querySelectorAll(".form-item.field.textarea");
      for (const item of items) {
        const span = item.querySelector("label span");
        if (span && span.textContent.trim() === "Quote Details") {
          const ta = item.querySelector("textarea");
          if (ta) { quoteField = ta; break; }
        }
      }
    }
    if (!quoteField) {
      const tas = info.form.querySelectorAll("textarea");
      if (tas.length === 1) quoteField = tas[0];
    }
    if (!quoteField) return false;
    quoteField.value = quoteSummary;
    quoteField.dispatchEvent(new Event("input",  { bubbles: true }));
    quoteField.dispatchEvent(new Event("change", { bubbles: true }));
    const submitBtn = info.form.querySelector('button[type="submit"], .form-submit-button');
    if (submitBtn) submitBtn.click();
    else info.form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
    return true;
  }

  async function renderQuote({ sourceLabel, year, make, model, trim, engineCyl, fuelType, mileage, vin, displacementL }) {
    const container = document.getElementById("quoteResult");
    container.innerHTML = "";

    if (isExcluded(make, fuelType)) {
      container.innerHTML = '<p style="color:red;">We do not provide instant quotes for EV, hybrid, diesel, or luxury/high-end vehicles.</p>';
      return;
    }
    if (!mileage) {
      container.innerHTML = '<p style="color:red;">Mileage is required to generate an accurate quote.</p>';
      return;
    }

    // === NEW: get precise quarts ===
    const cap = await fetchOilCapacity({ vin, year, make, model, engineCyl, displacementL });
    const quarts = cap.quarts;

    const recommendedOilTypeKey = determineRecommendedOilType(fuelType, year, mileage);
    let selectedOilTypeKey = recommendedOilTypeKey;

    const oilFilterCost    = OIL_FILTER_COST_DEFAULT;
    const cabinFilterCost  = CABIN_FILTER_COST_DEFAULT;
    const engineFilterCost = ENGINE_FILTER_COST_DEFAULT;
    const wiperCost        = WIPER_BLADE_COST_DEFAULT;

    let quote  = calculateOilChangePrice(quarts, selectedOilTypeKey, oilFilterCost);
    const addOns = getAddOns(cabinFilterCost, engineFilterCost, wiperCost);

    const vehicleLabel = [year, make, model, trim].filter(Boolean).join(" ");
    const mileageLabel = mileage.toLocaleString() + " miles";

    const vehicleSummaryHtml = `
      <h3>Estimated Quote</h3>
      <p style="font-size:0.9rem;color:#555;">Source: ${sourceLabel}${cap.note ? " · " + cap.note : ""}</p>
      <p><strong>Vehicle:</strong> ${vehicleLabel || "Not provided"}</p>
      <p><strong>Engine:</strong> ${engineCyl ? engineCyl + "-cylinder" : "Not specified"}${displacementL ? " · " + displacementL + "L" : ""}</p>
      <p><strong>Mileage:</strong> ${mileageLabel}</p>
      <p><strong>Oil Capacity:</strong> ${quarts.toFixed(1)} qt</p>
      ${vin ? `<p><strong>VIN:</strong> ${vin}</p>` : "" }
    `;

    const highMileageNote = (selectedOilTypeKey === "highMileage")
      ? `<p style="color:#2563eb;font-size:0.85rem;">Based on your mileage (≥ ${HIGH_MILEAGE_THRESHOLD.toLocaleString()}), we recommend a high mileage full synthetic.</p>`
      : "";

    const oilOptionsHtml = `
      <h4>Select Oil Type</h4>
      <p style="font-size:0.85rem;">We recommend <strong>${OIL_LABELS[recommendedOilTypeKey]}</strong>, but you can choose another option.</p>
      <div>
        ${["conventional","syntheticBlend","fullSynthetic","highMileage"].map(key => `
          <label style="display:block;margin:2px 0;font-size:0.9rem;">
            <input type="radio" name="oilTypeOption" value="${key}" ${key === selectedOilTypeKey ? "checked" : ""}>
            ${OIL_LABELS[key]}${key === recommendedOilTypeKey ? " (Recommended)" : ""}
          </label>
        `).join("")}
      </div>
    `;

    const breakdownHtml = `
      <h4>Oil Change Price Breakdown</h4>
      <ul id="breakdownList" style="list-style:none;padding-left:0;font-size:0.9rem;">
        <li>• Oil capacity used: ${quarts.toFixed(1)} qt</li>
        <li>• Selected oil type: ${OIL_LABELS[selectedOilTypeKey]}</li>
        <li>• Estimated oil change total (incl. parts, labor & mobile service): $${quote.customerPrice.toFixed(2)}</li>
      </ul>
    `;

    const addOnsHtml = `
      <h4>Optional Add-On Services</h4>
      ${addOns.map(a => `
        <label style="display:block;margin:4px 0;font-size:0.9rem;">
          <input type="checkbox" class="addon-checkbox" data-label="${a.label}" data-price="${a.displayPrice}">
          ${a.label} (+$${a.displayPrice.toFixed(2)})
        </label>
      `).join("")}
    `;

    container.innerHTML = `
      ${vehicleSummaryHtml}
      ${highMileageNote}
      ${oilOptionsHtml}
      ${breakdownHtml}
      ${addOnsHtml}
      <h2 style="margin-top:15px;font-size:1.4rem;">
        Your Estimated Total: $<span id="finalTotal">${quote.customerPrice.toFixed(2)}</span>
      </h2>
      <p style="font-size:0.75rem;color:#777;">*Estimate. Final pricing confirmed on-site.</p>
      <button id="bookServiceBtn"
              style="padding:10px 20px;border:none;border-radius:4px;cursor:pointer;background-color:#111827;color:#fff;font-size:1rem;">
        Continue to Schedule & Book
      </button>
    `;

    const radios          = container.querySelectorAll('input[name="oilTypeOption"]');
    const addOnCheckboxes = container.querySelectorAll(".addon-checkbox");
    const totalEl         = document.getElementById("finalTotal");
    const breakdownList   = document.getElementById("breakdownList");
    const bookBtn         = document.getElementById("bookServiceBtn");
    let currentBaseTotal  = quote.customerPrice;

    function getAddOnsTotal() {
      let t = 0;
      addOnCheckboxes.forEach(cb => { if (cb.checked) t += parseFloat(cb.dataset.price) || 0; });
      return t;
    }
    function getSelectedAddOnsLabels() {
      const labels = [];
      addOnCheckboxes.forEach(cb => { if (cb.checked) labels.push(cb.dataset.label); });
      return labels;
    }
    function updateTotals() {
      const grand = currentBaseTotal + getAddOnsTotal();
      totalEl.textContent = grand.toFixed(2);
      breakdownList.innerHTML = `
        <li>• Oil capacity used: ${quarts.toFixed(1)} qt</li>
        <li>• Selected oil type: ${OIL_LABELS[selectedOilTypeKey]}</li>
        <li>• Estimated oil change total (incl. parts, labor & mobile service): $${currentBaseTotal.toFixed(2)}</li>
      `;
    }

    radios.forEach(r => {
      r.addEventListener("change", () => {
        const checked = container.querySelector('input[name="oilTypeOption"]:checked');
        if (!checked) return;
        selectedOilTypeKey = checked.value;
        const newQuote = calculateOilChangePrice(quarts, selectedOilTypeKey, OIL_FILTER_COST_DEFAULT);
        currentBaseTotal = newQuote.customerPrice;
        updateTotals();
      });
    });
    addOnCheckboxes.forEach(cb => cb.addEventListener("change", updateTotals));
    updateTotals();

    // Send log + redirect with URL params
    bookBtn.addEventListener("click", () => {
      const finalTotal     = totalEl.textContent;
      const oilLabel       = OIL_LABELS[selectedOilTypeKey];
      const addonsSelected = getSelectedAddOnsLabels();

      const quoteLines = [
        "Instant Quote Details:",
        vehicleLabel ? "Vehicle: " + vehicleLabel : "",
        engineCyl ? "Engine: " + engineCyl + "-cylinder" : "",
        displacementL ? "Displacement: " + displacementL + "L" : "",
        mileageLabel ? "Mileage: " + mileageLabel : "",
        "Oil Capacity: " + quarts.toFixed(1) + " qt",
        oilLabel ? "Oil Type: " + oilLabel : "",
        addonsSelected.length ? "Selected Add-Ons: " + addonsSelected.join(", ") : "",
        finalTotal ? "Estimated Total: $" + finalTotal : "",
        vin ? "VIN: " + vin : "",
        sourceLabel ? "Quote Source: " + sourceLabel : ""
      ].filter(Boolean);
      const quoteSummary = quoteLines.join("\n");

      // (optional) fire hidden logging form
      fillAndSubmitInstantQuoteLog(quoteSummary);

      const params = new URLSearchParams({
        vehicle: vehicleLabel || "",
        engine: engineCyl ? engineCyl + "-cylinder" : "",
        displacementL: displacementL || "",
        mileage: mileageLabel || "",
        oilCapacityQt: quarts.toFixed(1),
        oil: oilLabel || "",
        addons: addonsSelected.join(", "),
        total: finalTotal || "",
        source: sourceLabel || ""
      });
      if (vin) params.append("vin", vin);
      window.location.href = BOOKING_URL + "?" + params.toString();
    });
  }

  // VIN handler
  const vinInput        = document.getElementById("vinInput");
  const vinMileageInput = document.getElementById("vinMileageInput");
  const vinButton       = document.getElementById("vinButton");
  const vinMessage      = document.getElementById("vinMessage");

  vinButton.addEventListener("click", () => {
    const vin = (vinInput.value || "").trim().toUpperCase();
    const mileage = parseMileage(vinMileageInput.value);
    const result = document.getElementById("quoteResult");
    result.innerHTML = "";
    vinMessage.style.color = "#333";
    vinMessage.textContent = "";

    if (vin.length !== 17) {
      vinMessage.style.color = "red";
      vinMessage.textContent = "Please enter a valid 17-character VIN.";
      return;
    }
    if (!mileage) {
      vinMessage.style.color = "red";
      vinMessage.textContent = "Please enter current mileage.";
      return;
    }

    vinMessage.textContent = "Decoding VIN and calculating your quote...";

    fetch("https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVinValuesExtended/" + vin + "?format=json")
      .then(res => res.json())
      .then(async (data) => {
        const info = data && data.Results && data.Results[0];
        if (!info || (!info.Make && !info.Model)) {
          vinMessage.style.color = "red";
          vinMessage.textContent = "We couldn't decode that VIN. Please double-check and try again.";
          return;
        }
        vinMessage.textContent = "";
        const year  = info.ModelYear || "";
        const make  = info.Make || "";
        const model = info.Model || "";
        const engineCyl = info.EngineCylinders || "";
        const fuelType  = info.FuelTypePrimary || "Gasoline";
        const displacementL = info.DisplacementL || info.DisplacementCI || ""; // VPIC may provide L

        renderQuote({
          sourceLabel: "VIN Decoded",
          year, make, model, trim: "",
          engineCyl, fuelType, mileage, vin, displacementL
        });
      })
      .catch(() => {
        vinMessage.style.color = "red";
        vinMessage.textContent = "Error connecting to VIN service. Please try again.";
      });
  });

  // Manual handler
  const manYear    = document.getElementById("manYear");
  const manMake    = document.getElementById("manMake");
  const manModel   = document.getElementById("manModel");
  const manTrim    = document.getElementById("manTrim");
  const manEngine  = document.getElementById("manEngine");
  const manMileage = document.getElementById("manMileage");
  const manButton  = document.getElementById("manButton");
  const manMessage = document.getElementById("manMessage");

  manButton.addEventListener("click", () => {
    const result = document.getElementById("quoteResult");
    result.innerHTML = "";
    manMessage.style.color = "#333";
    manMessage.textContent = "";

    const year      = manYear.value.trim();
    const make      = manMake.value.trim();
    const model     = manModel.value.trim();
    const trim      = manTrim.value.trim();
    const engineRaw = manEngine.value.trim();
    const mileage   = parseMileage(manMileage.value);

    if (!year || !make || !model || !engineRaw) {
      manMessage.style.color = "red";
      manMessage.textContent = "Please enter year, make, model, and engine size/cylinders.";
      return;
    }
    if (!mileage) {
      manMessage.style.color = "red";
      manMessage.textContent = "Please enter current mileage.";
      return;
    }

    let engineCyl = parseInt(engineRaw, 10);
    if (isNaN(engineCyl)) engineCyl = 4;

    if (isExcluded(make, "gasoline")) {
      manMessage.style.color = "red";
      manMessage.textContent = "We do not provide instant quotes for this vehicle. Please contact us directly.";
      return;
    }

    renderQuote({
      sourceLabel: "Manual Vehicle Entry",
      year, make, model, trim,
      engineCyl, fuelType: "Gasoline",
      mileage, vin: "", displacementL: "" // you can add a field to capture liters if desired
    });
  });
})();
</script>