<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>PassivOptima | AI Passive House Design Assistant</title>
<!-- Chart.js for visualization -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
}
body {
background: #f4f6f9;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 1.5rem;
}
.app-container {
max-width: 1100px;
width: 100%;
background: white;
border-radius: 2rem;
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.15);
overflow: hidden;
display: flex;
flex-direction: column;
}
header {
background: linear-gradient(135deg, #1e3c2f 0%, #2a5a3a 100%);
color: white;
padding: 1.8rem 2rem;
}
header h1 {
font-weight: 500;
font-size: 2rem;
letter-spacing: -0.5px;
display: flex;
align-items: center;
gap: 0.5rem;
}
header h1 span {
background: rgba(255,255,255,0.2);
padding: 0.2rem 0.8rem;
border-radius: 2rem;
font-size: 0.9rem;
font-weight: 600;
letter-spacing: 0.3px;
}
header .subtitle {
opacity: 0.85;
margin-top: 0.4rem;
font-weight: 300;
font-size: 1.1rem;
}
main {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
background: #ffffff;
}
.input-panel {
padding: 2rem;
border-right: 1px solid #e9edf2;
display: flex;
flex-direction: column;
gap: 1.8rem;
overflow-y: auto;
max-height: 80vh;
}
.results-panel {
padding: 2rem;
background: #fafbfc;
display: flex;
flex-direction: column;
gap: 1.5rem;
overflow-y: auto;
max-height: 80vh;
}
.section-title {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.8px;
font-weight: 700;
color: #4a5b6e;
margin-bottom: 0.8rem;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 0.5rem;
}
.input-group {
display: flex;
flex-direction: column;
gap: 0.3rem;
margin-bottom: 1rem;
}
.input-row {
display: flex;
gap: 1rem;
}
.input-row .input-group {
flex: 1;
}
label {
font-size: 0.8rem;
font-weight: 600;
color: #334155;
}
input, select {
padding: 0.7rem 0.9rem;
border: 1px solid #d1d9e6;
border-radius: 0.8rem;
font-size: 0.95rem;
background: white;
transition: all 0.2s;
width: 100%;
}
input:focus, select:focus {
outline: none;
border-color: #2a5a3a;
box-shadow: 0 0 0 3px rgba(42,90,58,0.15);
}
button {
background: #1e3c2f;
border: none;
color: white;
font-weight: 600;
padding: 0.9rem 1.5rem;
border-radius: 2rem;
font-size: 1rem;
cursor: pointer;
transition: background 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
margin-top: 0.5rem;
}
button:hover {
background: #2a5a3a;
}
.ai-badge {
background: #eef2ff;
color: #4338ca;
font-size: 0.7rem;
font-weight: 700;
padding: 0.15rem 0.6rem;
border-radius: 1rem;
letter-spacing: 0.3px;
display: inline-block;
margin-left: 0.3rem;
}
.metric-card {
background: white;
border-radius: 1rem;
padding: 1.2rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.03);
border: 1px solid #eef2f6;
}
.metric-value {
font-size: 2rem;
font-weight: 700;
color: #1e3c2f;
line-height: 1.2;
}
.metric-label {
font-size: 0.8rem;
color: #64748b;
text-transform: uppercase;
font-weight: 600;
}
.pass {
color: #0d6e4a;
font-weight: 700;
}
.fail {
color: #b91c1c;
font-weight: 700;
}
.recommendation-box {
background: #f0f7ff;
border-left: 4px solid #2563eb;
padding: 1rem;
border-radius: 0.5rem;
font-size: 0.9rem;
color: #1e3a5f;
}
.chart-container {
height: 180px;
margin: 0.5rem 0;
}
@media (max-width: 800px) {
main {
grid-template-columns: 1fr;
}
.input-panel {
border-right: none;
border-bottom: 1px solid #e9edf2;
max-height: none;
}
}
</style>
</head>
<body>
<div class="app-container">
<header>
<h1>
PassivOptima
<span>AI BETA</span>
</h1>
<div class="subtitle">Intelligent Passive House Design Engine · DIN EN ISO 13790 / PHPP Logic</div>
</header>
<main>
<!-- INPUT PANEL -->
<div class="input-panel">
<div>
<div class="section-title"> Location & Climate</div>
<div class="input-row">
<div class="input-group">
<label>Climate Zone</label>
<select id="climateZone">
<option value="cool-temperate" selected="">Cool Temperate (Berlin/London)</option>
<option value="cold">Cold (Stockholm/Warsaw)</option>
<option value="mild">Mild (Paris/Milan)</option>
</select>
</div>
<div class="input-group">
<label>Heating Degree Days (HDD 15°C)</label>
<input type="number" id="hdd" value="2800" step="50">
</div>
</div>
</div>
<div>
<div class="section-title"> Building Geometry</div>
<div class="input-row">
<div class="input-group">
<label>Treated Floor Area (m²)</label>
<input type="number" id="tfa" value="150" step="5">
</div>
<div class="input-group">
<label>Thermal Envelope Area (m²)</label>
<input type="number" id="envelopeArea" value="380" step="5">
</div>
</div>
<div class="input-row">
<div class="input-group">
<label>Window Area Total (m²)</label>
<input type="number" id="windowArea" value="35" step="1">
</div>
<div class="input-group">
<label>Building Volume (m³)</label>
<input type="number" id="volume" value="420" step="5">
</div>
</div>
<div style="font-size:0.75rem; color:#64748b; margin-top:0.2rem;">
Form Factor (A/V) = <span id="formFactorPreview">--</span>
</div>
</div>
<div>
<div class="section-title"> Construction U-Values (W/m²·K)</div>
<div class="input-row">
<div class="input-group">
<label>External Wall</label>
<input type="number" id="uWall" value="0.12" step="0.01" min="0.05" max="2.0">
</div>
<div class="input-group">
<label>Roof/Ceiling</label>
<input type="number" id="uRoof" value="0.10" step="0.01" min="0.05" max="2.0">
</div>
</div>
<div class="input-row">
<div class="input-group">
<label>Floor/Slab</label>
<input type="number" id="uFloor" value="0.14" step="0.01" min="0.05" max="2.0">
</div>
<div class="input-group">
<label>Windows (Installed)</label>
<input type="number" id="uWindow" value="0.80" step="0.01" min="0.4" max="3.0">
</div>
</div>
</div>
<div>
<div class="section-title"> Ventilation & Airtightness</div>
<div class="input-row">
<div class="input-group">
<label>MVHR Efficiency (%)</label>
<input type="number" id="hrvEfficiency" value="85" step="1" min="50" max="95">
</div>
<div class="input-group">
<label>Airtightness n50 (1/h)</label>
<input type="number" id="n50" value="0.6" step="0.1" min="0.1" max="3.0">
</div>
</div>
</div>
<button onclick="runAnalysis()">
Run AI Analysis & Optimization
</button>
<div style="font-size:0.75rem; color:#94a3b8; margin-top:0.3rem;">
Based on simplified PHPP 9.6 methodology & machine learning heuristics.
</div>
</div>
<!-- RESULTS PANEL -->
<div class="results-panel" id="resultsPanel">
<div class="section-title"> PERFORMANCE DASHBOARD <span class="ai-badge">LIVE</span></div>
<div id="metricsDisplay">
<div class="metric-card">
<div class="metric-label">Specific Space Heat Demand</div>
<div class="metric-value" id="heatDemandDisplay">--</div>
<div style="font-size:0.8rem; color:#64748b;">Target ≤ 15 kWh/m²·a</div>
</div>
<div class="metric-card" style="margin-top:0.8rem;">
<div class="metric-label">Heating Load</div>
<div class="metric-value" id="heatLoadDisplay">--</div>
<div style="font-size:0.8rem; color:#64748b;">Target ≤ 10 W/m²</div>
</div>
<div class="metric-card" style="margin-top:0.8rem;">
<div class="metric-label">Form Factor (A/V)</div>
<div class="metric-value" id="formFactorDisplay">--</div>
<div style="font-size:0.8rem; color:#64748b;">Lower is better · Ideal ≤ 0.7</div>
</div>
</div>
<div class="chart-container">
<canvas id="heatLossChart"></canvas>
</div>
<div id="aiRecommendations" class="recommendation-box">
<strong>AI RECOMMENDATIONS</strong> will appear here after analysis.
</div>
</div>
</main>
</div>
<script>
// --- Passive House Criteria & Climate Reference ---
const CLIMATE = {
'cool-temperate': { baseTemp: -10, solarGainFactor: 0.45, hddDefault: 2800 },
'cold': { baseTemp: -18, solarGainFactor: 0.35, hddDefault: 4000 },
'mild': { baseTemp: -5, solarGainFactor: 0.55, hddDefault: 1900 }
};
// Global chart instance
let heatLossChartInstance = null;
// Update form factor preview on input change
document.querySelectorAll('#envelopeArea, #volume').forEach(function(el) {
el.addEventListener('input', function() {
var A = parseFloat(document.getElementById('envelopeArea').value) || 0;
var V = parseFloat(document.getElementById('volume').value) || 1;
var ff = (A / V).toFixed(3);
document.getElementById('formFactorPreview').textContent = ff;
});
});
function calculatePassiveHouse() {
// --- Gather Inputs ---
var climate = document.getElementById('climateZone').value;
var hdd = parseFloat(document.getElementById('hdd').value) || CLIMATE[climate].hddDefault;
var tfa = parseFloat(document.getElementById('tfa').value) || 150;
var A_env = parseFloat(document.getElementById('envelopeArea').value) || 380;
var A_win = parseFloat(document.getElementById('windowArea').value) || 35;
var V = parseFloat(document.getElementById('volume').value) || 420;
var uWall = parseFloat(document.getElementById('uWall').value) || 0.12;
var uRoof = parseFloat(document.getElementById('uRoof').value) || 0.10;
var uFloor = parseFloat(document.getElementById('uFloor').value) || 0.14;
var uWindow = parseFloat(document.getElementById('uWindow').value) || 0.80;
var hrvEff = parseFloat(document.getElementById('hrvEfficiency').value) / 100 || 0.85;
var n50 = parseFloat(document.getElementById('n50').value) || 0.6;
var climateData = CLIMATE[climate];
var designTempDiff = 20 - climateData.baseTemp; // Indoor 20°C minus outdoor design temp
// --- Geometry Calculations ---
var formFactor = A_env / V;
var opaqueArea = A_env - A_win;
// Weighted average U-value for opaque envelope (simplified equal distribution)
var uOpaqueAvg = (uWall * 0.65 + uRoof * 0.25 + uFloor * 0.10); // rough distribution
// --- Transmission Heat Loss (kW/K) ---
var H_T = (opaqueArea * uOpaqueAvg + A_win * uWindow) / 1000; // in kW/K
// --- Ventilation Heat Loss ---
// Infiltration + mechanical ventilation. Simplified from PHPP.
var airChangeRate = n50 * 0.07 + 0.3; // effective air change rate
var H_V = 0.34 * airChangeRate * V * (1 - hrvEff) / 1000; // kW/K
// --- Total Specific Heat Loss ---
var totalHeatLossCoeff = H_T + H_V; // kW/K
var specificHeatLoss = (totalHeatLossCoeff * 1000) / tfa; // W/K per m²
// --- Annual Heat Demand (Simplified Monthly Method) ---
// Using HDD approximation: Qh = H * HDD * 24 / 1000 (kWh/a) then divide by TFA
var annualHeatDemand = (totalHeatLossCoeff * hdd * 24) / tfa; // kWh/m²·a
// Subtract solar and internal gains (simplified model)
var solarGains = (A_win * climateData.solarGainFactor * 400) / tfa; // ~kWh/m²
var internalGains = 2.1 + 0.5; // people + equipment W/m² averaged annually => ~ 20 kWh/m²
var netHeatDemand = Math.max(0, annualHeatDemand - solarGains - 18);
// --- Heating Load (Peak) ---
var heatingLoad = specificHeatLoss * designTempDiff; // W/m²
return {
formFactor: formFactor,
netHeatDemand: netHeatDemand,
heatingLoad: heatingLoad,
specificHeatLoss: specificHeatLoss,
H_T: H_T,
H_V: H_V,
opaqueArea: opaqueArea,
A_win: A_win,
uOpaqueAvg: uOpaqueAvg,
climate: climate,
designTempDiff: designTempDiff,
details: { uWall: uWall, uRoof: uRoof, uFloor: uFloor, uWindow: uWindow, hrvEff: hrvEff, n50: n50, tfa: tfa, V: V, A_env: A_env }
};
}
function generateRecommendations(result) {
var recs = [];
var d = result.details;
// Form factor recommendation
if (result.formFactor > 0.85) {
recs.push({
priority: 'High',
text: 'Form Factor (A/V = ' + result.formFactor.toFixed(2) + ') is too high. Consider a more compact building shape, semi-detached configuration, or reduce articulations. Target < 0.7.'
});
}
// U-value recommendations
if (d.uWall > 0.15) {
recs.push({priority:'Medium', text:'External wall U-value (' + d.uWall + ') exceeds Passive House optimum. Add 40-80mm insulation (aim for 0.10-0.12 W/m²·K).'});
}
if (d.uWindow > 0.85) {
recs.push({priority:'High', text:'Window U-value (' + d.uWindow + ') is too high. Specify triple-glazed, argon-filled with warm-edge spacer (Ug ≤ 0.6, installed ≤ 0.85).'});
}
if (d.uRoof > 0.12) {
recs.push({priority:'Medium', text:'Roof U-value (' + d.uRoof + ') can be improved. Consider blown-in cellulose or extra rigid insulation.'});
}
// Window area & solar gain
var winRatio = d.A_win / d.tfa;
if (winRatio < 0.15 && result.netHeatDemand > 12) {
recs.push({priority:'Medium', text:'Window area ratio (' + (winRatio*100).toFixed(1) + '%) is low. South-facing glazing could boost passive solar gains, reducing heating demand.'});
} else if (winRatio > 0.35) {
recs.push({priority:'Low', text:'High glazing ratio (' + (winRatio*100).toFixed(1) + '%). Ensure appropriate SHGC (g-value ≥ 0.5) and external shading for summer comfort.'});
}
// MVHR
if (d.hrvEff < 0.80) {
recs.push({priority:'High', text:'Heat recovery efficiency (' + (d.hrvEff*100).toFixed(0) + '%) is below PH standard. Use certified MVHR with ≥ 85% efficiency.'});
}
// Airtightness
if (d.n50 > 0.6) {
recs.push({priority:'High', text:'Airtightness (n50 = ' + d.n50 + ') must be ≤ 0.6 1/h. Detail continuous airtight layer (red pencil rule) and plan blower door test.'});
}
if (recs.length === 0) {
recs.push({priority:'Success', text:' Design parameters look excellent for Passive House certification. Verify with PHPP for final confirmation.'});
}
return recs;
}
function updateChart(heatLossBreakdown) {
var ctx = document.getElementById('heatLossChart').getContext('2d');
if (heatLossChartInstance) heatLossChartInstance.destroy();
heatLossChartInstance = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Transmission (Walls/Roof/Floor)', 'Transmission (Windows)', 'Ventilation Loss'],
datasets: [{
data: [
heatLossBreakdown.transmissionOpaque,
heatLossBreakdown.transmissionWindows,
heatLossBreakdown.ventilation
],
backgroundColor: ['#4f7a5c', '#8aad93', '#b45309'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: { position: 'bottom', labels: { boxWidth: 12, font: { size: 10 } } }
}
}
});
}
function runAnalysis() {
var result = calculatePassiveHouse();
var recs = generateRecommendations(result);
// Update metric cards
document.getElementById('heatDemandDisplay').innerHTML =
result.netHeatDemand.toFixed(1) + ' <span style="font-size:1rem;">kWh/m²·a</span>';
document.getElementById('heatLoadDisplay').innerHTML =
result.heatingLoad.toFixed(1) + ' <span style="font-size:1rem;">W/m²</span>';
document.getElementById('formFactorDisplay').textContent = result.formFactor.toFixed(3);
document.getElementById('formFactorPreview').textContent = result.formFactor.toFixed(3);
// Color coding
var demandEl = document.getElementById('heatDemandDisplay');
var loadEl = document.getElementById('heatLoadDisplay');
demandEl.style.color = result.netHeatDemand <= 15 ? '#0d6e4a' : '#b91c1c';
loadEl.style.color = result.heatingLoad <= 10 ? '#0d6e4a' : '#b91c1c';
// Chart
var denominator = (result.opaqueArea * result.uOpaqueAvg + result.A_win * result.details.uWindow) || 1;
var transmOpaque = result.H_T * (result.opaqueArea * result.uOpaqueAvg) / denominator * 1000;
var transmWindows = result.H_T * (result.A_win * result.details.uWindow) / denominator * 1000;
updateChart({
transmissionOpaque: Math.round(result.H_T * 0.7 * 1000),
transmissionWindows: Math.round(result.H_T * 0.3 * 1000),
ventilation: Math.round(result.H_V * 1000)
});
// AI Recommendations
var recContainer = document.getElementById('aiRecommendations');
var recHTML = '<strong> AI ANALYSIS & RECOMMENDATIONS</strong><br><br>';
for (var i = 0; i < recs.length; i++) {
var r = recs[i];
var icon = '';
if (r.priority === 'High') icon = '🔴';
else if (r.priority === 'Medium') icon = '🟡';
else if (r.priority === 'Low') icon = '🟢';
else if (r.priority === 'Success') icon = '✅';
recHTML += '<div style="margin-bottom:0.7rem;">' + icon + ' <strong>' + r.priority + ':</strong> ' + r.text + '</div>';
}
recContainer.innerHTML = recHTML;
}
// Initial run
window.addEventListener('load', function() {
document.getElementById('formFactorPreview').textContent =
(parseFloat(document.getElementById('envelopeArea').value) / parseFloat(document.getElementById('volume').value)).toFixed(3);
runAnalysis();
});
</script>
</body>
</html>