Skip to Content

Brining Calculator

By
Disclosure: This post may contain affiliate links from which I earn commissions at no extra cost to you. As an Amazon Associate, I earn from qualifying purchases. Full policy.

When to use wet vs dry, how equilibrium brining works, and the right salt-by-weight targets for juicy results—plus a calculator for exact grams.

Brining is easier when you do the math once. Use this Brining Calculator to set a target salt percentage, enter meat (and water, if wet), and get exact salt by weight—plus a realistic minimum brining time based on thickness and shape. Prefer grams for accuracy; volume equivalents (Diamond Crystal, Morton, or a custom density) are available for quick estimates, and can be shown as a range.

Tip: If your timing is uncertain, choose Equilibrium (self-limiting) so the meat won’t creep past your target salinity. Note: Most cooks land between 1.2–1.8%. Start at 1.5%.

Calculate Brine

Brine Calculator

Get exact salt by weight for wet, dry, or equilibrium brines, and a realistic minimum cure time.

Tip: weigh salt for accuracy. Volume equals are estimates.




Units


1.5%
Recommended range 1.2–1.8%. Use 0 water for dry EQ.
Enter your meat weight and water amount. (Use 0 water for dry brine or dry equilibrium.)
FOR CURED MEATS

For bacon, ham, pastrami, etc.

1.0%

Safety: keep brining ≤ 40°F (4°C). Discard used brine. Learn more about wet vs dry vs equilibrium brines.

Additional Resources:

Disclaimer: This calculator is provided for informational and educational purposes only. Results are estimates based on general brining principles and should not be considered professional food safety advice. Always follow local health department regulations and USDA guidelines for food safety. Use accurate measurements, proper food handling practices, and maintain appropriate temperatures. The user assumes all risk associated with food preparation. No warranty or guarantee is provided regarding the accuracy of calculations or safety of results. When in doubt, consult a food safety professional.

Copied!

// Density const DENSITY_PRESETS={dc:{label:'Diamond Crystal',range:[130,150]},morton:{label:'Morton Kosher',range:[235,250]}}; const DState={preset:'dc',showRange:true,customGPerCup:135};

// DOM const $=id=>document.getElementById(id); const app=$('brine-app'), themeToggle=$('theme-toggle'); const tabs={eq:$('tab-eq'),wet:$('tab-wet'),dry:$('tab-dry'),time:$('tab-time')}; const panels={salt:$('panel-salt'),time:$('panel-time')}; const unitBtns={m:$('u-metric'), i:$('u-imperial')}; const target=$('target'), tVal=$('target-val'), lblTarget=$('lbl-target'), tHelp=$('target-help'); const meatLb=$('meat-lb'), meatOz=$('meat-oz'), meatG=$('meat-g'), meatImp=$('meat-imp'), meatMet=$('meat-met'), lblMeat=$('lbl-meat'); const waterVol=$('water-vol'), waterUnit=$('water-unit'), waterGEl=$('water-g'), waterImp=$('water-imp'), waterMet=$('water-met'), lblWater=$('lbl-water'); const copyBtn=$('copy'), toast=$('toast'), readyHint=$('ready-hint'); const thick=$('thick'), lblThick=$('lbl-thick'), shape=$('shape'), copyTime=$('copy-time'); const tablist=$('tablist'); const saltResults=$('salt-results'); const timeBrineBtn=$('time-brine'), timeCureBtn=$('time-cure'), shapeHelper=$('shape-helper'); const timeResultTitle=$('time-result-title'), timeResultHelper=$('time-result-helper'), thicknessWarning=$('thickness-warning'); const densControls=$('density-controls'), densNote=$('dens-note'), densCustom=$('dens-custom'), densRange=$('dens-range'); const modeWarn=$('mode-warn'), volumes=$('volumes'), vdc=$('v-dc'), vmo=$('v-mo'), vcu=$('v-cu'), vcuWrap=$('v-cu-wrap'); const presetChipsContainer=$('preset-chips'); const useCure=$('use-cure'), sugarWrap=$('sugar-wrap'), targetSugar=$('target-sugar'), sugarVal=$('sugar-val'); const printBtn=$('print-btn'); const cureInfo=$('cure-info'), curePpmInfo=$('cure-ppm-info'), cureWarning=$('cure-warning'); const cureModeToggle=$('cure-mode-toggle'), cureHelpTrigger=$('cure-help-trigger'), cureModeExplanation=$('cure-mode-explanation'); const summary={ method:$('summary-method'), total:$('summary-total'), meat:$('summary-meat'), water:$('summary-water'), saltPct:$('summary-salt-pct'), salt:$('summary-salt'), sugarWrap:$('summary-sugar-wrap'), sugarPct:$('summary-sugar-pct'), sugar:$('summary-sugar'), cureWrap:$('summary-cure-wrap'), curePct:$('summary-cure-pct'), cure:$('summary-cure') }; const showMathSaltBtn=$('show-math-salt'), mathSaltContent=$('math-salt-content'), mathSaltFormula=$('math-salt-formula'); const showMathTimeBtn=$('show-math-time'), mathTimeContent=$('math-time-content'), mathTimeFormula=$('math-time-formula'); const timeExamplesList=$('time-examples-list');

// Update cure label dynamically function updateCureLabel(){ const label=document.querySelector('label[for="use-cure"]'); const header=$('cure-context-header'); const help=$('cure-context-help'); if(!label || !header || !help)return;

if(S.cureTargetMode==='ppm'){ // PPM mode: show calculated percentage with FSIS ingoing basis const calcPct=(S.targetPPM/PPM_PER_PERCENT).toFixed(3); let basis=''; if(S.mode==='dry' || S.productType==='bacon-dry'){ basis='of meat (PPM ingoing basis)'; }else{ const pickupClamped=Math.min(25,Math.max(5,Number(S.pickupPct)||10)); basis=`of meat + ${pickupClamped}% pickup (PPM ingoing basis)`; } label.textContent=`Add Curing Salt (~${calcPct}% ${basis} for ${S.targetPPM} ppm)`; }else{ // Legacy percent mode (convenience basis) const pct=(S.cureLevel==='bacon')?CURE_PCT_BACON:CURE_SALT_PERCENT; let basis=(S.mode==='dry')?'of meat':'of meat + water'; label.textContent=`Add Curing Salt (${pct}% ${basis})`; }

// Context-aware header and help text if(S.mode==='eq'){ header.textContent='FOR CURED MEATS'; header.style.color=''; help.textContent='For bacon, ham, pastrami, etc.'; help.style.color=''; }else{ header.innerHTML='⚠️ FOR LONG-TERM CURING ONLY'; header.style.color='#c2410c'; help.innerHTML='Not needed for flavor brining (turkey, chicken, pork chops, etc.)'; help.style.color='#d97706'; } }

// Manage inline help visibility for cure modes function syncCureHelpVisibility({forceOpen=false}={}){ if(!cureModeExplanation || !cureHelpTrigger)return;

if(!S.useCure){ cureModeExplanation.classList.add('hidden'); cureModeExplanation.setAttribute('aria-hidden','true'); cureHelpTrigger.setAttribute('aria-expanded','false'); cureHelpTrigger.disabled=true; cureHelpOverride=null; return; }

cureHelpTrigger.disabled=false;

let shouldOpen; if(forceOpen){ shouldOpen=true; cureHelpOverride=true; }else if(cureHelpOverride!==null){ shouldOpen=cureHelpOverride; }else{ shouldOpen=true; }

cureModeExplanation.classList.toggle('hidden',!shouldOpen); cureModeExplanation.setAttribute('aria-hidden',shouldOpen?'false':'true'); cureHelpTrigger.setAttribute('aria-expanded',shouldOpen?'true':'false'); }

// Update cure info dynamically function updateCureInfo(){ syncCureHelpVisibility();

if(!S.useCure){ cureInfo.classList.add('hidden'); cureWarning.classList.add('hidden'); $('accelerator-notice').classList.add('hidden'); return; }

cureInfo.classList.remove('hidden');

// Show PPM info with calculated percentage if(S.cureTargetMode==='ppm'){ const calcPct=(S.targetPPM/PPM_PER_PERCENT).toFixed(3); curePpmInfo.textContent=`Targeting ${S.targetPPM} ppm nitrite (${calcPct}% Cure #1 on ingoing basis).`; }else{ // Legacy percent mode if(S.cureLevel==='bacon'){ curePpmInfo.textContent='Using bacon-specific rate targeting 120 ppm nitrite (convenience basis).'; }else{ curePpmInfo.textContent='Using standard rate targeting 156 ppm nitrite (convenience basis).'; } }

// Validate and show warnings const validationMsg=validateCurePPM(); if(validationMsg){ cureWarning.textContent=validationMsg; cureWarning.classList.remove('hidden'); }else{ cureWarning.classList.add('hidden'); }

// Show accelerator notice for pumped bacon if(S.productType==='bacon-pumped' && S.useCure){ $('accelerator-notice').classList.remove('hidden'); }else{ $('accelerator-notice').classList.add('hidden'); } }

// Helpers function fmtImperial(g){ const totalOzRaw=g/G.GRAMS_PER_OZ; let totalOz=Math.round(totalOzRaw*10)/10; let lb=Math.floor(totalOz/16); let oz=Math.round((totalOz-lb*16)*10)/10; if(oz>=16-1e-9){lb+=1;oz=0.0;} return lb>0?`${lb} lb ${oz.toFixed(1)} oz`:`${oz.toFixed(1)} oz`; } function fmtMetric(g){return g>=1000?`${(g/1000).toFixed(2)} kg`:`${g.toFixed(0)} g`;} function hoursToDH(h){ const d=Math.floor(h/24); let r=Math.round(h - d*24); if(r===24){ r=0; } const days=d?`${d} Day${d>1?'s':''}`:''; const hours=r?`${r} Hour${r>1?'s':''}`:''; return days&&hours?`${days}, ${hours}`:(days||hours||'0 Hours'); } function showToast(msg){toast.textContent=msg;toast.classList.add('show');setTimeout(()=>toast.classList.remove('show'),1400);} async function copy(text){ try{ if(navigator.clipboard&&isSecureContext){ await navigator.clipboard.writeText(text); return true; } }catch(e){} const ta=document.createElement('textarea'); ta.value=text; ta.style.position='fixed'; ta.style.left='-9999px'; ta.style.top='0'; ta.setAttribute('aria-hidden','true'); document.body.appendChild(ta); ta.select(); let ok=false; try{ ok=document.execCommand('copy'); }catch(e){} document.body.removeChild(ta); return ok; } function track(evt,data={}){ try{ if(window.dataLayer)window.dataLayer.push({event:evt,...data}); }catch(e){} }

// Density helpers function volumesFromGrams(grams,gPerCup){const cups=grams/gPerCup;const tbsp=cups*16;const tsp=tbsp*3;return {cups,tbsp,tsp};} function fmtRange(a,b,unit,prec=2){const same=Math.abs(a-b)<1e-6;const A=a.toFixed(prec),B=b.toFixed(prec);return same?`${A} ${unit}`:`${A}–${B} ${unit}`;} function volString(grams,density,showRange){ if(showRange&&Array.isArray(density)){ const[lo,hi]=density;const vLo=volumesFromGrams(grams,hi);const vHi=volumesFromGrams(grams,lo); return `Cups: ${fmtRange(vLo.cups,vHi.cups,'cups',2)}; Tbsp: ${fmtRange(vLo.tbsp,vHi.tbsp,'Tbsp',1)}; tsp: ${fmtRange(vLo.tsp,vHi.tsp,'tsp',0)}`; }else{ const gpc=Array.isArray(density)?(density[0]+density[1])/2:density;const v=volumesFromGrams(grams,gpc); return `Cups: ${v.cups.toFixed(2)}; Tbsp: ${v.tbsp.toFixed(1)}; tsp: ${v.tsp.toFixed(0)}`; } } function updateDensNote(){ let base='Approximate volumes only. '; if(DState.preset==='dc') base+='Calculated using Diamond Crystal at ~130–150 g per cup. '; else if(DState.preset==='morton') base+='Calculated using Morton Kosher at ~235–250 g per cup. '; else base+=`Calculated using ${DState.customGPerCup} g per cup. `; densNote.innerHTML=base+'Densities vary with humidity and packing. For accuracy, weigh salt.'; } function renderVolumeBoxes(grams){ volumes.classList.remove('hidden');densControls.classList.remove('hidden'); vdc.innerHTML=volString(grams,DENSITY_PRESETS.dc.range,DState.showRange); vmo.innerHTML=volString(grams,DENSITY_PRESETS.morton.range,DState.showRange); if(DState.preset==='custom'){ vcuWrap.classList.remove('hidden'); const gpc=Math.max(80,Math.min(350,Number(DState.customGPerCup)||135)); vcu.innerHTML=volString(grams,gpc,false); }else{vcuWrap.classList.add('hidden');} updateDensNote(); } // Core math function calc(){ // Guard against invalid inputs if(S.meatG <= 0) { return { total:0, tableSalt:0, cureSalt:0, accelerator:0, sugar:0, warn:'Enter meat weight to calculate.', cureRate:0, ingoingBasisG:0 }; } let total=0, tableSalt=0, cureSalt=0, accelerator=0, sugar=0, warn=null; let ingoingBasisG=0; // FSIS "ingoing" basis for cure calculations let cureRate=0; // % of cure salt // Auto-set accelerator for pumped bacon (mandatory per 9 CFR 424.22(b)(1)) if(S.productType==='bacon-pumped' && S.useCure){ S.useAccelerator=true; }else{ S.useAccelerator=false; } // Calculate cure rate and ingoing basis if(S.cureTargetMode==='percent'){ // Legacy percent mode: convenience basis (meat+water or meat) cureRate=(S.cureLevel==='bacon')?CURE_PCT_BACON:CURE_SALT_PERCENT; }else{ // PPM mode: FSIS "ingoing" basis // Clamp pickup to valid range const pickupClamped=Math.min(25,Math.max(5,Number(S.pickupPct)||10)); if(S.productType==='bacon-dry' || S.mode==='dry'){ ingoingBasisG=S.meatG; // dry cure = meat only }else{ // pumped/massaged/immersion: meat + pickup const pickup=pickupClamped/100; ingoingBasisG=S.meatG*(1+pickup); } // Formula: 1% Cure #1 = 625 ppm nitrite (Cure #1 is 6.25% NaNO₂) // Keep full precision for calculations; round only for display cureRate=S.targetPPM/PPM_PER_PERCENT; } // Calculate weights by brining mode if(S.mode==='eq'){ total=S.meatG+S.waterG; tableSalt=total*S.target/100; sugar=total*S.targetSugar/100; if(S.cureTargetMode==='ppm'){ // PPM mode: use ingoing basis cureSalt=S.useCure? ingoingBasisG*(cureRate/100) : 0; accelerator=S.useAccelerator? ingoingBasisG*(ACCELERATOR_PPM/1000000) : 0; }else{ // Percent mode: use total (legacy convenience) cureSalt=S.useCure? total*(cureRate/100) : 0; accelerator=S.useAccelerator? total*(ACCELERATOR_PPM/1000000) : 0; } } if(S.mode==='wet'){ total=S.meatG+S.waterG; if(S.waterG<=0) warn='Wet brine needs water. Add water or switch to Dry/Equilibrium.'; tableSalt=S.waterG*S.target/100; sugar=S.waterG*S.targetSugar/100; if(S.cureTargetMode==='ppm'){ cureSalt=S.useCure? ingoingBasisG*(cureRate/100) : 0; accelerator=S.useAccelerator? ingoingBasisG*(ACCELERATOR_PPM/1000000) : 0; }else{ cureSalt=S.useCure? total*(cureRate/100) : 0; accelerator=S.useAccelerator? total*(ACCELERATOR_PPM/1000000) : 0; } } if(S.mode==='dry'){ total=S.meatG; tableSalt=S.meatG*S.target/100; sugar=S.meatG*S.targetSugar/100; // Dry always uses meat basis regardless of mode cureSalt=S.useCure? S.meatG*(cureRate/100) : 0; accelerator=S.useAccelerator? S.meatG*(ACCELERATOR_PPM/1000000) : 0; } return { total, tableSalt, cureSalt, accelerator, sugar, warn, cureRate, ingoingBasisG }; } function validateCurePPM(){ if(!S.useCure || S.cureTargetMode!=='ppm') return null; const limits=CURE_LIMITS[S.productType]; const ppm=S.targetPPM; // Hard limits (blocking errors) if(ppm>limits.max){ if(S.productType==='comminuted'){ return `ERROR: Exceeds ${limits.max} ppm limit for comminuted products per 9 CFR 424.21(c). Must reduce target.`; } if(S.productType==='bacon-pumped'){ return `ERROR: Pumped bacon is fixed at ${limits.max} ppm per 9 CFR 424.22(b)(1). Cannot exceed.`; } return `ERROR: Exceeds ${limits.max} ppm limit for ${S.productType} per 9 CFR 424.21(c). Must reduce target.`; }

// Warnings (advisory) if(ppm>=limits.warn && ppm<=limits.max){ return `Warning: ${ppm} ppm approaches upper limit for ${S.productType}. Consider reducing slightly.`; } if(ppm0,w=S.waterG>0;if(S.mode==='eq')return m;if(S.mode==='wet')return m&&w;if(S.mode==='dry')return m;return false;}

// URL / storage function stateToParams(){ const p=new URLSearchParams(); p.set('mode',S.mode); p.set('unit',S.unit); p.set('target',S.target.toFixed(2)); if(S.meatG>0)p.set('meatG',Math.round(S.meatG)); if(S.waterG>0)p.set('waterG',Math.round(S.waterG)); p.set('thickIn',S.thickIn.toFixed(2)); p.set('shape',S.shape); p.set('theme',S.theme); p.set('dens',DState.preset); p.set('densRange',DState.showRange?'1':'0'); p.set('densCustom',DState.customGPerCup); if(S.selectedPreset)p.set('preset',S.selectedPreset); p.set('useCure', S.useCure ? '1' : '0'); p.set('sugar', S.targetSugar.toFixed(2)); p.set('timeMode', S.timeMode); p.set('cureTargetMode', S.cureTargetMode); p.set('targetPPM', S.targetPPM); p.set('productType', S.productType); p.set('pickupPct', S.pickupPct); history.replaceState(null,'','?'+p.toString()); try{ localStorage.setItem('brine_mode',S.mode); localStorage.setItem('brine_unit',S.unit); localStorage.setItem('brine_theme',S.theme); }catch(e){} } function paramsToState(){ const sp=new URLSearchParams(location.search); let lsMode=null, lsUnit=null, lsTheme=null; try{ lsMode=localStorage.getItem('brine_mode'); lsUnit=localStorage.getItem('brine_unit'); lsTheme=localStorage.getItem('brine_theme'); }catch(e){}

const urlMode=sp.get('mode'); if(urlMode&&VALID_MODES.includes(urlMode)) S.mode=urlMode; else if(lsMode&&VALID_MODES.includes(lsMode)) S.mode=lsMode;

const urlUnit=sp.get('unit'); if(urlUnit&&VALID_UNITS.includes(urlUnit)) S.unit=urlUnit; else if(lsUnit&&VALID_UNITS.includes(lsUnit)) S.unit=lsUnit;

const urlTheme=sp.get('theme'); if(urlTheme&&VALID_THEMES.includes(urlTheme)) S.theme=urlTheme; else if(lsTheme&&VALID_THEMES.includes(lsTheme)) S.theme=lsTheme; else S.theme='auto';

if(sp.has('target'))S.target=parseFloat(sp.get('target'))||S.target; if(sp.has('meatG'))S.meatG=parseFloat(sp.get('meatG'))||S.meatG; if(sp.has('waterG'))S.waterG=parseFloat(sp.get('waterG'))||S.waterG; if(sp.has('thickIn'))S.thickIn=parseFloat(sp.get('thickIn'))||S.thickIn;

const urlShape=sp.get('shape'); if(urlShape&&VALID_SHAPES.includes(urlShape)) S.shape=urlShape;

const urlDens=sp.get('dens'); if(urlDens&&VALID_DENS.includes(urlDens)) DState.preset=urlDens;

if(sp.has('densRange'))DState.showRange=sp.get('densRange')==='1'; if(sp.has('densCustom'))DState.customGPerCup=parseInt(sp.get('densCustom'))||135;

const urlPreset=sp.get('preset'); if(urlPreset&&PRESETS[urlPreset]) S.selectedPreset=urlPreset; if(sp.has('useCure')) S.useCure = sp.get('useCure')==='1'; if(sp.has('sugar')) S.targetSugar = parseFloat(sp.get('sugar')) || 0;

const urlTimeMode=sp.get('timeMode'); if(urlTimeMode&&VALID_TIME_MODES.includes(urlTimeMode)) S.timeMode=urlTimeMode;

const urlCureMode=sp.get('cureTargetMode'); if(urlCureMode&&VALID_CURE_MODES.includes(urlCureMode)){ S.cureTargetMode=urlCureMode; }

if(sp.has('targetPPM')){ S.targetPPM=Math.min(250,Math.max(80,parseInt(sp.get('targetPPM'))||156)); }

const urlProductType=sp.get('productType'); if(urlProductType&&VALID_PRODUCT_TYPES.includes(urlProductType)){ S.productType=urlProductType; }

if(sp.has('pickupPct')){ S.pickupPct=Math.min(25,Math.max(5,parseInt(sp.get('pickupPct'))||10)); }

document.querySelectorAll('input[name="dens"]').forEach(rb=>{ rb.checked=(rb.value===DState.preset); }); }

// Theme function applyTheme(){ app.classList.remove('theme-dark','theme-light'); let icon='A',label='Theme: Auto'; if(S.theme==='dark'){app.classList.add('theme-dark');icon='D';label='Theme: Dark';} if(S.theme==='light'){app.classList.add('theme-light');icon='L';label='Theme: Light';} themeToggle.setAttribute('data-icon',icon); themeToggle.setAttribute('aria-label',label); themeToggle.title=label; } function cycleTheme(){ S.theme=(S.theme==='auto')?'dark':(S.theme==='dark')?'light':'auto'; applyTheme(); stateToParams(); track('brine_theme',{theme:S.theme}); }

// Render / UI function setHelpHTML(html){ tHelp.innerHTML=html; } function calculateModeRanges(mode){ // --- START NEW LOGIC --- // Add a hard-coded, wider range for 'wet' mode for better usability. if (mode === 'wet') { return {min: 2.0, max: 8.0, recommendedMin: 4.0, recommendedMax: 6.0}; } // --- END NEW LOGIC ---

const presetsForMode=Object.values(PRESETS).filter(p=>p.mode===mode); if(presetsForMode.length===0){ const fallbacks={eq:[1.0,2.0],wet:[5.0,10.0],dry:[1.0,2.5]}; const [min,max]=fallbacks[mode]||[1.0,2.0]; return {min,max,recommendedMin:min,recommendedMax:max}; } const targets=presetsForMode.map(p=>p.target); const presetMin=Math.min(...targets); const presetMax=Math.max(...targets); const padding=0.5; const min=Math.max(0.5,presetMin-padding); const max=presetMax+padding; return {min,max,recommendedMin:presetMin,recommendedMax:presetMax}; } function renderPresetChips(){ if(!presetChipsContainer)return; presetChipsContainer.innerHTML=''; if(S.mode==='time'){presetChipsContainer.style.display='none';return;} presetChipsContainer.style.display='flex'; const presetsForMode=Object.entries(PRESETS).filter(([k,v])=>v.mode===S.mode); presetsForMode.forEach(([key,preset])=>{ const chip=document.createElement('button'); chip.type='button'; chip.className='preset-chip'; chip.textContent=preset.label; chip.setAttribute('data-preset-key',key); chip.setAttribute('aria-label',`${S.selectedPreset===key?'Deselect':'Select'} ${preset.label} preset`); if(S.selectedPreset===key)chip.classList.add('active'); chip.setAttribute('aria-pressed', S.selectedPreset===key ? 'true' : 'false'); chip.addEventListener('click',()=>{ if(S.selectedPreset===key){ S.selectedPreset=null; S.cureLevel='default'; updateCureLabel(); updateCureInfo(); renderPresetChips(); stateToParams(); track('brine_preset_clear',{preset:key,mode:S.mode}); return; } S.selectedPreset=key;

// Set product type if specified if(preset.productType) S.productType=preset.productType;

// Set PPM/percent values based on current mode if(S.cureTargetMode==='ppm' && preset.targetPPM){ S.targetPPM=preset.targetPPM; const ppmPresetEl=$('ppm-preset'); if(ppmPresetEl){ // Try to match preset value, otherwise set to custom if(['120','156','200'].includes(String(preset.targetPPM))){ ppmPresetEl.value=String(preset.targetPPM); }else{ ppmPresetEl.value='custom'; const ppmCustomEl=$('ppm-custom'); if(ppmCustomEl) ppmCustomEl.value=preset.targetPPM; } } }

if(S.mode!==preset.mode){ S.target=preset.target; S.targetSugar=preset.sugar; S.useCure=preset.useCure; S.cureLevel=preset.cureLevel||'default'; updateCureLabel(); updateCureInfo(); setMode(preset.mode); }else{ const rangeInfo=calculateModeRanges(S.mode); if(preset.targetparseFloat(target.max)){ target.min=String(rangeInfo.min); target.max=String(rangeInfo.max); } S.target=preset.target; S.targetSugar=preset.sugar; S.useCure=preset.useCure; S.cureLevel=preset.cureLevel||'default'; target.value=S.target; targetSugar.value=S.targetSugar; useCure.checked=S.useCure; updateCureLabel(); updateCureInfo(); render(); } renderPresetChips(); track('brine_preset',{preset:key,mode:S.mode}); }); presetChipsContainer.appendChild(chip); }); } function setMode(m){ const modeChanged=S.mode!==m; S.mode=m; Object.entries(tabs).forEach(([k,el])=>{const sel=(k===m); el.setAttribute('aria-selected',sel?'true':'false'); el.tabIndex=sel?0:-1;}); if(m==='time'){ panels.salt.classList.add('hidden'); panels.time.classList.remove('hidden'); panels.salt.setAttribute('aria-labelledby','tab-eq'); panels.time.setAttribute('aria-labelledby','tab-time'); render(); renderPresetChips(); track('brine_mode',{mode:S.mode}); return; } const rangeInfo=calculateModeRanges(m); target.min=String(rangeInfo.min); target.max=String(rangeInfo.max); target.step='0.1';

if(modeChanged && !S.selectedPreset && MODE_DEFAULTS[m]){ const d=MODE_DEFAULTS[m]; S.target=d.target; S.targetSugar=d.sugar; S.useCure=d.useCure; target.value=S.target; targetSugar.value=S.targetSugar; useCure.checked=S.useCure; }else{ if(S.targetrangeInfo.max){ S.target=Math.max(rangeInfo.min,Math.min(rangeInfo.max,S.target)); } target.valueAsNumber=S.target; }

panels.salt.classList.remove('hidden'); panels.time.classList.add('hidden'); panels.salt.setAttribute('aria-labelledby','tab-'+m); panels.time.setAttribute('aria-labelledby','tab-time');

if(m==='eq'){ lblTarget.textContent='Target salt (% of meat + water)'; const rangeStr=`${rangeInfo.recommendedMin.toFixed(1)}–${rangeInfo.recommendedMax.toFixed(1)}%`; setHelpHTML(`Recommended range ${rangeStr}. Use 0 water for dry EQ.`); } if(m==='wet'){ lblTarget.textContent='Target salt (% of water only)'; const rangeStr=`${rangeInfo.recommendedMin.toFixed(1)}–${rangeInfo.recommendedMax.toFixed(1)}%`; setHelpHTML(`Typical range ${rangeStr} of the water weight.`); } if(m==='dry'){ lblTarget.textContent='Target salt (% of meat only)'; const rangeStr=`${rangeInfo.recommendedMin.toFixed(1)}–${rangeInfo.recommendedMax.toFixed(1)}%`; setHelpHTML(`Typical range ${rangeStr} by meat weight.`); }

const showOptions=(m!=='time'); const showSugar=(m!=='time'); const cureBasic=$('cure-basic-controls'); const cureAdvanced=$('cure-advanced-controls'); if(cureBasic) cureBasic.classList.toggle('hidden',!showOptions); if(cureAdvanced) cureAdvanced.classList.toggle('hidden',!showOptions); sugarWrap.classList.toggle('hidden',!showSugar);

const sugarLabel=(m==='dry')?'Target Sugar (% of meat only)':'Target Sugar (% of meat + water)'; sugarWrap.querySelector('label').textContent=sugarLabel;

updateCureLabel(); updateCureInfo(); updateUnits(S.unit); render(); renderPresetChips(); track('brine_mode',{mode:S.mode}); } let lastUnit='metric'; function updateUnits(u){ S.unit=u; lastUnit=u; const imp=(u==='imperial'); unitBtns.i.setAttribute('aria-pressed',imp?'true':'false'); unitBtns.m.setAttribute('aria-pressed',imp?'false':'true'); if(imp){ lblThick.textContent='Max diameter or thickness (inches)'; thick.value=S.thickIn.toFixed(1); thick.step='0.1'; }else{ lblThick.textContent='Max diameter or thickness (centimeters)'; thick.value=(S.thickIn*CM_PER_IN).toFixed(1); thick.step='0.1'; } meatImp.classList.toggle('hidden',!imp); waterImp.classList.toggle('hidden',!imp); meatMet.classList.toggle('hidden',imp); waterMet.classList.toggle('hidden',imp); lblMeat.textContent=imp?'Meat weight (lb/oz)':'Meat weight (grams)'; lblWater.textContent=imp?'Water volume (cups/quarts/gallons)':'Water weight (grams)'; densControls.classList.toggle('hidden',!imp); volumes.classList.add('hidden'); render(); } function render(){ tVal.textContent=S.target.toFixed(1)+'%'; sugarVal.textContent=S.targetSugar.toFixed(2)+'%'; const needWater=(S.mode==='wet'); if(S.unit==='imperial'){ meatLb.setAttribute('aria-invalid',S.meatG>0?'false':'true'); meatG.setAttribute('aria-invalid','false'); waterVol.setAttribute('aria-invalid',needWater&&S.waterG<=0?'true':'false'); waterGEl.setAttribute('aria-invalid','false'); }else{ meatG.setAttribute('aria-invalid',S.meatG>0?'false':'true'); meatLb.setAttribute('aria-invalid','false'); meatOz.setAttribute('aria-invalid','false'); waterGEl.setAttribute('aria-invalid',needWater&&S.waterG<=0?'true':'false'); waterVol.setAttribute('aria-invalid','false'); } if(S.mode==='eq'&&S.waterG===0){ const rangeInfo=calculateModeRanges('eq'); const rangeStr=`${rangeInfo.recommendedMin.toFixed(1)}–${rangeInfo.recommendedMax.toFixed(1)}%`; setHelpHTML(`Recommended range ${rangeStr}. Dry equilibrium (no added water).`); } const ready=isReady(); readyHint.textContent='';

if(ready){ const { total, tableSalt, cureSalt, accelerator, sugar, warn, cureRate, ingoingBasisG } = calc();

summary.method.textContent=`${S.mode.charAt(0).toUpperCase()+S.mode.slice(1)} Brine`; summary.total.textContent=(S.unit==='imperial')?fmtImperial(total):fmtMetric(total); summary.meat.textContent=(S.unit==='imperial')?fmtImperial(S.meatG):fmtMetric(S.meatG); summary.water.textContent=(S.unit==='imperial')?fmtImperial(S.waterG):fmtMetric(S.waterG); summary.water.closest('div').style.display=(S.mode==='dry'||S.waterG===0)?'none':'block'; summary.total.closest('div').style.display=(S.mode==='dry')?'none':'block';

summary.saltPct.textContent=`${S.target.toFixed(1)}%`; summary.salt.textContent=(S.unit==='imperial')?`${fmtImperial(tableSalt)} (${tableSalt.toFixed(0)} g)`:fmtMetric(tableSalt);

summary.sugarWrap.classList.toggle('hidden',sugar<=0); summary.sugarPct.textContent=`${S.targetSugar.toFixed(2)}%`; summary.sugar.textContent=(S.unit==='imperial')?`${fmtImperial(sugar)} (${sugar.toFixed(0)} g)`:fmtMetric(sugar); summary.cureWrap.classList.toggle('hidden',cureSalt<=0); // Update cure percentage display in summary if(S.cureTargetMode==='ppm'){ const ppmPct=(S.targetPPM/PPM_PER_PERCENT).toFixed(3); summary.curePct.textContent=`${ppmPct}% (${S.targetPPM} ppm)`; }else{ const legacyPct=(S.cureLevel==='bacon')?CURE_PCT_BACON:CURE_SALT_PERCENT; const legacyPPM=(S.cureLevel==='bacon')?120:156; summary.curePct.textContent=`${legacyPct}% (~${legacyPPM} ppm)`; } summary.cure.textContent=(S.unit==='imperial')?`${fmtImperial(cureSalt)} (${cureSalt.toFixed(0)} g)`:fmtMetric(cureSalt); // Update accelerator in summary const acceleratorWrap=$('summary-accelerator-wrap'); if(acceleratorWrap && accelerator>0){ acceleratorWrap.classList.remove('hidden'); $('summary-accelerator').textContent=(S.unit==='imperial')?`${fmtImperial(accelerator)} (${accelerator.toFixed(0)} g)`:fmtMetric(accelerator); }else if(acceleratorWrap){ acceleratorWrap.classList.add('hidden'); }

if(S.unit==='imperial'){ renderVolumeBoxes(tableSalt); } else { volumes.classList.add('hidden'); }

if(S.mode==='wet'&&warn){ modeWarn.textContent=warn; modeWarn.classList.remove('hidden'); } else { modeWarn.classList.add('hidden'); }

saltResults.classList.remove('hidden'); }else{ saltResults.classList.add('hidden'); }

// Show/hide cure mode toggle and sync inline help if(cureModeToggle){ cureModeToggle.classList.toggle('hidden',!S.useCure); } if(cureHelpTrigger){ cureHelpTrigger.disabled=!S.useCure; } syncCureHelpVisibility();

// Show/hide advanced controls (only in PPM mode) const cureAdvancedControls=$('cure-advanced-controls'); if(cureAdvancedControls){ cureAdvancedControls.classList.toggle('hidden',!(S.useCure && S.cureTargetMode==='ppm')); }

// Show/hide pickup % (only for PPM mode in wet/eq) const pickupWrap=$('pickup-pct-wrap'); if(pickupWrap){ pickupWrap.classList.toggle('hidden',!(S.cureTargetMode==='ppm' && S.mode!=='dry')); }

// Lock PPM controls for bacon-pumped (mandatory 120 ppm) const ppmPreset=$('ppm-preset'); const ppmCustom=$('ppm-custom'); const baconLockNote=$('bacon-pumped-lock-note'); const lockBacon=(S.productType==='bacon-pumped' && S.cureTargetMode==='ppm');

if(ppmPreset){ ppmPreset.disabled=lockBacon; if(lockBacon) ppmPreset.value='120'; } if(ppmCustom){ const customSelected=(ppmPreset && ppmPreset.value==='custom'); ppmCustom.classList.toggle('hidden',!customSelected); ppmCustom.disabled=lockBacon; } if(baconLockNote){ baconLockNote.classList.toggle('hidden',!lockBacon); }

copyBtn.disabled=!ready; printBtn.disabled=!ready;

// Time calculation based on mode if(S.timeMode==='brine'){ const hrs=Math.ceil(Math.pow(S.thickIn,2)*BRINE_DIFFUSION_RATES[S.shape]); $('time-out').textContent=hoursToDH(hrs); }else{ // S.timeMode === 'cure' const thicknessSq=Math.pow(S.thickIn,2); const hrs=Math.ceil(thicknessSq*CURE_K[S.shape]); const days=(hrs/24).toFixed(1); const [kLo,kHi]=CURE_K_RANGE[S.shape]; const rangeLo=Math.ceil(thicknessSq*kLo); const rangeHi=Math.ceil(thicknessSq*kHi); const daysLo=(rangeLo/24).toFixed(1); const daysHi=(rangeHi/24).toFixed(1); $('time-out').textContent=`${days} days (Range: ${daysLo}-${daysHi} days)`; }

// Validate thickness - warn if unrealistic for home curing if(S.thickIn > 6){ thicknessWarning.classList.remove('hidden'); }else{ thicknessWarning.classList.add('hidden'); }

updatePrintView(); updateMathFormulas(); updateTimeExamples(); stateToParams(); }

// Update time examples function updateTimeExamples(){ if(!timeExamplesList) return;

let examples=[]; if(S.timeMode==='brine'){ // Flavor brine examples examples=[ '1″ chicken breast (flat) ≈ 8 hours', '1.5″ pork chop (flat) ≈ 18 hours', '2″ turkey breast (tubular) ≈ 48 hours (2 days)' ]; }else{ // Equilibrium cure examples examples=[ '1″ duck breast (flat) ≈ 5 days', '1.5″ pork belly/bacon (flat) ≈ 11 days', '2″ pastrami flat (flat) ≈ 20 days' ]; }

timeExamplesList.innerHTML=examples.map(ex=>`

  • ${ex}`).join(''); }

    // Update math formulas function updateMathFormulas(){ if(!mathSaltFormula || !mathTimeFormula) return;

    // Salt calculation formula if(isReady()){ const { total, tableSalt } = calc(); let formula=''; if(S.mode==='eq'){ formula=`Equilibrium: Salt = (Meat + Water) × Target%
    `; formula+=`Example: (${S.meatG.toFixed(0)}g + ${S.waterG.toFixed(0)}g) × ${S.target.toFixed(1)}% = ${tableSalt.toFixed(1)}g`; }else if(S.mode==='wet'){ formula=`Wet Brine: Salt = Water × Target%
    `; formula+=`Example: ${S.waterG.toFixed(0)}g × ${S.target.toFixed(1)}% = ${tableSalt.toFixed(1)}g`; }else if(S.mode==='dry'){ formula=`Dry Brine: Salt = Meat × Target%
    `; formula+=`Example: ${S.meatG.toFixed(0)}g × ${S.target.toFixed(1)}% = ${tableSalt.toFixed(1)}g`; } mathSaltFormula.innerHTML=formula; }

    // Time calculation formula const thickDisp=(S.unit==='imperial')?`${S.thickIn.toFixed(1)} in`:`${(S.thickIn*CM_PER_IN).toFixed(1)} cm`; if(S.timeMode==='brine'){ const k=BRINE_DIFFUSION_RATES[S.shape]; const hrs=Math.ceil(Math.pow(S.thickIn,2)*k); mathTimeFormula.innerHTML=`Flavor Brine: Time (hours) = thickness² × diffusion rate
    `; mathTimeFormula.innerHTML+=`Example: (${S.thickIn.toFixed(1)} in)² × ${k} hrs/in² = ${hrs} hours
    `; mathTimeFormula.innerHTML+=`Shape: ${S.shape} (${S.shape==='flat'?'faster':'slower'} diffusion)`; }else{ const k=CURE_K[S.shape]; const thicknessSq=Math.pow(S.thickIn,2); const hrs=Math.ceil(thicknessSq*k); const days=(hrs/24).toFixed(1); mathTimeFormula.innerHTML=`Equilibrium Cure: Time (hours) = thickness² × cure constant
    `; mathTimeFormula.innerHTML+=`Example: (${S.thickIn.toFixed(1)} in)² × ${k} hrs/in² = ${hrs} hours (${days} days)
    `; mathTimeFormula.innerHTML+=`Shape: ${S.shape} (${S.shape==='flat'?'faster':'slower'} equilibration)`; } }

    // Print view updater function updatePrintView(){ const ready=isReady(); if(!ready) return;

    const { total, tableSalt, cureSalt, accelerator, sugar, cureRate } = calc(); const f=(S.unit==='imperial')?fmtImperial:fmtMetric; const g=(val)=>`(${val.toFixed(0)} g)`; const f_g=(val)=>(S.unit==='imperial')?`${f(val)} ${g(val)}`:f(val);

    $('print-date').textContent=`Generated: ${new Date().toLocaleDateString()}`;

    $('print-method').textContent=`Method: ${S.mode.charAt(0).toUpperCase()+S.mode.slice(1)} Brine`; $('print-salt-pct').textContent=`Salt: ${S.target.toFixed(1)}%`; $('print-sugar-pct').textContent=`Sugar: ${S.targetSugar.toFixed(2)}%`;

    const presetName=S.selectedPreset&&PRESETS[S.selectedPreset]?PRESETS[S.selectedPreset].label:null; $('print-preset-li').classList.toggle('print-hidden',!presetName); if(presetName){ $('print-preset').textContent=`Preset: ${presetName}`; }

    $('print-meat').textContent=`Meat: ${f_g(S.meatG)}`; const showWater=(S.waterG>0 && S.mode!=='dry'); $('print-water-li').classList.toggle('print-hidden',!showWater); $('print-water').textContent=`Water: ${f_g(S.waterG)}`; $('print-total-li').classList.toggle('print-hidden',S.mode==='dry'); $('print-total').textContent=`Total Weight: ${f_g(total)}`;

    $('print-salt').textContent=`Salt (by weight): ${f_g(tableSalt)} (${S.target.toFixed(1)}%)`; $('print-sugar-li').classList.toggle('print-hidden',sugar<=0); $('print-sugar').textContent=`Sugar: ${f_g(sugar)} (${S.targetSugar.toFixed(2)}%)`; $('print-cure-li').classList.toggle('print-hidden',cureSalt<=0); if(cureSalt>0){ const ppmPct=(S.cureTargetMode==='ppm')?(S.targetPPM/PPM_PER_PERCENT).toFixed(3):((S.cureLevel==='bacon')?CURE_PCT_BACON:CURE_SALT_PERCENT).toFixed(3); const displayPPM=(S.cureTargetMode==='ppm')?S.targetPPM:((S.cureLevel==='bacon')?120:156);

    let basisNote=''; if(S.cureTargetMode==='ppm'){ basisNote=(S.mode==='dry' || S.productType==='bacon-dry')?', ingoing basis = meat only':`, ingoing basis = meat + ${S.pickupPct}% pickup`; }

    $('print-cure').textContent=`Curing Salt #1: ${f_g(cureSalt)} — ${ppmPct}% (${displayPPM} ppm${basisNote})`;

    // Add new paragraph explaining basis (create new element if needed) let cureBasisP=$('print-cure-basis'); if(!cureBasisP && S.cureTargetMode==='ppm'){ cureBasisP=document.createElement('p'); cureBasisP.id='print-cure-basis'; cureBasisP.style.fontSize='11px'; cureBasisP.style.color='#64748b'; cureBasisP.style.marginTop='4px'; $('print-cure').parentNode.appendChild(cureBasisP); }

    if(cureBasisP && S.cureTargetMode==='ppm'){ if(S.mode==='dry' || S.productType==='bacon-dry'){ cureBasisP.textContent='PPM calculated on meat weight only (dry cure).'; }else{ cureBasisP.textContent=`PPM calculated on ingoing basis: ${S.meatG.toFixed(0)}g meat + ${S.pickupPct}% pickup = ${ingoingBasisG.toFixed(0)}g total.`; } cureBasisP.classList.remove('print-hidden'); }else if(cureBasisP){ cureBasisP.classList.add('print-hidden'); } }

    // Add accelerator to print (create new list item if needed) if(accelerator>0){ let accLi=$('print-accelerator-li'); if(!accLi){ // Create if doesn't exist accLi=document.createElement('li'); accLi.id='print-accelerator-li'; const accSpan=document.createElement('span'); accSpan.id='print-accelerator'; accLi.appendChild(accSpan); $('print-ingredients-list').appendChild(accLi); } accLi.classList.remove('print-hidden'); $('print-accelerator').textContent=`Sodium Ascorbate/Erythorbate: ${f_g(accelerator)} — 550 ppm on ingoing basis`; }else{ const accLi=$('print-accelerator-li'); if(accLi) accLi.classList.add('print-hidden'); }

    $('print-salt-vol').classList.toggle('print-hidden',S.unit!=='imperial'); if(S.unit==='imperial'){ const avgDc=(DENSITY_PRESETS.dc.range[0]+DENSITY_PRESETS.dc.range[1])/2; const v=volumesFromGrams(tableSalt,avgDc); let volText=''; if(v.cups>0.5){ volText=`~${v.cups.toFixed(1)} cups`; } else if(v.tbsp>1){ volText=`~${v.tbsp.toFixed(1)} Tbsp`; } else { volText=`~${v.tsp.toFixed(0)} tsp`; } $('print-salt-vol').textContent=`Volume (Diamond Crystal): ${volText}`; } }

    // Events themeToggle.addEventListener('click',cycleTheme); tabs.eq.addEventListener('click',()=>setMode('eq')); tabs.wet.addEventListener('click',()=>setMode('wet')); tabs.dry.addEventListener('click',()=>setMode('dry')); tabs.time.addEventListener('click',()=>setMode('time')); tablist.addEventListener('keydown',(e)=>{ if(e.key!=='ArrowLeft'&&e.key!=='ArrowRight')return; e.preventDefault(); const order=['eq','wet','dry','time']; let i=order.indexOf(S.mode); i=e.key==='ArrowRight'?(i+1)%order.length:(i-1+order.length)%order.length; const next=order[i]; tabs[next].focus(); setMode(next); }); unitBtns.m.addEventListener('click',()=>{updateUnits('metric');track('brine_unit',{unit:'metric'});}); unitBtns.i.addEventListener('click',()=>{updateUnits('imperial');track('brine_unit',{unit:'imperial'});});

    target.addEventListener('input',e=>{S.target=parseFloat(e.target.value)||0; S.selectedPreset=null; S.cureLevel='default'; render(); renderPresetChips();}); function updMeatImp(){const lb=parseFloat(meatLb.value)||0,oz=parseFloat(meatOz.value)||0; S.meatG=(lb*16+oz)*G.GRAMS_PER_OZ; S.selectedPreset=null; S.cureLevel='default'; render(); renderPresetChips();} meatLb.addEventListener('input',updMeatImp); meatOz.addEventListener('input',updMeatImp); meatG.addEventListener('input',()=>{S.meatG=parseFloat(meatG.value)||0; S.selectedPreset=null; S.cureLevel='default'; render(); renderPresetChips();}); function updWaterImp(){const amt=parseFloat(waterVol.value)||0; const u=waterUnit.value; S.waterG=(u==='cup')?amt*G.G_PER_CUP:(u==='quart')?amt*G.G_PER_QUART:amt*G.G_PER_GAL; S.selectedPreset=null; S.cureLevel='default'; render(); renderPresetChips();} waterVol.addEventListener('input',updWaterImp); waterUnit.addEventListener('change',updWaterImp); waterGEl.addEventListener('input',()=>{S.waterG=parseFloat(waterGEl.value)||0; S.selectedPreset=null; S.cureLevel='default'; render(); renderPresetChips();});

    thick.addEventListener('input',()=>{ const val=parseFloat(thick.value)||0; const newThickIn=(S.unit==='imperial')?val:(val/CM_PER_IN); S.thickIn=parseFloat(newThickIn.toFixed(4)); S.selectedPreset=null; render(); }); shape.addEventListener('change',()=>{S.shape=shape.value; S.selectedPreset=null; render();});

    copyBtn.addEventListener('click', async ()=>{ if(copyInProgress) return; copyInProgress=true; const { tableSalt, cureSalt, accelerator, sugar, cureRate, ingoingBasisG } = calc(); const modeName=S.mode.charAt(0).toUpperCase()+S.mode.slice(1); const presetName=S.selectedPreset&&PRESETS[S.selectedPreset]?PRESETS[S.selectedPreset].label:null;

    let txt=`BRINING PLAN\n`; txt+=`Method: ${modeName} Brine - ${S.target.toFixed(1)}% Salt\n`; if(presetName) txt+=`Preset: ${presetName}\n`; txt+=`\n--- Weights ---\n`; txt+=`Meat: ${S.unit==='imperial'?fmtImperial(S.meatG):fmtMetric(S.meatG)}\n`; if(S.mode!=='dry'&&S.waterG>0){ txt+=`Water: ${S.unit==='imperial'?fmtImperial(S.waterG):fmtMetric(S.waterG)}\n`; } txt+=`\n--- Ingredients ---\n`; txt+=`Salt (by weight): ${S.unit==='imperial'?fmtImperial(tableSalt):fmtMetric(tableSalt)}`; if(S.unit==='imperial') txt+=` (${tableSalt.toFixed(0)} g)`; txt+=`\n`; if(sugar>0){ txt+=`Sugar: ${S.unit==='imperial'?fmtImperial(sugar):fmtMetric(sugar)}`; if(S.unit==='imperial') txt+=` (${sugar.toFixed(0)} g)`; txt+=`\n`; } if(cureSalt>0){ const ppmPct=(S.cureTargetMode==='ppm')?(S.targetPPM/PPM_PER_PERCENT).toFixed(3):((S.cureLevel==='bacon')?CURE_PCT_BACON:CURE_SALT_PERCENT).toFixed(3); const displayPPM=(S.cureTargetMode==='ppm')?S.targetPPM:((S.cureLevel==='bacon')?120:156);

    txt+=`Curing Salt #1: ${S.unit==='imperial'?fmtImperial(cureSalt):fmtMetric(cureSalt)}`; if(S.unit==='imperial') txt+=` (${cureSalt.toFixed(0)} g)`;

    // Add basis information for PPM mode if(S.cureTargetMode==='ppm'){ if(S.mode==='dry' || S.productType==='bacon-dry'){ txt+=` — ${ppmPct}% (${displayPPM} ppm, ingoing basis = meat only)`; }else{ txt+=` — ${ppmPct}% (${displayPPM} ppm, ingoing basis = meat + ${S.pickupPct}% pickup)`; } }else{ txt+=` — ${ppmPct}% (~${displayPPM} ppm)`; } txt+=`\n`;

    // Add detailed basis calculation in PPM mode if(S.cureTargetMode==='ppm'){ if(S.mode==='dry' || S.productType==='bacon-dry'){ txt+=` (PPM basis: meat weight only)\n`; }else{ txt+=` (PPM basis: meat weight + ${S.pickupPct}% pickup = ${ingoingBasisG.toFixed(0)}g)\n`; } } } if(accelerator>0){ txt+=`Sodium Ascorbate/Erythorbate: ${S.unit==='imperial'?fmtImperial(accelerator):fmtMetric(accelerator)}`; if(S.unit==='imperial') txt+=` (${accelerator.toFixed(0)} g)`; txt+=` — 550 ppm on ingoing basis (required for pumped bacon)\n`; }

    const ok=await copy(txt); showToast(ok?'Plan copied!':'Copy failed'); copyInProgress=false; track('brine_copy',{mode:S.mode,unit:S.unit,cured:S.useCure,cureMode:S.cureTargetMode}); });

    copyTime.addEventListener('click', async ()=>{ if(copyInProgress) return; copyInProgress=true; const thicknessSq=Math.pow(S.thickIn,2); const thickDisp=(S.unit==='imperial')?`${S.thickIn.toFixed(1)} in`:`${(S.thickIn*CM_PER_IN).toFixed(1)} cm`; let timeText=''; if(S.timeMode==='brine'){ const hrs=Math.ceil(thicknessSq*BRINE_DIFFUSION_RATES[S.shape]); timeText=`${hoursToDH(hrs)}\n(Plan ±20% around this estimate)`; }else{ const hrs=Math.ceil(thicknessSq*CURE_K[S.shape]); const days=(hrs/24).toFixed(1); const [kLo,kHi]=CURE_K_RANGE[S.shape]; const rangeLo=Math.ceil(thicknessSq*kLo); const rangeHi=Math.ceil(thicknessSq*kHi); const daysLo=(rangeLo/24).toFixed(1); const daysHi=(rangeHi/24).toFixed(1); timeText=`${days} days (Range: ${daysLo}-${daysHi} days)\n(Minimum time for full equilibrium)`; } const modeLabel=S.timeMode==='brine'?'Flavor Brining':'Equilibrium Cure'; const tempReminder='\n\nTemperature: Keep at 36-38°F (2-3°C). Colder temps increase time; verify your fridge temperature.'; const txt=`BRINING TIME (${modeLabel})\nShape: ${S.shape.charAt(0).toUpperCase()+S.shape.slice(1)}\nThickness: ${thickDisp}\nTime: ${timeText}${tempReminder}`; const ok=await copy(txt); showToast(ok?'Time copied!':'Copy failed'); copyInProgress=false; track('brine_time_copy',{shape:S.shape,timeMode:S.timeMode}); });

    // Density listeners const densRadios=document.querySelectorAll('input[name="dens"]'); densRadios.forEach(r=>{ r.addEventListener('change',(e)=>{ if(e.target.checked){ DState.preset=e.target.value; render(); } }); }); densCustom.addEventListener('input',(e)=>{ let val=parseInt(e.target.value)||135; val=Math.max(80,Math.min(350,val)); DState.customGPerCup=val; const customRadio=document.querySelector('input[name="dens"][value="custom"]'); if(customRadio) customRadio.checked=true; DState.preset='custom'; render(); }); densRange.addEventListener('change',(e)=>{ DState.showRange=e.target.checked; render(); });

    // New feature listeners useCure.addEventListener('change',(e)=>{ S.useCure=e.target.checked; S.selectedPreset=null; S.cureLevel='default'; cureHelpOverride=S.useCure?true:null; updateCureInfo(); render(); renderPresetChips(); });

    if(cureHelpTrigger){ cureHelpTrigger.addEventListener('click',()=>{ if(!S.useCure){ S.useCure=true; useCure.checked=true; cureHelpOverride=true; updateCureInfo(); render(); renderPresetChips(); track('brine_cure_help_toggle',{open:true,reason:'auto-enable'}); return; }

    const expanded=cureHelpTrigger.getAttribute('aria-expanded')==='true'; cureHelpOverride=!expanded; syncCureHelpVisibility(); track('brine_cure_help_toggle',{open:cureHelpOverride}); }); } targetSugar.addEventListener('input',(e)=>{ S.targetSugar=parseFloat(e.target.value)||0; S.selectedPreset=null; S.cureLevel='default'; render(); renderPresetChips(); });

    // Cure mode toggle (% vs PPM) document.querySelectorAll('input[name="cure-mode"]').forEach(radio=>{ radio.addEventListener('change',(e)=>{ S.cureTargetMode=e.target.value; S.selectedPreset=null; updateCureLabel(); updateCureInfo(); render(); renderPresetChips(); }); });

    // Product type selector const productTypeEl=$('product-type'); if(productTypeEl){ productTypeEl.addEventListener('change',(e)=>{ S.productType=e.target.value;

    // Auto-lock bacon-pumped to 120 ppm if(S.productType==='bacon-pumped' && S.cureTargetMode==='ppm'){ S.targetPPM=120; const preset=$('ppm-preset'); if(preset) preset.value='120'; }

    S.selectedPreset=null; updateCureLabel(); updateCureInfo(); render(); }); }

    // PPM preset selector const ppmPresetEl=$('ppm-preset'); if(ppmPresetEl){ ppmPresetEl.addEventListener('change',(e)=>{ if(e.target.value==='custom'){ const customInput=$('ppm-custom'); if(customInput){ S.targetPPM=Math.min(250,Math.max(80,parseInt(customInput.value)||156)); } }else{ S.targetPPM=parseInt(e.target.value); } S.selectedPreset=null; updateCureLabel(); updateCureInfo(); render(); }); }

    // PPM custom input const ppmCustomInput=$('ppm-custom'); if(ppmCustomInput){ ppmCustomInput.addEventListener('input',(e)=>{ // Clamp to valid range let val=parseInt(e.target.value); if(isNaN(val)) val=156; S.targetPPM=Math.min(250,Math.max(80,val)); S.selectedPreset=null; updateCureLabel(); updateCureInfo(); render(); }); }

    // Pickup % input const pickupPctInput=$('pickup-pct'); if(pickupPctInput){ pickupPctInput.addEventListener('input',(e)=>{ // Clamp to valid range [5, 25] let val=parseInt(e.target.value); if(isNaN(val)) val=10; S.pickupPct=Math.min(25,Math.max(5,val)); e.target.value=S.pickupPct; // Force clamped value back into input updateCureLabel(); render(); }); }

    // Time mode listeners function updateTimeMode(mode){ S.timeMode=mode; timeBrineBtn.setAttribute('aria-pressed',mode==='brine'?'true':'false'); timeCureBtn.setAttribute('aria-pressed',mode==='cure'?'true':'false'); if(mode==='brine'){ timeBrineBtn.style.background='var(--accent)'; timeBrineBtn.style.color='#fff'; timeCureBtn.style.background='#fff'; timeCureBtn.style.color='var(--accent)'; timeResultTitle.textContent='Brining time estimate'; timeResultHelper.innerHTML='Guideline for flavor brining (e.g., poultry, chops) at 34–39°F. Plan ±20% around this estimate.'; }else{ timeCureBtn.style.background='var(--accent)'; timeCureBtn.style.color='#fff'; timeBrineBtn.style.background='#fff'; timeBrineBtn.style.color='var(--accent)'; timeResultTitle.textContent='Minimum equilibrium cure time'; timeResultHelper.innerHTML='Minimum time for full equilibrium cure (e.g., bacon, ham) at 34–39°F. Colder fridges or higher fat content may take longer.'; } render(); track('brine_time_mode',{timeMode:mode}); } timeBrineBtn.addEventListener('click',()=>updateTimeMode('brine')); timeCureBtn.addEventListener('click',()=>updateTimeMode('cure'));

    // Show math toggle listeners if(showMathSaltBtn && mathSaltContent){ showMathSaltBtn.addEventListener('click',()=>{ const isExpanded=showMathSaltBtn.getAttribute('aria-expanded')==='true'; showMathSaltBtn.setAttribute('aria-expanded',!isExpanded?'true':'false'); mathSaltContent.classList.toggle('hidden'); track('brine_show_math',{type:'salt',expanded:!isExpanded}); }); } if(showMathTimeBtn && mathTimeContent){ showMathTimeBtn.addEventListener('click',()=>{ const isExpanded=showMathTimeBtn.getAttribute('aria-expanded')==='true'; showMathTimeBtn.setAttribute('aria-expanded',!isExpanded?'true':'false'); mathTimeContent.classList.toggle('hidden'); track('brine_show_math',{type:'time',expanded:!isExpanded}); }); }

    // Print logic printBtn.addEventListener('click',()=>{ track('brine_print',{mode:S.mode,unit:S.unit}); const printContent=$('print-view'); if(!printContent){ console.error('Print view element not found!'); return; }

    const iframe=document.createElement('iframe'); iframe.style.position='absolute'; iframe.style.width='0'; iframe.style.height='0'; iframe.style.border='0'; iframe.style.left='-9999px'; iframe.style.top='-9999px'; iframe.setAttribute('aria-hidden','true'); iframe.setAttribute('title','Print Content'); document.body.appendChild(iframe); const iDoc=iframe.contentWindow.document;

    const allStyles=document.querySelectorAll('style'); allStyles.forEach(s=>{ iDoc.head.appendChild(s.cloneNode(true)); });

    iDoc.body.innerHTML = `

  • Mike

    Thursday 20th of November 2025

    Does entering the turkey weigh for the whole bird correct for the weight of the bones, etc? I entered a 13.2 pound turkey in grams, but not all of that needs to be salted or will take salt, so will I end up with an overly salty bird. Do I need to manually calculate how much meat there actually is or does the calculator do that?

    James Roller

    Friday 21st of November 2025

    @Mike, these are excellent questions and you are not too late for Thanksgiving.

    1. Whole bird weight vs bones For the equilibrium brine mode, the calculator expects the whole raw weight of the turkey, just like you entered. It does not try to subtract bones or guess how much is edible meat, and that is intentional.

    In an equilibrium brine you are treating the bird and the brine as one system. The math uses the total weight (meat, skin, bones, and water if you are using a wet brine) to hit a target overall salt percentage. The salt then diffuses until everything settles around that level. Because of that:

    Using the full 13.2 lb turkey weight is the normal way to do it. You will not oversalt the bird simply because some of that weight is bone. If anything, the bone and less-salty parts slightly dilute the effective salt percentage in the meat.

    If you want to be extra cautious, you can always aim for the lower end of the recommended range (for example 1.3–1.4% instead of 1.5%), but you should not need to manually estimate “real meat weight” to get a good result.

    2. That “10.5 days” time estimate

    On the Time tab there are two very different modes:

    Flavor Brine (hours) – for typical holiday brines where you care about seasoning and juiciness. Equilibrium Cure (days) – for long, charcuterie-style cures (bacon, hams, etc.) where you want the very center to match the exact salt level of the outside. If you had Equilibrium Cure selected with “tubular” and a 3 cm value, that 10.5-day estimate is the calculator saying, “This is how long it might take for a full equilibrium cure all the way to the center at fridge temps.” That is useful for something like a cured ham, but it is overkill for a Thanksgiving turkey.

    For a turkey, you have two good options instead:

    Use Flavor Brine (hours) on the Time tab to get a more realistic window in hours. Or follow the quick-reference guidance on the page:

    Whole turkey in a wet brine at 5–7%: roughly 8–24 hours. Dry brine around 2% of meat weight: roughly 24–48 hours, uncovered for better skin.

    So no, you are not too late. That 10.5-day number is correct for a full equilibrium cure model, but it is not the target you need for a straightforward holiday turkey.

    Mike

    Thursday 20th of November 2025

    @Mike, Also the the brining time calculator the thing show "10.5 days (Range: 7.8-14.0 days)" for tubular 3sm max thickness. Is that correct? IF so, I am too late for thanksgiving this year.

    Mike

    Thursday 20th of November 2025

    @Mike, This is for a equilibrium brine.

    What Folks Say about

    Front cover of the Going Whole Hog cookbook

    AWESOME!!

    Absolutely amazing! I expected a quality book and Going Whole Hog exceeded my expectations. Well written and researched with high quality photos which draw the reader along. A true tribute to South Carolina BBQ. The recipes and narrative compel us to reach out and savor not only our own ‘Que, but to explore the finest… Read more “AWESOME!!”

    Andy Petrone