-->
<!-- Visualization & Content Choices:
- Budget Allocation (Donut Chart): Shows the ratio of spending between Cleaning, Repairs, and Projects. Goal: Inform/Compare.
- Service Intensity (Bar Chart): Visualizes estimated hours/effort per category. Goal: Inform.
- Dynamic Text: The proposal letter injects the client name and specific services selected. Goal: Personalization.
- Interaction: Sliders and Toggles. Immediate feedback loop.
- Library: Chart.js for reliable, responsive canvas charts.
-->
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
</head>
<body class="bg-slate-50 min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-white border-b border-slate-200 sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-20 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 bg-sky-600 rounded flex items-center justify-center text-white font-bold text-xl">S</div>
<div>
<h1 class="text-xl font-bold text-slate-800 tracking-tight">Skyline Facility Group</h1>
<p class="text-xs text-slate-500 uppercase tracking-wider">Commercial Services</p>
</div>
</div>
<div class="hidden md:block text-right">
<p class="text-sm font-semibold text-slate-700">David Caneccio</p>
<p class="text-xs text-slate-500">Owner & Operator</p>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-grow max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 w-full">
<div class="mb-8">
<h2 class="text-3xl font-bold text-slate-800 mb-2">Custom Service Proposal</h2>
<p class="text-slate-600 max-w-3xl">
Configure your facility needs below to generate a preliminary service agreement and cost estimate.
Skyline Facility Group specializes in comprehensive office maintenance, form general cleaning to specialized marble and metal polishing.
</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Left Column: Input Form -->
<div class="lg:col-span-5 space-y-6">
<!-- Client Details -->
<div class="section-card p-6">
<h3 class="text-lg font-semibold text-slate-800 mb-4 border-b pb-2">1. Client Information</h3>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">Company Name</label>
<input type="text" id="companyName" placeholder="e.g. Acme Corp" class="w-full px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-sky-500 transition-colors">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">Facility Size (Sq. Ft.)</label>
<input type="number" id="sqFt" value="5000" min="1000" step="500" class="w-full px-3 py-2 border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-sky-500 transition-colors">
</div>
</div>
</div>
<!-- Janitorial Services -->
<div class="section-card p-6">
<div class="flex items-center justify-between mb-4 border-b pb-2">
<h3 class="text-lg font-semibold text-slate-800">2. Janitorial Services</h3>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="janitorialActive" class="sr-only peer" checked>
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-sky-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-sky-600"></div>
</label>
</div>
<div id="janitorialControls" class="space-y-4 transition-all duration-300">
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">Cleaning Frequency (Days/Week): <span id="freqVal" class="text-sky-600 font-bold">3</span></label>
<input type="range" id="frequency" min="1" max="7" value="3" class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer">
</div>
<div class="flex items-center gap-2">
<input type="checkbox" id="supplies" class="w-4 h-4 text-sky-600 border-gray-300 rounded focus:ring-sky-500">
<label for="supplies" class="text-sm text-slate-700">Include Consumable Supplies (Paper/Soap)</label>
</div>
</div>
</div>
<!-- Maintenance & Repair -->
<div class="section-card p-6">
<div class="flex items-center justify-between mb-4 border-b pb-2">
<h3 class="text-lg font-semibold text-slate-800">3. General Repairs & Paint</h3>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="repairActive" class="sr-only peer">
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-sky-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-sky-600"></div>
</label>
</div>
<div id="repairControls" class="space-y-4 opacity-50 pointer-events-none transition-all duration-300">
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">Maintenance Hours / Month: <span id="hoursVal" class="text-sky-600 font-bold">5</span></label>
<input type="range" id="repairHours" min="0" max="40" value="5" class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer">
</div>
<div class="grid grid-cols-2 gap-2">
<div class="flex items-center gap-2">
<input type="checkbox" id="painting" class="w-4 h-4 text-sky-600 border-gray-300 rounded focus:ring-sky-500">
<label for="painting" class="text-sm text-slate-700">Painting Touch-ups</label>
</div>
<div class="flex items-center gap-2">
<input type="checkbox" id="fixtures" class="w-4 h-4 text-sky-600 border-gray-300 rounded focus:ring-sky-500">
<label for="fixtures" class="text-sm text-slate-700">Fixture Install</label>
</div>
</div>
</div>
</div>
<!-- Specialized Services -->
<div class="section-card p-6">
<div class="flex items-center justify-between mb-4 border-b pb-2">
<h3 class="text-lg font-semibold text-slate-800">4. Specialized Polishing</h3>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="specialActive" class="sr-only peer">
<div class="w-11 h-6 bg-slate-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-sky-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-sky-600"></div>
</label>
</div>
<div id="specialControls" class="space-y-4 opacity-50 pointer-events-none transition-all duration-300">
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">Marble Floor Area (Sq Ft): <span id="marbleVal" class="text-sky-600 font-bold">0</span></label>
<input type="range" id="marbleSqFt" min="0" max="2000" step="100" value="0" class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer">
</div>
<div>
<label class="block text-sm font-medium text-slate-700 mb-1">Metal Fixtures Polishing (Hrs): <span id="metalVal" class="text-sky-600 font-bold">0</span></label>
<input type="range" id="metalHours" min="0" max="20" step="1" value="0" class="w-full h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer">
</div>
</div>
</div>
</div>
<!-- Right Column: Live Proposal Preview -->
<div class="lg:col-span-7 space-y-6">
<!-- Financial Summary -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Total Cost -->
<div class="section-card p-6 flex flex-col justify-center items-center text-center bg-sky-50 border-sky-100 border">
<h4 class="text-sm font-medium text-sky-800 uppercase tracking-wide mb-1">Estimated Monthly Investment</h4>
<div class="text-4xl font-bold text-slate-900 mb-2" id="totalCostDisplay">$0.00</div>
<p class="text-xs text-slate-500">*Estimate based on standard rates. Final quote subject to site walkthrough.</p>
</div>
<!-- Breakdown Chart -->
<div class="section-card p-4">
<h4 class="text-sm font-medium text-slate-700 mb-2 text-center">Cost Allocation</h4>
<div class="chart-container" style="height: 180px;">
<canvas id="costChart"></canvas>
</div>
</div>
</div>
<!-- Proposal Document -->
<div class="section-card p-8 bg-white border border-slate-200 relative overflow-hidden">
<!-- Decorative background element -->
<div class="absolute top-0 right-0 w-32 h-32 bg-sky-50 rounded-bl-full -z-0 opacity-50"></div>
<div class="relative z-10">
<div class="flex justify-between items-start mb-8">
<div>
<h2 class="text-2xl font-bold text-slate-900">Service Proposal</h2>
<p class="text-sm text-slate-500" id="proposalDate">January 1, 2026</p>
</div>
<div class="text-right">
<p class="text-sm font-bold text-slate-800">PREPARED FOR:</p>
<p class="text-lg text-sky-700 font-medium" id="clientNameDisplay">[Client Name]</p>
</div>
</div>
<div class="prose prose-slate max-w-none">
<p class="mb-4">
Dear <span id="clientSalutation">Valued Partner</span>,
</p>
<p class="mb-4">
Thank you for considering <strong>Skyline Facility Group</strong> for your property management needs.
Based on the specifications provided for your facility, we have designed a comprehensive service package
tailored to maintain the highest standards of cleanliness and operational efficiency.
</p>
<h4 class="text-lg font-semibold text-slate-800 mt-6 mb-2">Scope of Work Summary:</h4>
<ul class="list-disc pl-5 space-y-1 mb-6 text-slate-700" id="scopeList">
<!-- Dynamic List Items -->
<li>Configure services on the left to see details here.</li>
</ul>
<h4 class="text-lg font-semibold text-slate-800 mt-6 mb-2">Service Intensity Analysis:</h4>
<p class="text-sm text-slate-600 mb-4">The chart below illustrates the projected labor distribution across different service categories.</p>
<div class="chart-container mb-6 border rounded-lg p-4 bg-slate-50">
<canvas id="serviceChart"></canvas>
</div>
<p class="mb-8">
We are committed to providing "General Repairs," "Painting," "Fixture Installations," and specialized "Marble & Metal Polishing"
with the utmost professionalism. I personally oversee all major accounts to ensure quality.
</p>
<div class="border-t border-slate-200 pt-6 mt-8 flex flex-col sm:flex-row justify-between items-center gap-4">
<div class="text-center sm:text-left">
<p class="font-signature text-2xl text-sky-800 mb-1" style="font-family: 'Times New Roman', serif; font-style: italic;">David Caneccio</p>
<p class="text-sm font-bold text-slate-900">David Caneccio</p>
<p class="text-xs text-slate-500">Owner, Skyline Facility Group</p>
</div>
<button onclick="alert('Proposal Sent to Skyline Facility Group! We will contact you shortly.')" class="bg-sky-600 hover:bg-sky-700 text-white font-semibold py-2 px-6 rounded-lg shadow transition-colors">
Request Formal Quote
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-slate-900 text-slate-300 py-8 mt-12">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col md:flex-row justify-between items-center gap-4">
<div class="text-center md:text-left">
<h3 class="font-bold text-white text-lg">Skyline Facility Group</h3>
<p class="text-sm">Office Buildings • General Repairs • Painting • Fixtures • Polishing</p>
</div>
<div class="text-center md:text-right text-sm">
<p>© 2026 Skyline Facility Group. All rights reserved.</p>
<p>Owner: David Caneccio</p>
</div>
</div>
</footer>
<script>
// --- State Management ---
const state = {
companyName: '',
sqFt: 5000,
janitorial: { active: true, frequency: 3, supplies: false },
repairs: { active: false, hours: 5, painting: false, fixtures: false },
special: { active: false, marbleSqFt: 0, metalHours: 0 }
};
// --- Pricing Logic (Dummy Data for Demo) ---
const rates = {
cleaningPerSqFt: 0.05, // Monthly rate per sq ft per cleaning day
suppliesFlat: 150,
repairHourly: 85,
paintFlat: 200, // Monthly retainer for touchups
fixtureFlat: 150, // Per install retainer
marblePerSqFt: 1.50, // One-time amortized or monthly maintenance
metalHourly: 95
};
// --- DOM Elements ---
const inputs = {
name: document.getElementById('companyName'),
sqFt: document.getElementById('sqFt'),
// Janitorial
janActive: document.getElementById('janitorialActive'),
janFreq: document.getElementById('frequency'),
janSupplies: document.getElementById('supplies'),
janControls: document.getElementById('janitorialControls'),
janDisplay: document.getElementById('freqVal'),
// Repairs
repActive: document.getElementById('repairActive'),
repHours: document.getElementById('repairHours'),
repPaint: document.getElementById('painting'),
repFix: document.getElementById('fixtures'),
repControls: document.getElementById('repairControls'),
repDisplay: document.getElementById('hoursVal'),
// Special
specActive: document.getElementById('specialActive'),
specMarble: document.getElementById('marbleSqFt'),
specMetal: document.getElementById('metalHours'),
specControls: document.getElementById('specialControls'),
marbleDisplay: document.getElementById('marbleVal'),
metalDisplay: document.getElementById('metalVal')
};
const display = {
total: document.getElementById('totalCostDisplay'),
clientName: document.getElementById('clientNameDisplay'),
salutation: document.getElementById('clientSalutation'),
scopeList: document.getElementById('scopeList'),
date: document.getElementById('proposalDate')
};
// --- Chart Instances ---
let costChartInstance = null;
let serviceChartInstance = null;
// --- Initialization ---
function init() {
// Set Date
const today = new Date();
display.date.textContent = today.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
// Event Listeners
inputs.name.addEventListener('input', (e) => { state.companyName = e.target.value; updateUI(); });
inputs.sqFt.addEventListener('input', (e) => { state.sqFt = parseInt(e.target.value) || 0; updateUI(); });
// Janitorial Listeners
inputs.janActive.addEventListener('change', (e) => { toggleSection('janitorial', e.target.checked); updateUI(); });
inputs.janFreq.addEventListener('input', (e) => {
state.janitorial.frequency = parseInt(e.target.value);
inputs.janDisplay.textContent = state.janitorial.frequency;
updateUI();
});
inputs.janSupplies.addEventListener('change', (e) => { state.janitorial.supplies = e.target.checked; updateUI(); });
// Repair Listeners
inputs.repActive.addEventListener('change', (e) => { toggleSection('repairs', e.target.checked); updateUI(); });
inputs.repHours.addEventListener('input', (e) => {
state.repairs.hours = parseInt(e.target.value);
inputs.repDisplay.textContent = state.repairs.hours;
updateUI();
});
inputs.repPaint.addEventListener('change', (e) => { state.repairs.painting = e.target.checked; updateUI(); });
inputs.repFix.addEventListener('change', (e) => { state.repairs.fixtures = e.target.checked; updateUI(); });
// Special Listeners
inputs.specActive.addEventListener('change', (e) => { toggleSection('special', e.target.checked); updateUI(); });
inputs.specMarble.addEventListener('input', (e) => {
state.special.marbleSqFt = parseInt(e.target.value);
inputs.marbleDisplay.textContent = state.special.marbleSqFt;
updateUI();
});
inputs.specMetal.addEventListener('input', (e) => {
state.special.metalHours = parseInt(e.target.value);
inputs.metalDisplay.textContent = state.special.metalHours;
updateUI();
});
// Initial Draw
initCharts();
updateUI();
}
function toggleSection(section, isActive) {
state[section].active = isActive;
const el = inputs[section === 'janitorial' ? 'janControls' : section === 'repairs' ? 'repControls' : 'specControls'];
if (isActive) {
el.classList.remove('opacity-50', 'pointer-events-none');
} else {
el.classList.add('opacity-50', 'pointer-events-none');
}
}
// --- Calculation Logic ---
function calculateCosts() {
let cleaningCost = 0;
let repairCost = 0;
let specialCost = 0;
if (state.janitorial.active) {
// Base calculation: SqFt * Rate * Frequency Multiplier
cleaningCost = (state.sqFt * rates.cleaningPerSqFt * state.janitorial.frequency);
if (state.janitorial.supplies) cleaningCost += rates.suppliesFlat;
}
if (state.repairs.active) {
repairCost = (state.repairs.hours * rates.repairHourly);
if (state.repairs.painting) repairCost += rates.paintFlat;
if (state.repairs.fixtures) repairCost += rates.fixtureFlat;
}
if (state.special.active) {
// Marble assumes monthly maintenance cost for demo purposes
specialCost += (state.special.marbleSqFt * 0.25); // Maintenance rate
specialCost += (state.special.metalHours * rates.metalHourly);
}
return { cleaningCost, repairCost, specialCost, total: cleaningCost + repairCost + specialCost };
}
// --- UI Updates ---
function updateUI() {
const costs = calculateCosts();
// Text Updates
display.total.textContent = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(costs.total);
const cName = state.companyName.trim() || '[Client Company Name]';
display.clientName.textContent = cName;
display.salutation.textContent = state.companyName.trim() ? state.companyName.trim() : 'Valued Partner';
// List Update
let listHTML = '';
if (state.janitorial.active) {
listHTML += `<li class="font-medium text-slate-800">Office Cleaning Services</li>`;
listHTML += `<ul class="list-circle pl-4 mb-2 text-sm text-slate-600">
<li>Frequency: ${state.janitorial.frequency} days per week</li>
<li>Area: ${state.sqFt.toLocaleString()} sq. ft.</li>
${state.janitorial.supplies ? '<li>Includes Consumable Supply Restocking</li>' : ''}
</ul>`;
}
if (state.repairs.active) {
listHTML += `<li class="font-medium text-slate-800">General Repair & Maintenance</li>`;
listHTML += `<ul class="list-circle pl-4 mb-2 text-sm text-slate-600">
<li>Dedicated Hours: ${state.repairs.hours} hrs/month</li>
${state.repairs.painting ? '<li>Includes Periodic Painting Touch-ups</li>' : ''}
${state.repairs.fixtures ? '<li>Includes Basic Fixture Installation</li>' : ''}
</ul>`;
}
if (state.special.active) {
listHTML += `<li class="font-medium text-slate-800">Specialized Finishing</li>`;
listHTML += `<ul class="list-circle pl-4 mb-2 text-sm text-slate-600">
${state.special.marbleSqFt > 0 ? `<li>Marble Floor Polishing: ${state.special.marbleSqFt} sq. ft.</li>` : ''}
${state.special.metalHours > 0 ? `<li>Metal Polishing Service: ${state.special.metalHours} hrs/month</li>` : ''}
</ul>`;
}
if (!state.janitorial.active && !state.repairs.active && !state.special.active) {
listHTML = '<li class="text-slate-400 italic">No services selected. Please configure your proposal.</li>';
}
display.scopeList.innerHTML = listHTML;
// Chart Updates
updateCharts(costs);
}
// --- Chart.js Implementations ---
function initCharts() {
// Cost Distribution Donut
const ctxCost = document.getElementById('costChart').getContext('2d');
costChartInstance = new Chart(ctxCost, {
type: 'doughnut',
data: {
labels: ['Cleaning', 'Repairs', 'Specialized'],
datasets: [{
data: [1, 1, 1],
backgroundColor: ['#0ea5e9', '#64748b', '#e2e8f0'],
borderWidth: 0,
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'right', labels: { boxWidth: 12, font: { size: 11 } } },
tooltip: { callbacks: { label: (ctx) => ' ' + ctx.parsed.toLocaleString('en-US', {style:'currency', currency:'USD'}) } }
},
cutout: '70%'
}
});
// Service Intensity Bar
const ctxService = document.getElementById('serviceChart').getContext('2d');
serviceChartInstance = new Chart(ctxService, {
type: 'bar',
data: {
labels: ['Janitorial (Days)', 'Repair (Hrs)', 'Polish (Hrs)'],
datasets: [{
label: 'Service Intensity',
data: [0, 0, 0],
backgroundColor: ['#0ea5e9', '#64748b', '#94a3b8'],
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: { beginAtZero: true, grid: { color: '#f1f5f9' } },
x: { grid: { display: false } }
},
plugins: {
legend: { display: false }
}
}
});
}
function updateCharts(costs) {
// Update Cost Chart
if (costChartInstance) {
costChartInstance.data.datasets[0].data = [costs.cleaningCost, costs.repairCost, costs.specialCost];
// If all zero, show placeholder
if (costs.total === 0) costChartInstance.data.datasets[0].data = [1, 0, 0];
costChartInstance.update();
}
// Update Intensity Chart
if (serviceChartInstance) {
const janIntensity = state.janitorial.active ? state.janitorial.frequency : 0;
const repIntensity = state.repairs.active ? state.repairs.hours : 0;
const specIntensity = state.special.active ? state.special.metalHours + (state.special.marbleSqFt/200) : 0; // rough eq
serviceChartInstance.data.datasets[0].data = [janIntensity, repIntensity, specIntensity];
serviceChartInstance.update();
}
}
// Initialize App
init();
</script>
</body>
</html>