<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 <title>Investment Projection Calculator</title>
 <style>
   :root {
     --primary: #3c556e;
     --primary-light: #5a7a96;
     --primary-dark: #2a3b4d;
     --accent: #4a6a85;
     --accent-light: #6b8aa5;
     --bg: #f4f6f8;
     --card: #ffffff;
     --text: #1e2a36;
     --muted: #6b7c8d;
     --border: #d8e0e8;
     --slider-track: #d8e0e8;
   }

   * {
     box-sizing: border-box;
     margin: 0;
     padding: 0;
   }

   body {
     font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
     background: var(--bg);
     color: var(--text);
     line-height: 1.5;
     padding: 24px;
     min-height: 100vh;
   }

   .container {
     max-width: 1100px;
     margin: 0 auto;
     display: grid;
     grid-template-columns: 1.4fr 1fr;
     gap: 32px;
     align-items: start;
   }

   @media (max-width: 900px) {
     .container {
       grid-template-columns: 1fr;
     }
   }

   /* Chart Card */
   .chart-card {
     background: var(--card);
     border-radius: 16px;
     padding: 28px 24px 20px;
     box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.04);
     position: relative;
   }

   .chart-wrapper {
     position: relative;
     height: 320px;
     width: 100%;
   }

   #projectionChart {
     width: 100% !important;
     height: 100% !important;
   }

   .chart-labels {
     position: absolute;
     right: 12px;
     top: 20px;
     display: flex;
     flex-direction: column;
     gap: 6px;
     font-size: 13px;
     font-weight: 500;
     pointer-events: none;
     z-index: 10;
   }

   .label-higher {
     color: #5a7a96;
   }
   .label-target {
     color: #3c556e;
   }
   .label-lower {
     color: #2a3b4d;
   }

   .chart-footer {
     text-align: center;
     margin-top: 12px;
     font-size: 12px;
     color: var(--muted);
   }

   .chart-footer .axis-label {
     font-weight: 500;
     color: var(--text);
     margin-bottom: 4px;
   }

   /* Controls */
   .controls {
     display: flex;
     flex-direction: column;
     gap: 16px;
   }

   .control-card {
     background: var(--card);
     border-radius: 16px;
     padding: 18px 20px;
     box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 4px 12px rgba(0,0,0,0.04);
   }

   .control-header {
     display: flex;
     align-items: center;
     justify-content: space-between;
     margin-bottom: 10px;
   }

   .control-title {
     font-size: 14px;
     font-weight: 600;
     color: var(--text);
   }

   .unit-badge {
     background: #f1f5f9;
     color: var(--muted);
     font-size: 11px;
     font-weight: 500;
     padding: 2px 9px;
     border-radius: 999px;
   }

   .value-display {
     font-size: 20px;
     font-weight: 600;
     color: var(--text);
     margin-bottom: 12px;
     letter-spacing: -0.02em;
   }

   .slider-container {
     position: relative;
     padding: 0 2px;
   }

   input[type="range"] {
     -webkit-appearance: none;
     appearance: none;
     width: 100%;
     height: 6px;
     border-radius: 3px;
     background: var(--slider-track);
     outline: none;
     cursor: pointer;
   }

   input[type="range"]::-webkit-slider-thumb {
     -webkit-appearance: none;
     appearance: none;
     width: 18px;
     height: 18px;
     border-radius: 50%;
     background: var(--primary);
     border: 3px solid white;
     box-shadow: 0 1px 4px rgba(60,85,110,0.4);
     cursor: pointer;
     transition: transform 0.15s ease;
   }

   input[type="range"]::-webkit-slider-thumb:hover {
     transform: scale(1.12);
   }

   input[type="range"]::-moz-range-thumb {
     width: 18px;
     height: 18px;
     border-radius: 50%;
     background: var(--primary);
     border: 3px solid white;
     box-shadow: 0 1px 4px rgba(60,85,110,0.4);
     cursor: pointer;
   }

   .slider-labels {
     display: flex;
     justify-content: space-between;
     margin-top: 6px;
     font-size: 11px;
     color: var(--muted);
   }

   .hint {
     font-size: 12px;
     color: var(--muted);
     margin-top: 8px;
     line-height: 1.4;
   }

   .summary-row {
     display: flex;
     justify-content: space-between;
     font-size: 13px;
     color: var(--muted);
     margin-top: 6px;
   }

   .summary-row strong {
     color: var(--text);
   }

   .summary-row.target-row {
     margin-top: 12px;
     padding-top: 10px;
     border-top: 1px solid var(--border);
     align-items: baseline;
   }

   .summary-row.target-row span {
     font-size: 14px;
     font-weight: 500;
     color: var(--text);
   }

   .summary-row.target-row strong {
     font-size: 22px;
     font-weight: 700;
     color: #3c556e;
     letter-spacing: -0.02em;
   }

   /* Dropdown styles */
   select.plan-select {
     width: 100%;
     padding: 10px 12px;
     font-size: 14px;
     font-weight: 500;
     color: var(--text);
     background: #f8fafc;
     border: 1px solid var(--border);
     border-radius: 10px;
     cursor: pointer;
     appearance: none;
     background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
     background-repeat: no-repeat;
     background-position: right 12px center;
     padding-right: 36px;
     transition: border-color 0.15s ease;
   }

   select.plan-select:hover,
   select.plan-select:focus {
     border-color: var(--primary);
     outline: none;
   }

   .plans-btn {
     display: block;
     width: 100%;
     padding: 12px 16px;
     font-size: 14px;
     font-weight: 600;
     color: white;
     background: var(--primary);
     border: none;
     border-radius: 10px;
     cursor: pointer;
     text-align: center;
     text-decoration: none;
     transition: background 0.15s ease, transform 0.1s ease;
   }

   .plans-btn:hover {
     background: var(--primary-dark);
   }

   .plans-btn:active {
     transform: scale(0.98);
   }

   @media (max-width: 600px) {
     body { padding: 16px; }
     .chart-wrapper { height: 260px; }
     .value-display { font-size: 18px; }
   }
 </style>
</head>
<body>
 <div class="container">
   <!-- Chart -->
   <div class="chart-card">
     <div class="chart-wrapper">
       <canvas id="projectionChart"></canvas>
       <div class="chart-labels" id="endLabels">
         <div class="label-higher">Higher Range<br><span id="higherVal">—</span></div>
         <div class="label-target">Target<br><span id="targetVal">—</span></div>
         <div class="label-lower">Lower Range<br><span id="lowerVal">—</span></div>
       </div>
     </div>
     <div class="chart-footer">
       <div class="axis-label">Time</div>
       <div>Expected scenarios are based on historical standard deviation. Actual results may vary.</div>
     </div>
   </div>

   <!-- Controls -->
   <div class="controls">
     <div class="control-card">
       <div class="control-header">
         <span class="control-title">Investment Plan</span>
       </div>
       <select id="planSelect" class="plan-select">
         <option value="Retire Safely">Retire Safely</option>
         <option value="Buy a big ticket item (new house, car, education)">Buy a big ticket item (new house, car, education)</option>
         <option value="Treat myself (Holiday, new clothes etc)">Treat myself (Holiday, new clothes etc)</option>
       </select>
     </div>

     <div class="control-card">
       <div class="control-header">
         <span class="control-title">Initial Investment</span>
         <span class="unit-badge">ZAR</span>
       </div>
       <div class="value-display" id="initialDisplay">329,000</div>
       <div class="slider-container">
         <input type="range" id="initialSlider" min="0" max="1000000" step="1000" value="329000" />
         <div class="slider-labels">
           <span>0</span>
           <span>1,000,000</span>
         </div>
       </div>
     </div>

     <div class="control-card">
       <div class="control-header">
         <span class="control-title">Monthly Contribution</span>
         <span class="unit-badge">ZAR</span>
       </div>
       <div class="value-display" id="contribDisplay">0</div>
       <div class="slider-container">
         <input type="range" id="contribSlider" min="0" max="30000" step="50" value="0" />
         <div class="slider-labels">
           <span>0</span>
           <span>30,000</span>
         </div>
       </div>
     </div>

     <div class="control-card">
       <div class="control-header">
         <span class="control-title">Duration</span>
         <span class="unit-badge">years</span>
       </div>
       <div class="value-display" id="durationDisplay">3</div>
       <div class="slider-container">
         <input type="range" id="durationSlider" min="1" max="30" step="1" value="3" />
         <div class="slider-labels">
           <span>1</span>
           <span>30</span>
         </div>
       </div>
     </div>

     <div class="control-card">
       <div class="control-header">
         <span class="control-title">Expected Annual Return</span>
         <span class="unit-badge">%</span>
       </div>
       <div class="value-display" id="returnDisplay">15.0</div>
       <div class="slider-container">
         <input type="range" id="returnSlider" min="0" max="30" step="0.5" value="15" />
         <div class="slider-labels">
           <span>0%</span>
           <span>30%</span>
         </div>
       </div>
       <p class="hint">Target value is calculated from initial + contributions + this return. Higher/Lower ranges use historical volatility (~±15–20% annualised).</p>
     </div>

     <div class="control-card" style="background:#f8fafc; border:1px solid #e2e8f0;">
       <div class="summary-row">
         <span>Total contributions</span>
         <strong id="totalContrib">R0</strong>
       </div>
       <div class="summary-row target-row">
         <span>Projected target value</span>
         <strong id="targetSummary">—</strong>
       </div>
     </div>

     <div class="control-card">
       <a href="https://www.digiwealth.co.za/plans#compare" class="plans-btn" target="_blank" rel="noopener noreferrer">
         Our Investment plans
       </a>
     </div>
   </div>
 </div>

 <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
 <script>
   function fmt(n) {
     return Math.round(n).toLocaleString('en-US');
   }

   /**
    * Future value of initial lump sum + monthly contributions.
    * Monthly rate = annualRate / 12
    * FV = P0*(1+r)^n + PMT * [((1+r)^n - 1) / r]
    * where n = years * 12, r = monthly rate
    */
   function futureValue(initial, monthlyPmt, years, annualRate) {
     const months = Math.round(years * 12);
     const r = annualRate / 12;
     if (r === 0) {
       return initial + monthlyPmt * months;
     }
     const growth = Math.pow(1 + r, months);
     const fvLump = initial * growth;
     const fvAnnuity = monthlyPmt * (growth - 1) / r;
     return fvLump + fvAnnuity;
   }

   /**
    * Build yearly projection points including monthly contributions.
    * Volatility model calibrated so that at ~3 years the relative spread
    * roughly matches the original screenshot (higher ≈ 1.62×, lower ≈ 0.56×).
    */
   function project(initial, monthlyPmt, years, annualRate) {
     const t = Math.max(1, years);
     const points = [];

     // Base volatility calibrated to original screenshot
     const baseLogSpread = Math.log(1.617); // ≈ 0.480 at t=3
     const sigmaPerSqrtYear = baseLogSpread / Math.sqrt(3);

     for (let y = 0; y <= t; y++) {
       const expected = futureValue(initial, monthlyPmt, y, annualRate);
       const spreadFactor = y === 0 ? 0 : sigmaPerSqrtYear * Math.sqrt(y);
       const higher = expected * Math.exp(spreadFactor);
       const lower = expected * Math.exp(-spreadFactor * 1.15);

       points.push({
         year: y,
         expected: expected,
         higher: higher,
         lower: Math.max(0, lower)
       });
     }

     return points;
   }

   const ctx = document.getElementById('projectionChart').getContext('2d');
   let chart = null;

   function buildChart(points) {
     const labels = points.map(p => p.year === 0 ? '' : p.year);
     const expected = points.map(p => p.expected);
     const higher = points.map(p => p.higher);
     const lower = points.map(p => p.lower);

     if (chart) chart.destroy();

     chart = new Chart(ctx, {
       type: 'line',
       data: {
         labels: labels,
         datasets: [
           {
             label: 'Higher',
             data: higher,
             borderColor: 'transparent',
             backgroundColor: 'rgba(90, 122, 150, 0.35)',
             fill: '+1',
             pointRadius: 0,
             tension: 0.35,
             borderWidth: 0
           },
           {
             label: 'Expected',
             data: expected,
             borderColor: '#3c556e',
             backgroundColor: 'rgba(60, 85, 110, 0.45)',
             fill: '+1',
             pointRadius: points.map((_, i) => (i === 0 || i === points.length - 1) ? 5 : 3),
             pointBackgroundColor: '#3c556e',
             pointBorderColor: '#fff',
             pointBorderWidth: 2,
             tension: 0.35,
             borderWidth: 2.5
           },
           {
             label: 'Lower',
             data: lower,
             borderColor: 'transparent',
             backgroundColor: 'rgba(42, 59, 77, 0.30)',
             fill: false,
             pointRadius: points.map((_, i) => (i === 0 || i === points.length - 1) ? 5 : 3),
             pointBackgroundColor: '#2a3b4d',
             pointBorderColor: '#fff',
             pointBorderWidth: 2,
             tension: 0.35,
             borderWidth: 0
           }
         ]
       },
       options: {
         responsive: true,
         maintainAspectRatio: false,
         plugins: {
           legend: { display: false },
           tooltip: {
             mode: 'index',
             intersect: false,
             callbacks: {
               title: (items) => items[0].label ? `Year ${items[0].label}` : 'Start',
               label: (ctx) => ctx.dataset.label + ': R' + fmt(ctx.raw)
             }
           }
         },
         scales: {
           x: {
             display: true,
             grid: { display: false },
             ticks: { display: false }
           },
           y: {
             display: false,
             beginAtZero: false
           }
         },
         interaction: {
           mode: 'nearest',
           axis: 'x',
           intersect: false
         }
       }
     });
   }

   function update() {
     const initial = +document.getElementById('initialSlider').value;
     const monthly = +document.getElementById('contribSlider').value;
     const years = +document.getElementById('durationSlider').value;
     const annualRate = +document.getElementById('returnSlider').value / 100;

     document.getElementById('initialDisplay').textContent = fmt(initial);
     document.getElementById('contribDisplay').textContent = fmt(monthly);
     document.getElementById('durationDisplay').textContent = years;
     document.getElementById('returnDisplay').textContent = (annualRate * 100).toFixed(1);

     const totalContrib = monthly * years * 12;
     document.getElementById('totalContrib').textContent = 'R' + fmt(totalContrib);

     const points = project(initial, monthly, years, annualRate);
     const last = points[points.length - 1];

     document.getElementById('higherVal').textContent = 'R' + fmt(last.higher);
     document.getElementById('targetVal').textContent = 'R' + fmt(last.expected);
     document.getElementById('lowerVal').textContent = 'R' + fmt(last.lower);
     document.getElementById('targetSummary').textContent = 'R' + fmt(last.expected);

     buildChart(points);
   }

   ['initialSlider', 'contribSlider', 'durationSlider', 'returnSlider'].forEach(id => {
     document.getElementById(id).addEventListener('input', update);
   });

   // Initial render
   update();
 </script>
</body>
</html>

‍