<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Golf Cart Range Estimator</title>
<style>
/* Base */
#gc-range-widget * { box-sizing: border-box; }
#gc-range-widget {
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
color:#111827; max-width:940px; margin:24px auto; padding:0 12px;
}
#gc-range-widget .gc-card{
background:#fff; border:1px solid #e5e7eb; border-radius:14px; padding:16px; margin:10px 0 18px;
box-shadow:0 1px 2px rgba(0,0,0,.04);
}
#gc-range-widget h3{ margin:0 0 10px; font-size:1.05rem; line-height:1.2; display:flex; align-items:center; gap:10px; }
#gc-range-widget .gc-stepnum{
display:inline-flex; width:28px; height:28px; border-radius:999px; align-items:center; justify-content:center;
background:#111827; color:#fff; font-weight:700; font-size:.9rem;
}
.muted{ color:#6b7280; font-size:.9rem; }
/* Grid + battery cards */
.gc-grid{ display:grid; gap:12px; grid-template-columns:repeat(1,minmax(0,1fr)); }
@media(min-width:640px){ .gc-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media(min-width:1024px){ .gc-grid{ grid-template-columns:repeat(3,minmax(0,1fr)); } }
.gc-batt-card{
display:flex; gap:12px; border:1px solid #e5e7eb; border-radius:12px; padding:12px;
transition:box-shadow .15s, border-color .15s; cursor:pointer; background:#fff;
}
.gc-batt-card:hover{ box-shadow:0 2px 8px rgba(0,0,0,.06); border-color:#d1d5db; }
.gc-batt-media{ width:92px; min-width:92px; height:92px; border-radius:10px; overflow:hidden; background:#f3f4f6; display:flex; align-items:center; justify-content:center; }
.gc-batt-media img{ width:100%; height:100%; object-fit:cover; display:block; }
.gc-batt-body{ flex:1; min-width:0; }
.gc-batt-head{ display:flex; align-items:center; gap:8px; margin-bottom:6px; }
.gc-batt-name{ font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* Shared controls / results */
.gc-flex{ display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.cap-opt{ flex:1 1 160px; display:flex; gap:10px; align-items:center; border:1px solid #e5e7eb; border-radius:10px; padding:10px; background:#fff; transition:box-shadow .15s, border-color .15s; }
select, button{ font:inherit; }
select{ width:100%; padding:10px 12px; border:1px solid #e5e7eb; border-radius:10px; background:#fff; transition: box-shadow .15s, border-color .15s; }
input[type="radio"]{ accent-color:#111827; }
/* Selection highlight */
.is-selected{
border-color:#111827 !important;
box-shadow:0 0 0 3px rgba(17,24,39,.12);
}
select.is-selected{
border-color:#111827 !important;
box-shadow:0 0 0 2px rgba(17,24,39,.12);
}
/* Result */
.gc-result{ background:#f9fafb; border:1px dashed #e5e7eb; border-radius:14px; padding:16px; text-align:center; }
.gc-miles{ font-weight:800; font-size:2.2rem; }
.gc-actions{ display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-top:12px; }
.gc-btn{ border:1px solid #111827; background:#111827; color:#fff; border-radius:999px; padding:10px 16px; cursor:pointer; font-weight:700; }
.gc-btn.secondary{ background:#fff; color:#111827; }
/* Big power lines (20% smaller than miles) */
.gc-metric{ font-weight:800; font-size:1.76rem; line-height:1.15; margin-top:8px; }
</style>
</head>
<body>
<div id="gc-range-widget">
<!-- STEP 1 -->
<div class="gc-card" id="gc-step1">
<h3><span class="gc-stepnum">1</span> Choose your battery (photo, voltage & capacity)</h3>
<div class="muted" style="margin-bottom:8px">Pick your battery below.</div>
<div class="gc-grid" id="gc-battery-list"></div>
</div>
<!-- STEP 2 -->
<div class="gc-card" id="gc-step2">
<h3><span class="gc-stepnum">2</span> Golf Cart Size</h3>
<fieldset aria-labelledby="gc-step2" class="gc-flex">
<label class="cap-opt"><input type="radio" name="gc-capacity" value="2"> <span><strong>2 Seater</strong></span></label>
<label class="cap-opt"><input type="radio" name="gc-capacity" value="4"> <span><strong>4 Seater</strong></span></label>
<label class="cap-opt"><input type="radio" name="gc-capacity" value="6"> <span><strong>6 Seater</strong></span></label>
</fieldset>
</div>
<!-- STEP 3 -->
<div class="gc-card" id="gc-step3">
<h3><span class="gc-stepnum">3</span> Total passengers riding (including driver)</h3>
<select id="gc-passengers" aria-label="Total passengers"></select>
<div class="muted" id="gc-passenger-hint">Choose capacity in Step 2 to lock max passengers.</div>
</div>
<!-- STEP 4 -->
<div class="gc-card" id="gc-step4">
<h3><span class="gc-stepnum">4</span> Check all that apply</h3>
<div class="gc-grid modifiers">
<label class="cap-opt"><input type="checkbox" id="gc-windshield" data-factor="0.95"> <span><strong>Windshield up</strong><br><span class="muted">Slight aerodynamic penalty</span></span></label>
<label class="cap-opt"><input type="checkbox" id="gc-windy" data-factor="0.90"> <span><strong>Windy conditions</strong><br><span class="muted">Headwinds reduce efficiency</span></span></label>
<label class="cap-opt"><input type="checkbox" id="gc-hilly" data-factor="0.80"> <span><strong>Hilly terrain</strong><br><span class="muted">Climbing consumes more energy</span></span></label>
<label class="cap-opt"><input type="checkbox" id="gc-modified" data-factor="0.85"> <span><strong>Modified motor and/or controller</strong><br><span class="muted">Higher performance often lowers range</span></span></label>
</div>
</div>
<!-- RESULT -->
<div class="gc-card">
<div class="gc-result" role="status" aria-live="polite">
<div class="muted" style="margin-bottom:6px;">Estimated range on a full charge</div>
<div class="gc-miles" id="gc-miles">β</div>
<!-- Big power lines (20% smaller than miles) -->
<div class="gc-metric" id="gc-continuous"> </div>
<div class="gc-metric" id="gc-instant"> </div>
<!-- Smaller breakdown -->
<div class="muted" id="gc-breakdown" style="margin-top:6px;">Make selections to see your result.</div>
<div class="gc-actions">
<button class="gc-btn" id="gc-calc" type="button">Calculate</button>
<button class="gc-btn secondary" id="gc-reset" type="button">Reset</button>
</div>
<div class="muted" style="margin-top:8px;">
Estimates only. Real-world range varies with speed, payload, tires, temperature, maintenance, and route.
</div>
</div>
</div>
</div>
<script>
(function(){
// ---------- CONFIG ----------
// Set your real A (Amps) specs; kW are not calculated or shown.
const CONFIG = {
capacityMultiplier: { 2: 1.00, 4: 0.92, 6: 0.88 },
perPassengerPenalty: 0.05,
minMilesFloor: 1,
roundTo: 1,
batteries: [
{ id:"modelA", name:"36V 105Ah",
imageUrl:"https://cdn.shopify.com/s/files/1/0251/0484/2798/files/reduceImage_20250625235400_2.png?v=1755737490",
voltage:36, capacityAh:105, baseRange:35,
maxContinuousA:200, // TODO: replace with official spec
maxInstantaneousA:700 // TODO: replace with official spec
},
{ id:"modelB", name:"48V 105Ah MINI",
imageUrl:"https://cdn.shopify.com/s/files/1/0251/0484/2798/files/redImage_20250625235425_2.png?v=1755737489",
voltage:48, capacityAh:105, baseRange:50,
maxContinuousA:200, // TODO
maxInstantaneousA:700 // TODO
},
{ id:"modelC", name:"48V 105Ah",
imageUrl:"https://cdn.shopify.com/s/files/1/0251/0484/2798/files/Image_20241121224213_small_9bbd4a12-2ac2-4f25-bf61-4659d34d4c92_3.png?v=1755737490",
voltage:48, capacityAh:105, baseRange:50,
maxContinuousA:200, // TODO
maxInstantaneousA:700 // TODO
},
{ id:"modelD", name:"48V 160Ah",
imageUrl:"https://cdn.shopify.com/s/files/1/0251/0484/2798/files/untitled.82_1518fdc3-3507-4f38-a3d9-4f5b5a62c6cb.png?v=1755737490",
voltage:48, capacityAh:160, baseRange:75,
maxContinuousA:200, // TODO
maxInstantaneousA:700 // TODO
},
{ id:"modelE", name:"72V 105Ah",
imageUrl:"https://cdn.shopify.com/s/files/1/0251/0484/2798/files/12412412343124_2.png?v=1755737490",
voltage:72, capacityAh:105, baseRange:80,
maxContinuousA:250, // TODO
maxInstantaneousA:1000 // TODO
}
]
};
// ---------- Utilities ----------
const $ = (sel, root=document) => root.querySelector(sel);
const $$ = (sel, root=document) => Array.from(root.querySelectorAll(sel));
const roundTo = (n, step) => { const s=Math.max(step,1); return Math.round(n/s)*s; };
// Passenger select
function fillPassengerOptions(max=6){
const sel = $('#gc-passengers'); sel.innerHTML = '';
for(let i=1;i<=max;i++){
const opt = document.createElement('option');
opt.value = i; opt.textContent = i + (i===1 ? ' passenger' : ' passengers');
sel.appendChild(opt);
}
}
fillPassengerOptions(6);
// Render batteries
function renderBatteryCards(){
const holder = $('#gc-battery-list'); holder.innerHTML = '';
CONFIG.batteries.forEach((batt, idx)=>{
const card = document.createElement('label');
card.className = 'gc-batt-card';
card.setAttribute('data-id', batt.id);
const radioId = 'gc-batt-' + batt.id;
card.innerHTML = `
<div class="gc-batt-media">
<img src="${batt.imageUrl}" alt="${batt.name}">
</div>
<div class="gc-batt-body">
<div class="gc-batt-head">
<input type="radio" name="gc-battery" id="${radioId}" value="${batt.id}" ${idx===0 ? 'checked' : ''}>
<div class="gc-batt-name">${batt.name}</div>
</div>
<div class="muted">${batt.voltage}V β’ ${batt.capacityAh}Ah (base ${batt.baseRange} mi)</div>
</div>
`;
holder.appendChild(card);
});
// Recalculate + highlight on battery change
$$('input[name="gc-battery"]').forEach(r=>{
r.addEventListener('change', ()=>{
updateHighlights();
render();
});
});
}
renderBatteryCards();
// Capacity changes clamp passenger select
$$('#gc-step2 input[name="gc-capacity"]').forEach(r=>{
r.addEventListener('change', ()=>{
const cap = parseInt(r.value,10);
fillPassengerOptions(cap);
$('#gc-passenger-hint').textContent = `Max set to ${cap} based on your cart capacity.`;
updateHighlights();
render();
});
});
// Step 4 (checkboxes) highlight on change
$$('#gc-step4 input[type="checkbox"]').forEach(cb=>{
cb.addEventListener('change', ()=>{
updateHighlights();
render();
});
});
// Step 3 select highlight on change
$('#gc-passengers').addEventListener('change', ()=>{
updateHighlights();
render();
});
function getSelectedBattery(){
const selectedRadio = $('input[name="gc-battery"]:checked');
if(!selectedRadio) return null;
return CONFIG.batteries.find(b=> b.id === selectedRadio.value) || null;
}
function getSelectedCapacity(){ const el = $('input[name="gc-capacity"]:checked'); return el ? parseInt(el.value,10) : null; }
function getPassengers(){ return parseInt($('#gc-passengers').value, 10) || 1; }
function getModifiers(){
return $$('#gc-step4 input[type="checkbox"]:checked').map(cb => ({
id: cb.id,
factor: parseFloat(cb.dataset.factor || '1'),
label: cb.closest('label')?.innerText.split('\n')[0].trim() || cb.id
}));
}
function calculate(){
const battery = getSelectedBattery();
const capacity = getSelectedCapacity();
const passengers = getPassengers();
const modifiers = getModifiers();
if(!battery){ return { ok:false, message:'Select a battery in Step 1.' }; }
if(!capacity){ return { ok:false, message:'Select your cart size in Step 2.' }; }
const p = Math.min(Math.max(1, passengers), capacity);
let miles = battery.baseRange;
miles *= (CONFIG.capacityMultiplier[capacity] ?? 1);
const extra = Math.max(0, p - 1);
const passengerFactor = Math.max(0, 1 - (CONFIG.perPassengerPenalty * extra));
miles *= passengerFactor;
const modsText = [];
modifiers.forEach(m=>{ miles *= m.factor; modsText.push(m.label); });
miles = Math.max(CONFIG.minMilesFloor, miles);
miles = roundTo(miles, CONFIG.roundTo);
const breakdown = [
`<strong>Battery:</strong> ${battery.name} β ${battery.voltage}V / ${battery.capacityAh}Ah (base ${battery.baseRange} mi)`,
`<strong>Capacity:</strong> ${capacity}-seat (${Math.round((CONFIG.capacityMultiplier[capacity] ?? 1)*100)}% baseline)`,
`<strong>Riders:</strong> ${p} (${Math.round((1 - (CONFIG.perPassengerPenalty * Math.max(0,p-1)))*100)}% of baseline)`,
`<strong>Conditions:</strong> ${modsText.length ? modsText.join(', ') : 'none'}`
].filter(Boolean).join(' Β· ');
// Prepare big power lines (Amps only, no kW)
const contA = battery.maxContinuousA;
const instA = battery.maxInstantaneousA;
return { ok:true, miles, breakdown, contA, instA };
}
function updateHighlights(){
// Step 1 (battery cards)
$$('#gc-battery-list .gc-batt-card').forEach(card=>{
const input = card.querySelector('input[name="gc-battery"]');
card.classList.toggle('is-selected', !!(input && input.checked));
});
// Step 2 (capacity radios)
$$('#gc-step2 label.cap-opt').forEach(lbl=>{
const inp = lbl.querySelector('input[type="radio"]');
lbl.classList.toggle('is-selected', !!(inp && inp.checked));
});
// Step 3 (passenger select)
$('#gc-passengers').classList.add('is-selected');
// Step 4 (checkboxes)
$$('#gc-step4 label.cap-opt').forEach(lbl=>{
const cb = lbl.querySelector('input[type="checkbox"]');
lbl.classList.toggle('is-selected', !!(cb && cb.checked));
});
}
function render(){
const res = calculate();
const milesEl = $('#gc-miles');
const breakdownEl = $('#gc-breakdown');
const contEl = $('#gc-continuous');
const instEl = $('#gc-instant');
if(!res.ok){
milesEl.textContent = 'β';
breakdownEl.innerHTML = res.message || '';
contEl.textContent = '';
instEl.textContent = '';
return;
}
milesEl.textContent = res.miles + ' miles';
breakdownEl.innerHTML = res.breakdown;
// Show Amps (no kW) in big text (20% smaller than miles)
contEl.textContent = res.contA ? `Max Continuous: ${Math.round(res.contA)} A` : '';
instEl.textContent = res.instA ? `Max Instantaneous: ${Math.round(res.instA)} A` : '';
}
// Buttons
$('#gc-calc').addEventListener('click', ()=>{ updateHighlights(); render(); });
$('#gc-reset').addEventListener('click', ()=>{
$$('input[name="gc-battery"]').forEach((i,idx)=> i.checked = idx===0);
$$('input[name="gc-capacity"]').forEach(i=> i.checked=false);
$$('#gc-step4 input[type="checkbox"]').forEach(i=> i.checked=false);
fillPassengerOptions(6);
$('#gc-passenger-hint').textContent = 'Choose capacity in Step 2 to lock max passengers.';
$('#gc-miles').textContent = 'β';
$('#gc-continuous').textContent = '';
$('#gc-instant').textContent = '';
$('#gc-breakdown').textContent = 'Make selections to see your result.';
updateHighlights();
});
// Initial render + highlight
updateHighlights();
render();
// Live update
['change','input'].forEach(evt=>{
document.getElementById('gc-range-widget').addEventListener(evt, (e)=>{
if(e.target && (e.target.matches('input, select'))) {
updateHighlights();
render();
}
}, true);
});
// Accessibility: Enter triggers calculate
document.getElementById('gc-range-widget').addEventListener('keydown', (e)=>{
if(e.key === 'Enter') { updateHighlights(); render(); }
});
})();
</script>
</body>
</html>
Back to all posts
Battery Distance Calculator
Explore more articles
Discover more insights and resources from our blog.