331 lines
12 KiB
HTML
331 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Handmade Soap Price Calculator</title>
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
background-color: #0d0d1a;
|
|
background-image:
|
|
radial-gradient(circle at 50% 0, rgba(157, 78, 221, 0.1) 0%, transparent 50%),
|
|
radial-gradient(circle at 100% 100%, rgba(0, 191, 165, 0.1) 0%, transparent 50%),
|
|
radial-gradient(circle at 0 100%, rgba(0, 123, 255, 0.1) 0%, transparent 50%);
|
|
background-size: 50px 50px;
|
|
color: #e0e0e0;
|
|
margin: 0;
|
|
padding: 20px;
|
|
}
|
|
.container {
|
|
max-width: 800px;
|
|
margin: auto;
|
|
background: #2a2a3e;
|
|
padding: 25px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
|
|
}
|
|
h1, h2 {
|
|
text-align: center;
|
|
color: #a0a0d0;
|
|
}
|
|
h2 {
|
|
border-bottom: 2px solid #4a4a6e;
|
|
padding-bottom: 10px;
|
|
margin-top: 30px;
|
|
}
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 15px;
|
|
}
|
|
th, td {
|
|
border: 1px solid #4a4a6e;
|
|
padding: 12px;
|
|
text-align: left;
|
|
}
|
|
th {
|
|
background-color: #3a3a5e;
|
|
font-weight: 600;
|
|
}
|
|
input[type="text"], input[type="number"] {
|
|
width: 100%;
|
|
padding: 8px;
|
|
box-sizing: border-box;
|
|
border: 1px solid #5a5a7e;
|
|
border-radius: 4px;
|
|
background-color: #1a1a2e;
|
|
color: #e0e0e0;
|
|
}
|
|
.remove-btn, .add-btn {
|
|
background-color: #9d4edd;
|
|
color: #e0e0e0;
|
|
border: none;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
}
|
|
.add-btn {
|
|
background-color: #00bfa5;
|
|
margin-top: 10px;
|
|
display: inline-block;
|
|
}
|
|
.grid-container {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
.form-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: 600;
|
|
color: #a0a0d0;
|
|
}
|
|
#calculate-btn {
|
|
width: 100%;
|
|
padding: 15px;
|
|
background-color: #007bff;
|
|
color: #e0e0e0;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 1.2em;
|
|
margin-top: 20px;
|
|
}
|
|
#results {
|
|
margin-top: 30px;
|
|
background: #1a1a2e;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
}
|
|
#results h2 {
|
|
margin-top: 0;
|
|
}
|
|
.result-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid #4a4a6e;
|
|
}
|
|
.result-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.result-item strong {
|
|
color: #50fa7b;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="container">
|
|
<h1>Handmade Soap Price Calculator</h1>
|
|
|
|
<!-- Section for Oils, Butters, Lye -->
|
|
<h2>Oils, Butters & Lye</h2>
|
|
<table id="oils-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Ingredient</th>
|
|
<th>Cost per Unit (e.g., oz, g)</th>
|
|
<th>Amount Used</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><input type="text" class="ingredient-name" placeholder="e.g., Olive Oil"></td>
|
|
<td><input type="number" class="cost-per-unit" placeholder="0.25"></td>
|
|
<td><input type="number" class="amount-used" placeholder="16"></td>
|
|
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<button class="add-btn" onclick="addItem('oils-table')">Add Oil/Butter/Lye</button>
|
|
|
|
<!-- Section for Additives -->
|
|
<h2>Additives (Essential Oils, Colorants, etc.)</h2>
|
|
<table id="additives-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Item</th>
|
|
<th>Cost</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><input type="text" class="additive-name" placeholder="e.g., Lavender EO"></td>
|
|
<td><input type="number" class="additive-cost" placeholder="5.00"></td>
|
|
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<button class="add-btn" onclick="addItem('additives-table')">Add Additive</button>
|
|
|
|
<!-- Section for Packaging -->
|
|
<h2>Packaging</h2>
|
|
<table id="packaging-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Item</th>
|
|
<th>Cost per Bar</th>
|
|
<th>Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><input type="text" class="packaging-name" placeholder="e.g., Label"></td>
|
|
<td><input type="number" class="packaging-cost" placeholder="0.15"></td>
|
|
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<button class="add-btn" onclick="addItem('packaging-table')">Add Packaging Item</button>
|
|
|
|
<!-- Section for Labor & Overhead -->
|
|
<h2>Labor & Overhead</h2>
|
|
<div class="grid-container">
|
|
<div class="form-group">
|
|
<label for="hourly-rate">Your Hourly Rate ($)</label>
|
|
<input type="number" id="hourly-rate" value="25">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="time-spent">Time Spent per Batch (hours)</label>
|
|
<input type="number" id="time-spent" value="2">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="overhead-percent">Overhead (%)</label>
|
|
<input type="number" id="overhead-percent" value="15">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Section for Batch Details -->
|
|
<h2>Batch Details</h2>
|
|
<div class="grid-container">
|
|
<div class="form-group">
|
|
<label for="bars-per-batch">Number of Bars in Batch</label>
|
|
<input type="number" id="bars-per-batch" value="12">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="markup-factor">Markup Factor</label>
|
|
<input type="number" id="markup-factor" value="3">
|
|
</div>
|
|
</div>
|
|
|
|
<button id="calculate-btn" onclick="calculatePrice()">Calculate Price</button>
|
|
|
|
<div id="results"></div>
|
|
</div>
|
|
|
|
<script>
|
|
function addItem(tableId) {
|
|
const tableBody = document.getElementById(tableId).getElementsByTagName('tbody')[0];
|
|
const newRow = tableBody.insertRow();
|
|
let cells = '';
|
|
if (tableId === 'oils-table') {
|
|
cells = `
|
|
<td><input type="text" class="ingredient-name" placeholder="e.g., Coconut Oil"></td>
|
|
<td><input type="number" class="cost-per-unit" placeholder="0.15"></td>
|
|
<td><input type="number" class="amount-used" placeholder="12"></td>
|
|
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
|
|
`;
|
|
} else if (tableId === 'additives-table') {
|
|
cells = `
|
|
<td><input type="text" class="additive-name" placeholder="e.g., Clay"></td>
|
|
<td><input type="number" class="additive-cost" placeholder="1.50"></td>
|
|
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
|
|
`;
|
|
} else if (tableId === 'packaging-table') {
|
|
cells = `
|
|
<td><input type="text" class="packaging-name" placeholder="e.g., Box"></td>
|
|
<td><input type="number" class="packaging-cost" placeholder="0.50"></td>
|
|
<td><button class="remove-btn" onclick="removeItem(this)">Remove</button></td>
|
|
`;
|
|
}
|
|
newRow.innerHTML = cells;
|
|
}
|
|
|
|
function removeItem(button) {
|
|
const row = button.parentNode.parentNode;
|
|
// Prevent removing the last row in each table
|
|
if (row.parentNode.rows.length > 1) {
|
|
row.parentNode.removeChild(row);
|
|
} else {
|
|
alert("You must have at least one item in this section.");
|
|
}
|
|
}
|
|
|
|
function getTableTotal(tableId, col1Class, col2Class) {
|
|
let total = 0;
|
|
const rows = document.getElementById(tableId).getElementsByTagName('tbody')[0].rows;
|
|
for (const row of rows) {
|
|
const val1 = parseFloat(row.querySelector(col1Class).value) || 0;
|
|
const val2 = col2Class ? (parseFloat(row.querySelector(col2Class).value) || 0) : 1;
|
|
total += val1 * val2;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
function calculatePrice() {
|
|
// 1. Calculate Material Costs
|
|
const oilsCost = getTableTotal('oils-table', '.cost-per-unit', '.amount-used');
|
|
const additivesCost = getTableTotal('additives-table', '.additive-cost');
|
|
const totalMaterialCost = oilsCost + additivesCost;
|
|
|
|
// 2. Get Batch Details
|
|
const barsPerBatch = parseFloat(document.getElementById('bars-per-batch').value) || 1;
|
|
|
|
// 3. Calculate Packaging Cost
|
|
const packagingCostPerBar = getTableTotal('packaging-table', '.packaging-cost');
|
|
const totalPackagingCost = packagingCostPerBar * barsPerBatch;
|
|
|
|
// 4. Calculate Labor Cost
|
|
const hourlyRate = parseFloat(document.getElementById('hourly-rate').value) || 0;
|
|
const timeSpent = parseFloat(document.getElementById('time-spent').value) || 0;
|
|
const totalLaborCost = hourlyRate * timeSpent;
|
|
|
|
// 5. Calculate Subtotal (Materials + Packaging + Labor)
|
|
const subtotal = totalMaterialCost + totalPackagingCost + totalLaborCost;
|
|
|
|
// 6. Calculate Overhead
|
|
const overheadPercent = parseFloat(document.getElementById('overhead-percent').value) || 0;
|
|
const totalOverheadCost = subtotal * (overheadPercent / 100);
|
|
|
|
// 7. Calculate Total Batch Cost
|
|
const totalBatchCost = subtotal + totalOverheadCost;
|
|
|
|
// 8. Calculate Cost Per Bar
|
|
const costPerBar = totalBatchCost / barsPerBatch;
|
|
|
|
// 9. Calculate Suggested Pricing
|
|
const markupFactor = parseFloat(document.getElementById('markup-factor').value) || 3;
|
|
const retailPrice = costPerBar * markupFactor;
|
|
const wholesalePrice = retailPrice / 2;
|
|
|
|
// 10. Display Results
|
|
const resultsDiv = document.getElementById('results');
|
|
resultsDiv.innerHTML = `
|
|
<h2>Cost & Pricing Breakdown</h2>
|
|
<div class="result-item"><span>Total Material Cost:</span> <strong>$${totalMaterialCost.toFixed(2)}</strong></div>
|
|
<div class="result-item"><span>Total Packaging Cost:</span> <strong>$${totalPackagingCost.toFixed(2)}</strong></div>
|
|
<div class="result-item"><span>Total Labor Cost:</span> <strong>$${totalLaborCost.toFixed(2)}</strong></div>
|
|
<div class="result-item"><span>Overhead (${overheadPercent}%):</span> <strong>$${totalOverheadCost.toFixed(2)}</strong></div>
|
|
<div class="result-item"><span><strong>Total Cost for Batch:</strong></span> <strong>$${totalBatchCost.toFixed(2)}</strong></div>
|
|
<hr>
|
|
<div class="result-item"><span>Bars in Batch:</span> <strong>${barsPerBatch}</strong></div>
|
|
<div class="result-item"><span><strong>Cost per Bar:</strong></span> <strong>$${costPerBar.toFixed(2)}</strong></div>
|
|
<hr>
|
|
<h2>Suggested Pricing</h2>
|
|
<div class="result-item"><span>Markup Factor:</span> <strong>${markupFactor}x</strong></div>
|
|
<div class="result-item"><span><strong>Suggested Retail Price per Bar:</strong></span> <strong>$${retailPrice.toFixed(2)}</strong></div>
|
|
<div class="result-item"><span>Suggested Wholesale Price per Bar:</span> <strong>$${wholesalePrice.toFixed(2)}</strong></div>
|
|
`;
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html> |