---
title: "Citrus Irrigation Calculator"
format:
html:
page-layout: full
toc: false
echo: false
execute:
echo: false
---
```{=html}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;0,9..144,700;1,9..144,400&family=DM+Mono:wght@400;500&family=Inter:wght@400;500;600&display=swap" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
/* โโ BRAND TOKENS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
:root{
--mint: #33FFA2;
--magenta: #FF33FF;
--grey: #737373;
/* surfaces */
--bg: #F7F7F5;
--surface: #FFFFFF;
--border: #E5E5E5;
--border-strong: #D0D0D0;
/* text */
--ink: #1A1A1A;
--ink-2: #3D3D3D;
--ink-3: #737373;
/* accents (brand) */
--accent: #33FFA2;
--accent-dark: #00CC7A;
--accent-2: #FF33FF;
--accent-2-dark:#CC00CC;
--radius: 10px;
--shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 16px rgba(0,0,0,0.04);
}
/* โโ BASE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
body{background:var(--bg);color:var(--ink);font-family:'Inter',sans-serif;min-height:100vh;-webkit-font-smoothing:antialiased}
/* โโ LAYOUT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.app-wrap{max-width:1080px;margin:0 auto;padding:0 1.5rem 5rem}
/* โโ TOP BAR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.top-bar{
border-bottom:1px solid var(--border);
padding:1rem 0;
display:flex;
align-items:center;
justify-content:space-between;
margin-bottom:3.5rem;
}
.top-bar-left{display:flex;align-items:center;gap:.75rem}
.top-bar-logo{font-family:'Fraunces',serif;font-size:1rem;font-weight:600;color:var(--ink);letter-spacing:-.01em}
.top-bar-divider{width:1px;height:18px;background:var(--border-strong)}
.top-bar-label{font-family:'DM Mono',monospace;font-size:.65rem;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
.sdg-strip{display:flex;align-items:center;gap:.5rem}
/* โโ SDG BADGES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.sdg-badge{
display:inline-flex;align-items:center;gap:.3rem;
padding:.25rem .55rem;border-radius:4px;
font-family:'DM Mono',monospace;font-size:.6rem;letter-spacing:.06em;
font-weight:500;text-transform:uppercase;white-space:nowrap;
border:1px solid transparent;
}
.sdg-2 {background:#D4A017;color:#fff}
.sdg-6 {background:#26BDE2;color:#fff}
.sdg-12{background:#BF8B2E;color:#fff}
.sdg-13{background:#3F7E44;color:#fff}
.sdg-15{background:#56C02B;color:#fff}
/* SDG icon SVGs */
.sdg-icon{width:28px;height:28px;border-radius:3px;flex-shrink:0}
/* โโ HEADER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.app-header{
text-align:center;
padding:3rem 0 3.5rem;
animation:fadeDown .5s ease both;
}
.header-eyebrow{
display:inline-block;
font-family:'DM Mono',monospace;font-size:.65rem;
letter-spacing:.2em;text-transform:uppercase;
color:var(--ink-3);margin-bottom:1.2rem;
}
.app-header h1{
font-family:'Fraunces',serif;
font-size:clamp(2.2rem,5vw,3.8rem);
font-weight:700;line-height:1.05;
color:var(--ink);margin-bottom:1rem;
letter-spacing:-.02em;
}
.app-header h1 em{
font-style:italic;font-weight:300;
background:linear-gradient(90deg,var(--mint),var(--magenta));
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
background-clip:text;
}
.app-header p{
color:var(--ink-3);font-size:.95rem;
max-width:500px;margin:0 auto;line-height:1.75;
}
/* โโ SECTION DIVIDER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.section-rule{
display:flex;align-items:center;gap:1rem;
margin-bottom:1.2rem;
}
.section-rule-label{
font-family:'DM Mono',monospace;font-size:.62rem;
letter-spacing:.16em;text-transform:uppercase;
color:var(--ink-3);white-space:nowrap;
}
.section-rule-line{flex:1;height:1px;background:var(--border)}
/* โโ GRID โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.main-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.25rem;align-items:start}
@media(max-width:720px){.main-grid{grid-template-columns:1fr}}
/* โโ CARD โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.card{
background:var(--surface);
border:1px solid var(--border);
border-radius:var(--radius);
padding:1.75rem;
box-shadow:var(--shadow);
animation:fadeUp .5s ease both;
position:relative;
overflow:hidden;
}
/* thin top accent rule */
.card::before{
content:'';position:absolute;top:0;left:0;right:0;height:2px;
background:linear-gradient(90deg,var(--mint),transparent);
}
.card.fao-card::before{background:linear-gradient(90deg,var(--magenta),transparent)}
.card.results-card::before{background:linear-gradient(90deg,var(--mint),var(--magenta))}
.card-title{
font-family:'DM Mono',monospace;font-size:.62rem;
letter-spacing:.18em;text-transform:uppercase;
color:var(--ink-3);margin-bottom:1.5rem;
}
/* โโ FORM ELEMENTS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:.875rem;margin-bottom:.875rem}
.form-row.full{grid-template-columns:1fr}
.field label{
display:block;font-size:.7rem;letter-spacing:.05em;
color:var(--ink-3);text-transform:uppercase;margin-bottom:.35rem;
font-family:'DM Mono',monospace;
}
.field input,.field select{
width:100%;background:var(--bg);
border:1px solid var(--border-strong);border-radius:6px;
color:var(--ink);font-family:'DM Mono',monospace;font-size:.88rem;
padding:.55rem .8rem;transition:border-color .15s;appearance:none;
}
.field input:focus,.field select:focus{
outline:none;border-color:var(--accent-dark);
box-shadow:0 0 0 3px rgba(51,255,162,.12);
}
.field select option{background:var(--surface)}
.field input[type="range"]{
padding:0;height:3px;background:var(--border);
border:none;cursor:pointer;accent-color:var(--accent-dark);
border-radius:100px;
}
.range-row{display:flex;align-items:center;gap:.75rem}
.range-val{
font-family:'DM Mono',monospace;font-size:.82rem;
color:var(--ink);min-width:3.2rem;text-align:right;font-weight:500;
}
/* โโ PERIOD TABS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.period-tabs{display:flex;gap:.375rem;margin-bottom:1.5rem}
.tab-btn{
flex:1;padding:.5rem .4rem;
background:var(--bg);border:1px solid var(--border-strong);
border-radius:6px;color:var(--ink-3);
font-family:'DM Mono',monospace;font-size:.75rem;
font-weight:500;cursor:pointer;transition:all .15s;
}
.tab-btn.active{
background:var(--ink);border-color:var(--ink);color:#fff;
}
.tab-btn:hover:not(.active){border-color:var(--ink-2);color:var(--ink)}
/* โโ PRESET PILLS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.presets-row{display:flex;gap:.375rem;flex-wrap:wrap;margin-bottom:1.5rem}
.preset-lbl{font-family:'DM Mono',monospace;font-size:.62rem;letter-spacing:.08em;color:var(--ink-3);align-self:center;text-transform:uppercase}
.preset-btn{
padding:.3rem .75rem;background:var(--surface);
border:1px solid var(--border-strong);border-radius:100px;
color:var(--ink-2);font-family:'DM Mono',monospace;
font-size:.68rem;cursor:pointer;transition:all .15s;
}
.preset-btn:hover{background:var(--mint);border-color:var(--accent-dark);color:var(--ink)}
/* โโ SEPARATOR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.sep{height:1px;background:var(--border);margin:1.25rem 0}
/* โโ CALC BUTTON โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.calc-btn{
width:100%;padding:.875rem;margin-top:1.25rem;
background:var(--ink);border:none;border-radius:8px;
color:#fff;font-family:'Inter',sans-serif;
font-size:.9rem;font-weight:600;letter-spacing:.02em;
cursor:pointer;transition:all .2s;
}
.calc-btn:hover{background:var(--ink-2);transform:translateY(-1px);box-shadow:0 4px 16px rgba(0,0,0,.12)}
.calc-btn:active{transform:translateY(0)}
/* โโ RESULTS CARD โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.results-card{grid-column:1/-1;animation:fadeUp .5s ease .15s both}
.results-hidden{display:none}
/* โโ VALIDATION BANNER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.validation{border-radius:8px;padding:.875rem 1rem;font-size:.8rem;line-height:1.65;margin-bottom:1.5rem;font-family:'Inter',sans-serif}
.validation.ok{background:#F0FFF8;border:1px solid #A3F5CF;color:#0A5C36}
.validation.ok strong{color:#006633}
.validation.low,.validation.high{background:#FFFBF0;border:1px solid #FFD980;color:#7A4F00}
.validation.low strong,.validation.high strong{color:#8B5A00}
/* โโ DUAL TRACK โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.dual-track{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1.5rem}
@media(max-width:600px){.dual-track{grid-template-columns:1fr}}
.track{border-radius:8px;padding:1.4rem;border:1px solid var(--border)}
.track-ml {background:#F3FFFB;border-left:3px solid var(--mint)}
.track-fao{background:#FFF3FF;border-left:3px solid var(--magenta)}
.track-label{
font-family:'DM Mono',monospace;font-size:.58rem;
letter-spacing:.18em;text-transform:uppercase;margin-bottom:.6rem;
}
.track-ml .track-label{color:var(--accent-dark)}
.track-fao .track-label{color:var(--accent-2-dark)}
.track-main{
font-family:'Fraunces',serif;
font-size:clamp(1.8rem,3vw,2.6rem);
line-height:1;margin-bottom:.2rem;font-weight:600;
}
.track-ml .track-main{color:var(--accent-dark)}
.track-fao .track-main{color:var(--accent-2-dark)}
.track-sub{font-family:'DM Mono',monospace;font-size:.72rem;color:var(--ink-3)}
.track-detail{margin-top:.75rem;font-size:.76rem;color:var(--ink-3);line-height:1.65}
.track-detail strong{color:var(--ink)}
/* โโ METRICS ROW โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.metrics-row{display:grid;grid-template-columns:repeat(4,1fr);gap:.875rem;margin-bottom:1.5rem}
@media(max-width:700px){.metrics-row{grid-template-columns:repeat(2,1fr)}}
.metric{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:.875rem;text-align:center}
.metric-value{
font-family:'Fraunces',serif;font-size:1.5rem;font-weight:600;
color:var(--ink);display:block;line-height:1;margin-bottom:.3rem;
}
.metric-label{font-family:'DM Mono',monospace;font-size:.58rem;letter-spacing:.1em;color:var(--ink-3);text-transform:uppercase}
/* โโ SAVINGS BLOCK โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.savings-block{
background:var(--bg);border:1px solid var(--border);
border-radius:8px;padding:1.1rem 1.3rem;
display:flex;align-items:center;gap:1.25rem;flex-wrap:wrap;
margin-bottom:1rem;
}
.savings-pct{
font-family:'Fraunces',serif;font-size:2.2rem;
font-weight:700;color:var(--ink);white-space:nowrap;
}
.savings-bar-wrap{flex:1;min-width:160px}
.savings-bar-label{font-size:.72rem;color:var(--ink-3);margin-bottom:.4rem;font-family:'DM Mono',monospace;letter-spacing:.06em}
.savings-bar-track{height:4px;background:var(--border);border-radius:100px;overflow:hidden}
.savings-bar-fill{
height:100%;
background:linear-gradient(90deg,var(--mint),var(--magenta));
border-radius:100px;
transition:width .8s cubic-bezier(.16,1,.3,1);
}
.savings-text{font-size:.8rem;color:var(--ink-2);line-height:1.6;min-width:180px}
/* โโ EXTRA PILLS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.extra-row{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-top:1rem}
@media(max-width:600px){.extra-row{grid-template-columns:1fr}}
.extra-pill{
border-radius:8px;padding:.875rem 1.1rem;
display:flex;align-items:center;gap:.75rem;
border:1px solid var(--border);background:var(--bg);
}
.pill-icon-box{
width:32px;height:32px;border-radius:6px;
display:flex;align-items:center;justify-content:center;
flex-shrink:0;
}
.pill-cost .pill-icon-box{background:rgba(51,255,162,.15)}
.pill-co2 .pill-icon-box{background:rgba(255,51,255,.1)}
.pill-value{font-family:'Fraunces',serif;font-size:1.25rem;font-weight:600;color:var(--ink);display:block;line-height:1}
.pill-desc{font-family:'DM Mono',monospace;font-size:.62rem;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3)}
/* โโ MODEL INFO BOX โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.model-info{
background:var(--bg);border:1px solid var(--border);
border-radius:8px;padding:1rem 1.1rem;
font-size:.78rem;color:var(--ink-3);line-height:1.75;
margin-bottom:1.25rem;
}
.model-info strong{color:var(--ink);font-weight:600}
/* โโ PDF BUTTON โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.pdf-btn{
width:100%;padding:.8rem;margin-top:1.25rem;
background:var(--surface);
border:1px solid var(--border-strong);
border-radius:8px;color:var(--ink-2);
font-family:'DM Mono',monospace;font-size:.78rem;
font-weight:500;letter-spacing:.06em;cursor:pointer;
transition:all .18s;text-transform:uppercase;
display:flex;align-items:center;justify-content:center;gap:.5rem;
}
.pdf-btn:hover{background:var(--ink);color:#fff;border-color:var(--ink)}
.pdf-btn:disabled{opacity:.4;cursor:wait}
/* โโ SDG FOOTER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.sdg-footer{
margin-top:3.5rem;
padding-top:1.5rem;
border-top:1px solid var(--border);
display:flex;align-items:center;justify-content:space-between;
flex-wrap:wrap;gap:1rem;
}
.sdg-footer-left{
font-family:'DM Mono',monospace;font-size:.62rem;
letter-spacing:.1em;text-transform:uppercase;color:var(--ink-3);
}
.sdg-icons-row{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}
/* โโ ANIMATIONS โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
@keyframes fadeUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}
@keyframes fadeDown{from{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}
/* โโ DISCLAIMER โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
.disclaimer{
text-align:center;font-family:'DM Mono',monospace;
font-size:.6rem;color:var(--ink-3);margin-top:1rem;
letter-spacing:.06em;line-height:1.8;
}
</style>
</head>
<body>
<div class="app-wrap">
<!-- TOP BAR -->
<nav class="top-bar">
<div class="top-bar-left">
<span class="top-bar-logo">Danki Studio</span>
<div class="top-bar-divider"></div>
<span class="top-bar-label">Citrus DSS · v1.0</span>
</div>
<div class="sdg-strip">
<!-- SDG 2 Zero Hunger -->
<svg class="sdg-icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" title="SDG 2 Zero Hunger">
<rect width="100" height="100" fill="#DDA63A"/>
<text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">2</text>
<text x="50" y="58" font-family="Arial" font-size="9" fill="white" text-anchor="middle">ZERO</text>
<text x="50" y="70" font-family="Arial" font-size="9" fill="white" text-anchor="middle">HUNGER</text>
</svg>
<!-- SDG 6 Clean Water -->
<svg class="sdg-icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" title="SDG 6 Clean Water">
<rect width="100" height="100" fill="#26BDE2"/>
<text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">6</text>
<text x="50" y="58" font-family="Arial" font-size="9" fill="white" text-anchor="middle">CLEAN WATER</text>
<text x="50" y="70" font-family="Arial" font-size="9" fill="white" text-anchor="middle">& SANITATION</text>
</svg>
<!-- SDG 12 Responsible Consumption -->
<svg class="sdg-icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" title="SDG 12 Responsible Consumption">
<rect width="100" height="100" fill="#BF8B2E"/>
<text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">12</text>
<text x="50" y="57" font-family="Arial" font-size="8" fill="white" text-anchor="middle">RESPONSIBLE</text>
<text x="50" y="68" font-family="Arial" font-size="8" fill="white" text-anchor="middle">CONSUMPTION</text>
</svg>
<!-- SDG 13 Climate Action -->
<svg class="sdg-icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" title="SDG 13 Climate Action">
<rect width="100" height="100" fill="#3F7E44"/>
<text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">13</text>
<text x="50" y="58" font-family="Arial" font-size="9" fill="white" text-anchor="middle">CLIMATE</text>
<text x="50" y="70" font-family="Arial" font-size="9" fill="white" text-anchor="middle">ACTION</text>
</svg>
<!-- SDG 15 Life on Land -->
<svg class="sdg-icon" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" title="SDG 15 Life on Land">
<rect width="100" height="100" fill="#56C02B"/>
<text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">15</text>
<text x="50" y="58" font-family="Arial" font-size="9" fill="white" text-anchor="middle">LIFE</text>
<text x="50" y="70" font-family="Arial" font-size="9" fill="white" text-anchor="middle">ON LAND</text>
</svg>
</div>
</nav>
<!-- HEADER -->
<header class="app-header">
<div class="header-eyebrow">ML × FAO-56 · Irrigation Decision Support</div>
<h1>Citrus Irrigation<br/><em>Calculator</em></h1>
<p>Input your orchard and climate parameters to receive ML-optimised irrigation volumes alongside the FAO-56 classical baseline — in litres, cubic metres, cost and CO².</p>
</header>
<div class="main-grid">
<!-- INPUT PANEL -->
<div class="card">
<div class="card-title">01 — Orchard Parameters</div>
<div class="presets-row">
<span class="preset-lbl">Scenarios:</span>
<button class="preset-btn" onclick="loadPreset('hot')">Heat Wave</button>
<button class="preset-btn" onclick="loadPreset('mild')">Mild Season</button>
<button class="preset-btn" onclick="loadPreset('fruit')">Peak Fruiting</button>
<button class="preset-btn" onclick="loadPreset('rain')">After Rain</button>
</div>
<div class="form-row">
<div class="field"><label>Trees / hectare</label><input type="number" id="trees" value="400" min="1" max="2000" step="10"/></div>
<div class="field"><label>Tree age (years)</label><input type="number" id="age" value="8" min="1" max="40"/></div>
</div>
<div class="form-row">
<div class="field"><label>Growth stage</label>
<select id="stage">
<option value="vegetative">Vegetative</option>
<option value="flowering">Flowering</option>
<option value="fruiting" selected>Fruiting</option>
</select>
</div>
<div class="field"><label>Soil type</label>
<select id="soil">
<option value="sandy" selected>Sandy</option>
<option value="loam">Loam</option>
<option value="clay">Clay</option>
</select>
</div>
</div>
<div class="sep"></div>
<div class="form-row full"><div class="field"><label>Temperature (°C)</label>
<div class="range-row"><input type="range" id="temp" min="10" max="48" value="32" oninput="document.getElementById('tempVal').textContent=this.value+'ยฐC'"/><span class="range-val" id="tempVal">32ยฐC</span></div>
</div></div>
<div class="form-row full"><div class="field"><label>Humidity (%)</label>
<div class="range-row"><input type="range" id="hum" min="10" max="95" value="45" oninput="document.getElementById('humVal').textContent=this.value+'%'"/><span class="range-val" id="humVal">45%</span></div>
</div></div>
<div class="form-row full"><div class="field"><label>Wind speed (km/h)</label>
<div class="range-row"><input type="range" id="wind" min="0" max="60" value="12" oninput="document.getElementById('windVal').textContent=this.value+' km/h'"/><span class="range-val" id="windVal">12 km/h</span></div>
</div></div>
<div class="form-row full"><div class="field"><label>Rainfall (mm/day)</label>
<div class="range-row"><input type="range" id="rain" min="0" max="50" value="0" oninput="document.getElementById('rainVal').textContent=this.value+' mm'"/><span class="range-val" id="rainVal">0 mm</span></div>
</div></div>
</div>
<!-- OPTIONS PANEL -->
<div class="card fao-card" style="animation-delay:.08s">
<div class="card-title">02 — Output Configuration</div>
<div class="form-row full"><div class="field"><label>Planning period</label>
<div class="period-tabs" id="periodTabs">
<button class="tab-btn" data-p="1" onclick="setPeriod(this)">Daily</button>
<button class="tab-btn active" data-p="7" onclick="setPeriod(this)">Weekly</button>
<button class="tab-btn" data-p="30" onclick="setPeriod(this)">Monthly</button>
</div>
</div></div>
<div class="form-row">
<div class="field"><label>Water cost (€/m³)</label><input type="number" id="waterCost" value="0.15" min="0.01" max="5" step="0.01"/></div>
<div class="field"><label>Energy (kWh/m³)</label><input type="number" id="energyKwh" value="0.40" min="0.05" max="3" step="0.05"/></div>
</div>
<div class="form-row">
<div class="field"><label>CO&sub2; factor (kg/kWh)</label><input type="number" id="co2factor" value="0.233" min="0.05" max="1" step="0.01"/></div>
<div class="field"><label>Hectares</label><input type="number" id="hectares" value="1" min="0.1" max="1000" step="0.1"/></div>
</div>
<div class="sep"></div>
<div class="model-info">
<strong>ML estimate</strong> — XGBoost regression trained on 2,000 citrus observations. Water demand scaled by age-calibrated canopy area via logistic growth curve.<br/><br/>
<strong>FAO-56 baseline</strong> — Classical Penman-Monteith ET&sub0; × Kc × Ks. Stage coefficients: vegetative 0.65, flowering 0.85, fruiting 1.00.
</div>
<button class="calc-btn" onclick="calculate()">Calculate Irrigation Requirements</button>
</div>
<!-- RESULTS PANEL -->
<div class="card results-card results-hidden" id="resultsCard" style="animation-delay:.15s">
<div class="card-title">03 — Results — <span id="periodLabel">Weekly</span> Estimate</div>
<div id="validationMsg" class="validation ok" style="display:none"></div>
<div class="dual-track">
<div class="track track-ml">
<div class="track-label">ML Model Prediction</div>
<div class="track-main" id="mlMain">—</div>
<div class="track-sub">L / tree / day</div>
<div class="track-detail" id="mlDetail"></div>
</div>
<div class="track track-fao">
<div class="track-label">FAO-56 Classical Baseline</div>
<div class="track-main" id="faoMain">—</div>
<div class="track-sub">L / tree / day</div>
<div class="track-detail" id="faoDetail"></div>
</div>
</div>
<div class="metrics-row">
<div class="metric"><span class="metric-value" id="mTotalL">—</span><span class="metric-label">ML L /<span id="mLabelL">wk</span></span></div>
<div class="metric"><span class="metric-value" id="mTotalM3">—</span><span class="metric-label">ML m³/<span id="mLabelM">wk</span></span></div>
<div class="metric"><span class="metric-value" id="mFaoM3">—</span><span class="metric-label">FAO m³/<span id="mLabelF">wk</span></span></div>
<div class="metric"><span class="metric-value" id="mSavedM3">—</span><span class="metric-label">Saved m³/<span id="mLabelS">wk</span></span></div>
</div>
<div class="savings-block">
<div class="savings-pct" id="savingsPct">—%</div>
<div class="savings-bar-wrap">
<div class="savings-bar-label">Efficiency vs FAO-56 baseline</div>
<div class="savings-bar-track"><div class="savings-bar-fill" id="savingsBar" style="width:0%"></div></div>
</div>
<div class="savings-text" id="savingsText"></div>
</div>
<div class="extra-row">
<div class="extra-pill pill-cost">
<div class="pill-icon-box">
<!-- Euro / cost icon -->
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#00CC7A" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M14.5 8.5A4 4 0 0 0 8 12a4 4 0 0 0 6.5 3.1"/><line x1="7" y1="11" x2="13" y2="11"/><line x1="7" y1="13" x2="13" y2="13"/></svg>
</div>
<div class="pill-data"><span class="pill-value" id="costVal">—</span><span class="pill-desc">Cost saved (€/<span id="costLabel">wk</span>/ha)</span></div>
</div>
<div class="extra-pill pill-co2">
<div class="pill-icon-box">
<!-- Leaf / CO2 icon -->
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#CC00CC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22V12"/><path d="M5 12C5 7 9 3 12 3c3 0 7 4 7 9-2-1-4-1-7-1s-5 0-7 1z"/></svg>
</div>
<div class="pill-data"><span class="pill-value" id="co2Val">—</span><span class="pill-desc">CO&sub2; saved (kg/<span id="co2Label">wk</span>/ha)</span></div>
</div>
</div>
<button class="pdf-btn" id="pdfBtn" onclick="downloadPDF()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
Download Simulation Report (PDF)
</button>
</div>
</div><!-- /main-grid -->
<!-- SDG FOOTER -->
<footer class="sdg-footer">
<div class="sdg-footer-left">
Aligned with UN Sustainable Development Goals
</div>
<div class="sdg-icons-row">
<span style="font-family:'DM Mono',monospace;font-size:.6rem;color:var(--ink-3);letter-spacing:.08em">SDG</span>
<svg class="sdg-icon" viewBox="0 0 100 100"><rect width="100" height="100" fill="#DDA63A"/><text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">2</text><text x="50" y="58" font-family="Arial" font-size="9" fill="white" text-anchor="middle">ZERO</text><text x="50" y="70" font-family="Arial" font-size="9" fill="white" text-anchor="middle">HUNGER</text></svg>
<svg class="sdg-icon" viewBox="0 0 100 100"><rect width="100" height="100" fill="#26BDE2"/><text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">6</text><text x="50" y="58" font-family="Arial" font-size="9" fill="white" text-anchor="middle">CLEAN WATER</text><text x="50" y="70" font-family="Arial" font-size="9" fill="white" text-anchor="middle">& SANITATION</text></svg>
<svg class="sdg-icon" viewBox="0 0 100 100"><rect width="100" height="100" fill="#BF8B2E"/><text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">12</text><text x="50" y="57" font-family="Arial" font-size="8" fill="white" text-anchor="middle">RESPONSIBLE</text><text x="50" y="68" font-family="Arial" font-size="8" fill="white" text-anchor="middle">CONSUMPTION</text></svg>
<svg class="sdg-icon" viewBox="0 0 100 100"><rect width="100" height="100" fill="#3F7E44"/><text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">13</text><text x="50" y="58" font-family="Arial" font-size="9" fill="white" text-anchor="middle">CLIMATE</text><text x="50" y="70" font-family="Arial" font-size="9" fill="white" text-anchor="middle">ACTION</text></svg>
<svg class="sdg-icon" viewBox="0 0 100 100"><rect width="100" height="100" fill="#56C02B"/><text x="50" y="38" font-family="Arial" font-size="28" font-weight="bold" fill="white" text-anchor="middle">15</text><text x="50" y="58" font-family="Arial" font-size="9" fill="white" text-anchor="middle">LIFE</text><text x="50" y="70" font-family="Arial" font-size="9" fill="white" text-anchor="middle">ON LAND</text></svg>
</div>
</footer>
<p class="disclaimer">
Citrus Water Supply Project · Nambona Adeline YANGUERE · <a href="https://dankistudio.com" style="color:var(--ink-3)">Danki Studio</a><br/>
Approximated ML model and FAO-56 Penman-Monteith formula. Not a substitute for field agronomic advice.
</p>
</div><!-- /app-wrap -->
<script>
const KC = {vegetative:0.65, flowering:0.85, fruiting:1.00};
const KS = {sandy:1.20, loam:1.00, clay:0.85};
function canopyArea(age) {
// Logistic curve: age1=~1m2, age3=~3m2, age8=~13m2, age15+=~24m2
return 25 / (1 + Math.exp(-0.45 * (age - 7)));
}
function mlPredict(T, H, W, R, age, stage, soil) {
const Tmax = T+8, Tmin = T-8;
const Ra = 7.5 + 0.18*T;
let ET0 = 0.0023*(T+17.8)*Math.sqrt(Math.max(Tmax-Tmin,1))*Ra;
ET0 = Math.max(ET0, 0);
const humidFactor = 1 - 0.005*(H-50) - 0.00012*Math.pow(H-50,2);
const windFactor = 1 + (W>15 ? 0.008*(W-15)+0.0002*Math.pow(W-15,1.5) : 0.003*W);
const stageSoil = KC[stage]*KS[soil];
const tempBoost = T>35 ? 1+0.04*(T-35) : 1.0;
const canopy = canopyArea(age);
let mlL = ET0*humidFactor*windFactor*stageSoil*tempBoost*canopy;
const Peff = Math.min(R*0.80, mlL*0.6);
mlL = Math.max(mlL-Peff, 0.5);
return Math.round(mlL*10)/10;
}
function faoPredict(T, H, W, R, age, stage, soil) {
const es = 0.6108*Math.exp((17.27*T)/(T+237.3));
const ea = es*(H/100);
const VPD = Math.max(es-ea, 0);
const Rn = Math.max(0.6*T-4, 0);
const delta = 4098*es/Math.pow(T+237.3,2);
const gamma = 0.0665;
const u2 = W*0.278;
const ET0 = Math.max(
(0.408*delta*Rn + gamma*(900/(T+273))*u2*VPD) / (delta+gamma*(1+0.34*u2)),
0.1
);
const ETc = ET0*KC[stage]*KS[soil];
const Peff = Math.min(R*0.80, ETc*0.6);
const IrrigMm = Math.max(ETc-Peff, 0.1);
const canopy = canopyArea(age);
return Math.round(IrrigMm*canopy*10)/10;
}
let period = 7;
function setPeriod(btn) {
document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
period = parseInt(btn.dataset.p);
}
const presets = {
hot: {temp:42,hum:25,wind:22,rain:0, stage:'fruiting', soil:'sandy',trees:400,age:10},
mild: {temp:22,hum:65,wind:8, rain:2, stage:'vegetative', soil:'loam', trees:350,age:6},
fruit:{temp:32,hum:45,wind:12,rain:0, stage:'fruiting', soil:'sandy',trees:400,age:8},
rain: {temp:24,hum:82,wind:6, rain:20,stage:'flowering', soil:'clay', trees:300,age:12},
};
function loadPreset(k) {
const p=presets[k];
document.getElementById('temp').value =p.temp;
document.getElementById('hum').value =p.hum;
document.getElementById('wind').value =p.wind;
document.getElementById('rain').value =p.rain;
document.getElementById('stage').value=p.stage;
document.getElementById('soil').value =p.soil;
document.getElementById('trees').value=p.trees;
document.getElementById('age').value =p.age;
document.getElementById('tempVal').textContent=p.temp+'ยฐC';
document.getElementById('humVal').textContent =p.hum+'%';
document.getElementById('windVal').textContent=p.wind+' km/h';
document.getElementById('rainVal').textContent=p.rain+' mm';
}
function fmt(n,d){return n.toLocaleString('fr-FR',{minimumFractionDigits:d,maximumFractionDigits:d});}
function fmtK(n){if(n>=1e6)return(n/1e6).toFixed(1)+'M';if(n>=1e3)return(n/1e3).toFixed(1)+'k';return n.toFixed(0);}
const periodNames = {1:'Day',7:'Week',30:'Month'};
const periodLabelMap= {1:'day',7:'week',30:'month'};
function calculate() {
const T = parseFloat(document.getElementById('temp').value);
const H = parseFloat(document.getElementById('hum').value);
const W = parseFloat(document.getElementById('wind').value);
const R = parseFloat(document.getElementById('rain').value);
const age = parseFloat(document.getElementById('age').value);
const stage = document.getElementById('stage').value;
const soil = document.getElementById('soil').value;
const trees = parseFloat(document.getElementById('trees').value);
const ha = parseFloat(document.getElementById('hectares').value);
const wCost = parseFloat(document.getElementById('waterCost').value);
const eKwh = parseFloat(document.getElementById('energyKwh').value);
const co2f = parseFloat(document.getElementById('co2factor').value);
const totalTrees = trees*ha;
const mlDay = mlPredict(T,H,W,R,age,stage,soil);
const faoDay = faoPredict(T,H,W,R,age,stage,soil);
const mlPeriodL = mlDay*totalTrees*period;
const faoPeriodL = faoDay*totalTrees*period;
const mlPeriodM3 = mlPeriodL/1000;
const faoPeriodM3 = faoPeriodL/1000;
const savedM3 = Math.max(faoPeriodM3-mlPeriodM3,0);
const savingsPct = faoPeriodM3>0?(savedM3/faoPeriodM3*100):0;
const costSaved = savedM3*wCost;
const co2Saved = savedM3*eKwh*co2f;
const pLabel = periodLabelMap[period];
const canopy = canopyArea(age);
document.getElementById('periodLabel').textContent = periodNames[period]+'ly';
document.getElementById('mlMain').textContent = mlDay+' L';
document.getElementById('faoMain').textContent = faoDay+' L';
document.getElementById('mlDetail').innerHTML =
'<strong>'+fmt(mlPeriodM3,1)+' m\u00b3</strong> for '+totalTrees.toFixed(0)+' trees over '+period+' day'+(period>1?'s':'')+
'<br/>Stage: '+stage+' · Soil: '+soil+' · Canopy: '+canopy.toFixed(1)+' m\u00b2/tree';
document.getElementById('faoDetail').innerHTML =
'<strong>'+fmt(faoPeriodM3,1)+' m\u00b3</strong> FAO-56 Classical ET\u2080 × Kc'+
'<br/>Kc = '+KC[stage]+' · Ks = '+KS[soil]+' · Canopy: '+canopy.toFixed(1)+' m\u00b2/tree';
document.getElementById('mTotalL').textContent = fmtK(mlPeriodL);
document.getElementById('mTotalM3').textContent = fmt(mlPeriodM3,1);
document.getElementById('mFaoM3').textContent = fmt(faoPeriodM3,1);
document.getElementById('mSavedM3').textContent = fmt(savedM3,1);
['mLabelL','mLabelM','mLabelF','mLabelS'].forEach(id=>document.getElementById(id).textContent=pLabel);
document.getElementById('savingsPct').textContent = savingsPct.toFixed(1)+'%';
setTimeout(()=>{ document.getElementById('savingsBar').style.width=Math.min(savingsPct,100)+'%'; },50);
const saveMsg = savedM3>0
? 'ML scheduling saves <strong>'+fmt(savedM3,1)+' m\u00b3</strong> vs FAO-56 over this '+pLabel+'.'
: 'ML and FAO estimates are aligned under current conditions.';
document.getElementById('savingsText').innerHTML = saveMsg;
document.getElementById('costVal').textContent = '\u20ac'+fmt(costSaved,2);
document.getElementById('co2Val').textContent = fmt(co2Saved,2)+' kg';
['costLabel','co2Label'].forEach(id=>document.getElementById(id).textContent=pLabel);
// Agronomic validation
const refMin = age<=4 ? 3 : age<=8 ? 10 : 20;
const refMax = age<=4 ? 18: age<=8 ? 60 : 130;
const valEl = document.getElementById('validationMsg');
if (mlDay>=refMin && mlDay<=refMax) {
valEl.className='validation ok';
valEl.innerHTML='<strong>✓ Agronomically plausible</strong> — '+mlDay+' L/tree/day is within the expected '+refMin+'–'+refMax+' L/tree/day for a '+age+'-year-old citrus tree at '+T+'°C. (FAO-56, Univ. Arizona, Wikifarmer)';
} else if (mlDay<refMin) {
valEl.className='validation low';
valEl.innerHTML='<strong>⚠ Below reference range</strong> — '+mlDay+' L/tree/day is under expected '+refMin+'–'+refMax+' L/tree/day. Check temperature or rainfall inputs.';
} else {
valEl.className='validation high';
valEl.innerHTML='<strong>⚠ Above reference range</strong> — '+mlDay+' L/tree/day exceeds expected '+refMin+'–'+refMax+' L/tree/day. Likely extreme heat or very low humidity on sandy soil.';
}
valEl.style.display='block';
const card = document.getElementById('resultsCard');
card.classList.remove('results-hidden');
card.scrollIntoView({behavior:'smooth',block:'nearest'});
}
window.addEventListener('DOMContentLoaded',()=>{
setPeriod(document.querySelector('.tab-btn[data-p="7"]'));
calculate();
});
/* โโ PDF REPORT GENERATOR โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
async function downloadPDF() {
const btn = document.getElementById('pdfBtn');
btn.disabled = true;
btn.textContent = 'Generating PDF...';
const { jsPDF } = window.jspdf;
const doc = new jsPDF({orientation:'portrait',unit:'mm',format:'a4'});
const W=210, H=297, margin=18, cW=174;
let y=0;
const SOIL=[26,26,46], CANOPY=[13,59,43], CITRUS=[245,166,35];
const LIME=[126,200,80], WATER=[58,180,242];
const WHITE=[240,237,230], MUTED=[140,148,144];
const sf=(s,sz,c)=>{doc.setFont('helvetica',s);doc.setFontSize(sz);if(c)doc.setTextColor(...c);};
const rr=(x,y,w,h,r,s)=>doc.roundedRect(x,y,w,h,r,r,s);
const inp={
trees: document.getElementById('trees').value,
age: document.getElementById('age').value,
stage: document.getElementById('stage').value,
soil: document.getElementById('soil').value,
temp: document.getElementById('tempVal').textContent,
hum: document.getElementById('humVal').textContent,
wind: document.getElementById('windVal').textContent,
rain: document.getElementById('rainVal').textContent,
ha: document.getElementById('hectares').value,
period: document.querySelector('.tab-btn.active').textContent,
cost: document.getElementById('waterCost').value,
};
const res={
mlDay: document.getElementById('mlMain').textContent,
faoDay: document.getElementById('faoMain').textContent,
mlM3: document.getElementById('mTotalM3').textContent,
faoM3: document.getElementById('mFaoM3').textContent,
savedM3: document.getElementById('mSavedM3').textContent,
savings: document.getElementById('savingsPct').textContent,
cost: document.getElementById('costVal').textContent,
co2: document.getElementById('co2Val').textContent,
canopy: (()=>{const d=document.getElementById('mlDetail').textContent;const m=d.match(/Canopy: ([\d.]+)/);return m?m[1]+' m2':'N/A';})(),
valTxt: document.getElementById('validationMsg').textContent.trim().replace(/[^\x20-\x7E]/g,''),
valOk: document.getElementById('validationMsg').classList.contains('ok'),
};
const dateStr=new Date().toLocaleDateString('en-GB',{day:'2-digit',month:'long',year:'numeric'});
// HEADER โ load logo then draw
// logo.png lives at assets/logo.png relative to this page
let logoData = null;
try {
const resp = await fetch('assets/logo.png');
if (resp.ok) {
const blob = await resp.blob();
logoData = await new Promise(resolve => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result); // base64 data URL
reader.readAsDataURL(blob);
});
}
} catch(e) { console.warn('Logo not loaded:', e); }
doc.setFillColor(...SOIL); doc.rect(0,0,W,44,'F');
doc.setFillColor(...CANOPY); doc.rect(0,44,W,2,'F');
// Logo: real image if loaded, orange circle fallback
if (logoData) {
// Logo placed left โ 22mm tall, width auto-proportioned (max 30mm wide)
doc.addImage(logoData, 'PNG', margin, 11, 22, 22);
sf('bold',17,WHITE); doc.text('Citrus Irrigation Report', margin+27, 18);
sf('normal',8,MUTED);doc.text('ML-Optimised Decision Support x FAO-56 Classical Baseline', margin+27, 25);
doc.text('Generated: '+dateStr+' Period: '+inp.period, margin+27, 32);
} else {
// Fallback: coloured circle + text
doc.setFillColor(...CITRUS); doc.circle(margin+5,22,5,'F');
sf('bold',17,WHITE); doc.text('Citrus Irrigation Report', margin+14,18);
sf('normal',8,MUTED); doc.text('ML-Optimised Decision Support x FAO-56 Classical Baseline',margin+14,25);
doc.text('Generated: '+dateStr+' Period: '+inp.period, margin+14,32);
}
sf('bold',8,CITRUS); doc.text('Danki Studio', W-margin,18,{align:'right'});
sf('normal',7,MUTED); doc.text('dankistudio.com', W-margin,24,{align:'right'});
doc.text('Nambona Adeline YANGUERE', W-margin,30,{align:'right'});
y=52;
// INPUTS TABLE
sf('bold',9,CITRUS); doc.text('ORCHARD & ENVIRONMENTAL INPUTS',margin,y);
doc.setFillColor(...CITRUS); doc.rect(margin,y+1.5,46,0.5,'F');
y+=8;
const rows=[
['Trees / ha',inp.trees, 'Tree age', inp.age+' yrs'],
['Growth stage',inp.stage, 'Soil type', inp.soil],
['Temperature', inp.temp, 'Humidity', inp.hum],
['Wind speed', inp.wind, 'Rainfall', inp.rain],
['Hectares', inp.ha, 'Period', inp.period],
['Water cost', inp.cost+' EUR/m3','Canopy/tree',res.canopy],
];
const hw=cW/2-2;
rows.forEach((r,i)=>{
const ry=y+i*9;
if(i%2===0){doc.setFillColor(28,32,50);rr(margin,ry-4,cW,8,1,'F');}
sf('normal',7.5,MUTED); doc.text(r[0],margin+3,ry);
sf('bold',7.5,WHITE); doc.text(String(r[1]),margin+3+hw*0.48,ry);
sf('normal',7.5,MUTED); doc.text(r[2],margin+hw+6,ry);
sf('bold',7.5,WHITE); doc.text(String(r[3]),margin+hw+6+hw*0.48,ry);
});
y+=rows.length*9+8;
// DUAL TRACK
sf('bold',9,CITRUS); doc.text('DUAL-TRACK RESULTS',margin,y);
doc.setFillColor(...CITRUS); doc.rect(margin,y+1.5,30,0.5,'F');
y+=8;
const bw=(cW-4)/2;
// ML
doc.setFillColor(...CANOPY);rr(margin,y,bw,36,2,'F');
doc.setDrawColor(...LIME); rr(margin,y,bw,36,2,'S');
sf('bold',7,LIME); doc.text('ML MODEL PREDICTION',margin+4,y+7);
sf('bold',20,LIME); doc.text(res.mlDay,margin+4,y+20);
sf('normal',7,MUTED);doc.text('per tree / day',margin+4,y+26);
doc.text('Total '+inp.period.toLowerCase()+': '+res.mlM3+' m3',margin+4,y+32);
// FAO
const fx=margin+bw+4;
doc.setFillColor(15,35,55);rr(fx,y,bw,36,2,'F');
doc.setDrawColor(...WATER); rr(fx,y,bw,36,2,'S');
sf('bold',7,WATER); doc.text('FAO-56 CLASSICAL BASELINE',fx+4,y+7);
sf('bold',20,WATER); doc.text(res.faoDay,fx+4,y+20);
sf('normal',7,MUTED);doc.text('per tree / day',fx+4,y+26);
doc.text('Total '+inp.period.toLowerCase()+': '+res.faoM3+' m3',fx+4,y+32);
y+=42;
// METRICS
sf('bold',9,CITRUS); doc.text('EFFICIENCY SUMMARY',margin,y);
doc.setFillColor(...CITRUS); doc.rect(margin,y+1.5,32,0.5,'F');
y+=8;
const metrics=[
{l:'Water Saved',v:res.savedM3+' m3',c:LIME},
{l:'Savings vs FAO',v:res.savings,c:LIME},
{l:'Cost Saved',v:res.cost,c:CITRUS},
{l:'CO2 Avoided',v:res.co2,c:WATER},
];
const mw=(cW-6)/4;
metrics.forEach((m,i)=>{
const mx=margin+i*(mw+2);
doc.setFillColor(22,28,44);rr(mx,y,mw,22,2,'F');
sf('bold',12,m.c); doc.text(m.v,mx+mw/2,y+12,{align:'center'});
sf('normal',6,MUTED); doc.text(m.l,mx+mw/2,y+18,{align:'center'});
});
y+=28;
// SAVINGS BAR
const pct=parseFloat(res.savings)||0;
doc.setFillColor(30,36,54);rr(margin,y,cW,7,2,'F');
if(pct>0){doc.setFillColor(...LIME);rr(margin,y,cW*Math.min(pct/100,1),7,2,'F');}
sf('bold',6,SOIL); doc.text(' '+res.savings+' efficiency vs FAO-56',margin+2,y+5);
y+=13;
// VALIDATION
const vbg=res.valOk?[13,40,20]:[45,30,10], vc=res.valOk?LIME:CITRUS;
doc.setFillColor(...vbg);rr(margin,y,cW,18,2,'F');
doc.setDrawColor(...vc); rr(margin,y,cW,18,2,'S');
sf('bold',7,vc); doc.text(res.valOk?'AGRONOMIC VALIDATION - PASS':'AGRONOMIC CHECK - REVIEW',margin+4,y+6);
sf('normal',6.5,WHITE);
const vlines=doc.splitTextToSize(res.valTxt,cW-8);
doc.text(vlines.slice(0,3),margin+4,y+12);
y+=24;
// SCREENSHOT
try {
const canvas=await html2canvas(document.getElementById('resultsCard'),{
scale:1.4,backgroundColor:'#1a1a2e',useCORS:true,logging:false
});
const img=canvas.toDataURL('image/jpeg',0.85);
const ih=(canvas.height/canvas.width)*cW;
if(y+ih+20>H-15){doc.addPage();doc.setFillColor(...SOIL);doc.rect(0,0,W,H,'F');y=margin;}
sf('bold',9,CITRUS); doc.text('RESULTS SNAPSHOT',margin,y);
doc.setFillColor(...CITRUS); doc.rect(margin,y+1.5,28,0.5,'F');
y+=7;
doc.addImage(img,'JPEG',margin,y,cW,ih);
y+=ih+6;
} catch(e){console.warn('Screenshot skipped:',e);}
// FOOTER
const np=doc.getNumberOfPages();
for(let p=1;p<=np;p++){
doc.setPage(p);
doc.setFillColor(...SOIL); doc.rect(0,H-12,W,12,'F');
doc.setFillColor(...CANOPY); doc.rect(0,H-12,W,1,'F');
sf('normal',6,MUTED);
doc.text('Citrus Water Supply Project x Nambona Adeline YANGUERE x Danki Studio',margin,H-5);
doc.text('Page '+p+'/'+np,W-margin,H-5,{align:'right'});
// Footer logo (small)
if (logoData) { try { doc.addImage(logoData,'PNG',W-margin-10,H-11,10,8); } catch(e){} }
sf('normal',5.5,[70,70,70]);
doc.text('Approximated ML model. Not a substitute for field agronomic advice.',margin,H-2);
}
const fname='citrus-irrigation-'+inp.stage+'-'+inp.temp.replace(/[^0-9]/g,'')+'c-'+new Date().toISOString().slice(0,10)+'.pdf';
doc.save(fname);
btn.disabled=false;
btn.textContent='Download Simulation Report (PDF)';
}
</script>
</body>
</html>
```