<?php
// Datacenter cooling-loop simulator for AI accelerators — customer-facing page.
// Heavy lifting runs on the ChipFoundryServices compute pool: this page POSTs the
// chip TDP + die size, the cooling mode (air / liquid cold-plate / two-phase),
// the coolant, the full thermal-interface + lid + cold-plate conduction stack, the
// flow rate and the pump/fan efficiency + overhead budget to /edge/cooling
// (same-origin). NGINX routes it to the compute node, which builds the junction-to-
// coolant thermal resistance network (TIM1 + lid + TIM2 + plate base conduction,
// convective film, and the caloric coolant self-heating), computes the junction
// temperature Tj = coolant_inlet + Q·theta_JA, and the parasitic pump/fan power as
// a fraction of TDP. It sweeps flow and TDP to expose the Tj curves. A design ships
// only if it stays under Tj_max AND keeps the cooling overhead under budget.
// Results are annotated with explanatory text pulled live from the CFS knowledge base.
header('Content-Type: text/html; charset=utf-8');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Datacenter Chip Cooling Loop Simulator — Cold Plate, Two-Phase &amp; Air Thermal Resistance Network, Junction Temperature &amp; Pump/Fan Overhead | ChipFoundryServices</title>
<link rel="stylesheet" href="/cfs_theme.css?v=2">
<meta name="description" content="Run a datacenter cooling-loop simulation for an AI accelerator in your browser — computed on the ChipFoundryServices compute pool. As training/inference silicon pushes past 700 W–1 kW per package, the heat path stops being 'chip → heatsink → air' and becomes a thermal resistance network from the junction, through two thermal-interface layers (TIM1, TIM2) and a copper lid, into a liquid cold plate (or a two-phase/immersion microchannel evaporator), and finally out to the facility coolant. This reduced-order model sums the series conduction resistances, the convective film resistance to the coolant, and the caloric resistance (the coolant heats up as it carries the heat away) into theta_JA, computes the junction temperature Tj = coolant_inlet + Q·theta_JA against Tj_max, and reports the pump/fan power as a fraction of TDP. It sweeps coolant flow and chip power to expose the Tj curves and the cooling-overhead trade. A cooling design ships only if it stays under Tj_max AND keeps the parasitic overhead under budget. Reduced-order educational model of liquid cold-plate, two-phase and air cooling for AI chips.">
<style>
  :root{ --green:#07C160; --ink:#16181d; --muted:#6b7280; --line:#e6e8eb; --bg:#f5f6f8; --si:#3a4a63; --film:#4f9cf9; --cav:#eef2f7; }
  *{ box-sizing:border-box; }
  body{ margin:0; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; background:var(--bg); color:var(--ink); }
  .wrap{ max-width:1040px; margin:0 auto; padding:24px 16px 56px; }
  h1{ font-size:24px; margin:0 0 4px; }
  .lede{ color:var(--muted); font-size:14px; margin:0 0 20px; line-height:1.5; }
  .lede a{ color:var(--green); text-decoration:none; }
  .grid{ display:grid; grid-template-columns:320px 1fr; gap:20px; align-items:start; }
  @media(max-width:760px){ .grid{ grid-template-columns:1fr; } }
  .card{ background:#fff; border:1px solid var(--line); border-radius:12px; padding:18px; box-shadow:0 1px 3px rgba(0,0,0,.05); }
  label{ display:block; font-size:13px; font-weight:600; margin:14px 0 5px; }
  label:first-of-type{ margin-top:0; }
  .val{ float:right; color:var(--green); font-weight:700; font-variant-numeric:tabular-nums; }
  input[type=range]{ width:100%; accent-color:var(--green); }
  select{ width:100%; padding:8px; border:1px solid var(--line); border-radius:8px; font-size:14px; background:#fafbfc; accent-color:var(--green); }
  .grouphdr{ font-size:11px; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); font-weight:700; margin:18px 0 2px; border-top:1px solid var(--line); padding-top:12px; }
  .grouphdr:first-child{ border-top:0; padding-top:0; margin-top:0; }
  .presets{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
  .preset{ font-size:12px; padding:6px 10px; border:1px solid var(--line); border-radius:999px; background:#fafbfc; cursor:pointer; }
  .preset:hover{ border-color:var(--green); color:var(--green); }
  button.run{ width:100%; margin-top:18px; background:var(--green); color:#fff; border:0; border-radius:8px; padding:12px; font-size:15px; font-weight:700; cursor:pointer; }
  button.run:disabled{ opacity:.6; cursor:default; }
  .viz{ display:flex; justify-content:center; background:linear-gradient(#fbfcfd,#f3f5f7); border-radius:10px; padding:10px; margin-bottom:10px; }
  .vizcap{ font-size:12px; color:var(--muted); text-align:center; margin:-4px 0 12px; }
  svg{ max-width:100%; height:auto; }
  table{ width:100%; border-collapse:collapse; margin-top:14px; font-size:14px; }
  td,th{ text-align:left; padding:8px 10px; border-bottom:1px solid var(--line); }
  th{ color:var(--muted); font-weight:600; font-size:12px; text-transform:uppercase; letter-spacing:.03em; }
  td.num{ text-align:right; font-variant-numeric:tabular-nums; font-family:ui-monospace,Menlo,monospace; }
  .verdict{ margin-top:12px; padding:10px 12px; border-radius:8px; font-size:14px; font-weight:600; }
  .node{ margin-top:12px; font-size:13px; color:var(--muted); }
  .node b{ color:var(--green); }
  .kb{ margin-top:20px; }
  .kb h2{ font-size:15px; margin:0 0 4px; }
  .kb .src{ font-size:12px; color:var(--muted); margin:0 0 12px; }
  .kb .src a{ color:var(--green); text-decoration:none; }
  .kb-body{ font-size:14px; line-height:1.6; color:#e2e8f0; }
  .kb-body p{ margin:0 0 10px; }
  .kb-body ul{ margin:0 0 10px; padding-left:20px; }
  .kb-body li{ margin:2px 0; }
  .kb-block{ margin-bottom:16px; }
  .kb-body figure.kb-figure{ margin:18px 0; text-align:center; overflow-x:auto; -webkit-overflow-scrolling:touch; }
  .kb-body figure.kb-figure svg{ max-width:100%; height:auto; }
        /* Professional flowchart (Claude.ai-style grouped stage diagram) */
        .kb-body .cfs-flow { display: flex; flex-direction: column; align-items: stretch; gap: 0; margin: 22px 0 26px; font-family: 'Styrene B', 'Styrene A', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; -webkit-font-smoothing: antialiased; }
        .kb-body .cfs-flow-row { display: flex; justify-content: center; align-items: stretch; gap: 18px; flex-wrap: wrap; }
        .kb-body .cfs-node { flex: 1 1 220px; max-width: 340px; min-height: 62px; border-radius: 14px; padding: 14px 18px; display: flex; flex-direction: column; justify-content: center; gap: 3px; border: 1px solid; box-sizing: border-box; }
        .kb-body .cfs-node-title { font-weight: 600; font-size: 16px; line-height: 1.25; }
        .kb-body .cfs-node-sub { font-size: 13.5px; opacity: .82; line-height: 1.3; }
        .kb-body .cfs-node.neutral { background: #2b2a27; border-color: #45433d; color: #efeee8; }
        .kb-body .cfs-node.green   { background: #14312a; border-color: #2f6d55; color: #d6f0e2; }
        .kb-body .cfs-node.green .cfs-node-title { color: #8fe3bd; }
        .kb-body .cfs-node.blue { background: #173f68; border-color: #2f74b8; color: #d7e6f7; }
        .kb-body .cfs-node.blue .cfs-node-title { color: #c4ddf7; }
        .kb-body .cfs-node.orange  { background: #4a2317; border-color: #8a3c22; color: #f3d8ca; }
        .kb-body .cfs-node.orange .cfs-node-title { color: #f0b79a; }
        .kb-body .cfs-arrow { display: flex; justify-content: center; align-items: center; height: 34px; color: #8a8880; }
        .kb-body .cfs-arrow svg { width: 18px; height: 30px; }
        .kb-body .cfs-arrow-row { display: flex; justify-content: center; gap: 18px; height: 34px; }
        .kb-body .cfs-arrow-col { flex: 1 1 220px; max-width: 340px; display: flex; justify-content: center; align-items: center; color: #8a8880; }
        .kb-body .cfs-arrow-col svg { width: 18px; height: 30px; }
        .kb-body .cfs-tier { border: 1px dashed #3d3d3a; border-radius: 16px; padding: 18px 20px 20px; margin: 0 0 16px; }
        .kb-body .cfs-tier:last-child { margin-bottom: 0; }
        .kb-body .cfs-tier-h { font-size: 17px; font-weight: 600; color: #f2f1ec; margin-bottom: 14px; font-family: 'Styrene B', 'Styrene A', ui-sans-serif, -apple-system, sans-serif; }
        .kb-body .cfs-tier-row { display: flex; gap: 14px; flex-wrap: wrap; }
        .kb-body .cfs-tier-row .cfs-node { flex: 1 1 150px; text-align: center; }
        .kb-body .cfs-group { background: #3b357a; border: 1px solid #4f4894; border-radius: 18px; padding: 16px 18px 18px; margin: 0 auto; width: 100%; max-width: 720px; box-sizing: border-box; }
        .kb-body .cfs-group-head { text-align: center; margin-bottom: 14px; }
        .kb-body .cfs-group-title { font-weight: 600; font-size: 17px; color: #eceaff; }
        .kb-body .cfs-group-note { font-size: 13.5px; color: #c3bdf0; margin-top: 2px; }
        .kb-body .cfs-group-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 16px; }
        .kb-body .cfs-group-loop { text-align: center; font-size: 13.5px; color: #c3bdf0; margin-top: 14px; }
        @media (max-width: 620px) {
            .kb-body .cfs-flow-row { flex-direction: column; align-items: center; gap: 14px; }
            .kb-body .cfs-node { width: 100%; max-width: 100%; }
            .kb-body .cfs-tier-row { flex-direction: column; }
            .kb-body .cfs-tier-row .cfs-node { width: 100%; max-width: 100%; }
            .kb-body .cfs-group-grid { grid-template-columns: 1fr; }
            .kb-body .cfs-arrow-row { flex-direction: column; align-items: center; gap: 0; height: auto; }
            .kb-body .cfs-arrow-col { max-width: 100%; }
        }
  .kb-body mjx-container[display="true"]{ overflow-x:auto; overflow-y:hidden; max-width:100%; margin:8px 0; }
  .kb-body mjx-container{ max-width:100%; }
  .kb-body .kb-tablewrap{ overflow-x:auto; margin:12px 0; -webkit-overflow-scrolling:touch; border:1px solid #e6e8eb; border-radius:8px; }
  .kb-body table.kb-table{ border-collapse:collapse; width:100%; font-size:13px; background:#fff; }
  .kb-body table.kb-table th, .kb-body table.kb-table td{ border-bottom:1px solid #eef1f4; border-right:1px solid #eef1f4; padding:8px 12px; text-align:left; vertical-align:top; white-space:nowrap; }
  .kb-body table.kb-table th:last-child, .kb-body table.kb-table td:last-child{ border-right:0; }
  .kb-body table.kb-table tbody tr:last-child td{ border-bottom:0; }
  .kb-body table.kb-table th{ background:#f5f6f8; font-weight:700; color:#16181d; text-transform:none; letter-spacing:0; font-size:12px; }
  .kb-body table.kb-table tbody tr:nth-child(even) td{ background:#fafbfc; }
  .api{ margin-top:26px; font-size:13px; color:var(--muted); line-height:1.6; }
  .api code,.api pre{ background:#0f1220; color:#e6e6ef; border-radius:6px; }
  .api code{ padding:1px 6px; font-size:12px; }
  .api pre{ padding:12px; overflow:auto; font-size:12px; }
  .err{ color:#c0392b; font-size:14px; padding:10px 0; }
</style>
<script>
  window.MathJax = {
    tex: { inlineMath: [['$','$'],['\\(','\\)']], displayMath: [['$$','$$'],['\\[','\\]']], processEscapes: true },
    options: { skipHtmlTags: ['script','noscript','style','textarea','pre','code'] },
    startup: { typeset: false }
  };
</script>
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js"></script>
</head>
<body>
<?php require_once __DIR__ . "/cfs_nav.php"; ?>

<div class="wrap">
  <h1>Datacenter Chip Cooling Loop Simulator</h1>
  <p class="lede">
    Model <b>whether an AI accelerator stays cool &mdash; and worth cooling</b>, then run it: the simulation executes
    on the ChipFoundryServices distributed compute pool. As training silicon pushes past <b>700&nbsp;W&ndash;1&nbsp;kW</b>
    per package, the heat path stops being &ldquo;chip&nbsp;&rarr;&nbsp;heatsink&nbsp;&rarr;&nbsp;air&rdquo; and becomes a
    <b>thermal resistance network</b>: from the junction, through two thermal-interface layers (<code>TIM1</code>,
    <code>TIM2</code>) and a copper lid, into a liquid <b>cold plate</b> (or a two-phase / immersion microchannel
    evaporator), and out to the facility coolant. Two things gate the design. The <b>thermal budget</b>: the series
    conduction resistances, the convective film resistance, and the <b>caloric</b> resistance (the coolant heats up as it
    carries the heat away) sum to <code>theta_JA</code>; the junction temperature
    <code>Tj = coolant_inlet + Q&middot;theta_JA</code> has to sit under <code>Tj_max</code> with margin. And the
    <b>cooling overhead</b>: moving coolant costs pump/fan power &mdash; pressure drop grows ~flow<sup>1.75</sup>, so
    buying thermal margin with flow burns parasitic watts. The design ships only if it clears <b>both</b> gates.
    Reduced-order educational model. See also the
    <a href="/thermal">package thermal</a>, <a href="/warpage">package warpage</a>, <a href="/power">dynamic power</a>,
    <a href="/electromigration">electromigration</a>, <a href="/hbm">HBM bandwidth</a> and
    <a href="/pdn">power-delivery network</a> simulators and the <a href="/edge/">compute-pool status</a>.
  </p>

  <div class="grid">
    <!-- ============ controls ============ -->
    <form class="card" id="form" onsubmit="return false;">
      <div class="presets">
        <span class="preset" data-p="coldplate">Liquid cold plate</span>
        <span class="preset" data-p="twophase">Two-phase / immersion</span>
        <span class="preset" data-p="air">Air (modest TDP)</span>
      </div>

      <div class="grouphdr">Load &amp; limits</div>

      <label>Chip TDP <span class="val" id="v_tdp">700 W</span></label>
      <input type="range" id="tdp_w" min="50" max="2000" step="10" value="700">

      <label>Die edge <span class="val" id="v_die">26 mm</span></label>
      <input type="range" id="die_size_mm" min="5" max="50" step="1" value="26">

      <label>Coolant / air inlet temp <span class="val" id="v_inlet">30 &deg;C</span></label>
      <input type="range" id="coolant_inlet_c" min="10" max="60" step="1" value="30">

      <label>Max junction temp (Tj&nbsp;max) <span class="val" id="v_tjmax">105 &deg;C</span></label>
      <input type="range" id="tj_max_c" min="85" max="125" step="1" value="105">

      <div class="grouphdr">Cooling mode &amp; fluid</div>

      <label>Cooling mode</label>
      <select id="cooling_mode">
        <option value="cold_plate" selected>Liquid cold plate (single-phase)</option>
        <option value="two_phase">Two-phase / immersion (boiling)</option>
        <option value="air">Air (forced convection)</option>
      </select>

      <label>Coolant (liquid modes)</label>
      <select id="coolant">
        <option value="water" selected>Water</option>
        <option value="pg25">25% propylene glycol (PG25)</option>
        <option value="dielectric">Dielectric / oil</option>
      </select>

      <label>Liquid flow rate <span class="val" id="v_flow">2 LPM</span></label>
      <input type="range" id="flow_rate_lpm" min="0.2" max="20" step="0.1" value="2">

      <label>Air flow (air mode) <span class="val" id="v_cfm">60 CFM</span></label>
      <input type="range" id="air_flow_cfm" min="5" max="500" step="5" value="60">

      <div class="grouphdr">Thermal stack</div>

      <label>TIM1 thickness (die&rarr;lid) <span class="val" id="v_t1t">50 &micro;m</span></label>
      <input type="range" id="tim1_thickness_um" min="10" max="200" step="5" value="50">

      <label>TIM1 conductivity <span class="val" id="v_t1k">5 W/mK</span></label>
      <input type="range" id="tim1_conductivity" min="1" max="80" step="1" value="5">

      <label>Copper lid thickness <span class="val" id="v_lid">2 mm</span></label>
      <input type="range" id="lid_thickness_mm" min="0.5" max="5" step="0.1" value="2">

      <label>TIM2 thickness (lid&rarr;plate) <span class="val" id="v_t2t">100 &micro;m</span></label>
      <input type="range" id="tim2_thickness_um" min="10" max="300" step="5" value="100">

      <label>TIM2 conductivity <span class="val" id="v_t2k">5 W/mK</span></label>
      <input type="range" id="tim2_conductivity" min="1" max="80" step="1" value="5">

      <label>Cold-plate base thickness <span class="val" id="v_pb">2 mm</span></label>
      <input type="range" id="plate_base_mm" min="0.5" max="5" step="0.1" value="2">

      <label>Cold-plate footprint edge <span class="val" id="v_ps">40 mm</span></label>
      <input type="range" id="plate_size_mm" min="10" max="80" step="1" value="40">

      <label>Fin / microchannel area gain <span class="val" id="v_fin">12&times;</span></label>
      <input type="range" id="fin_enhancement" min="1" max="30" step="1" value="12">

      <div class="grouphdr">Pump/fan &amp; budget</div>

      <label>Pump / fan efficiency <span class="val" id="v_pe">50 %</span></label>
      <input type="range" id="pump_eff_pct" min="10" max="90" step="1" value="50">

      <label>Cooling overhead budget <span class="val" id="v_ob">5 % of TDP</span></label>
      <input type="range" id="overhead_budget_pct" min="1" max="20" step="0.5" value="5">

      <button class="run" id="run">Run simulation</button>
    </form>

    <!-- ============ results ============ -->
    <div class="card">
      <div class="viz" id="viz1"><svg viewBox="0 0 600 360" id="svg1"></svg></div>
      <div class="vizcap"><b>Junction temperature</b> vs <b>coolant flow</b> &mdash; Tj falls as flow rises (the film and caloric resistances drop with flow). The dashed red line is the <b>Tj&nbsp;max</b> limit: the curve must stay below it. The marker (&#9679;) is your operating point &mdash; below the line the chip is safe, above it it overheats</div>
      <div class="viz" id="viz2"><svg viewBox="0 0 600 240" id="svg2"></svg></div>
      <div class="vizcap">Left: <b>Tj</b> vs <b>chip power</b> &mdash; junction temperature climbs linearly with TDP; it crosses the dashed red <b>Tj&nbsp;max</b> line at the power ceiling (marker &#9679; = your TDP). Right: the <b>thermal resistance stack</b> &mdash; each element as a share of theta_JA; the amber bar is the dominant resistance to attack first</div>
      <div id="err" class="err" style="display:none"></div>
      <div id="verdict" class="verdict" style="display:none"></div>
      <table id="metrics"><tbody></tbody></table>
      <div class="node" id="nodeline"></div>
    </div>
  </div>

  <!-- ============ knowledge-base explanation ============ -->
  <div class="card kb" id="kb" style="display:none">
    <h2>What these results mean</h2>
    <p class="src">Explanatory text pulled live from the <a href="/chat/">CFS knowledge base</a>.</p>
    <div class="kb-body" id="kbBody"></div>
  </div>

  <div class="api">
    <b>Developer API</b> — same simulation over HTTP (load-balanced across the pool):
    <pre>curl -X POST https://www.chipfoundryservices.com/edge/cooling \
  -H "Content-Type: application/json" \
  -d '{"tdp_w":700,"die_size_mm":26,"coolant_inlet_c":30,"tj_max_c":105,
       "cooling_mode":"cold_plate","coolant":"water","flow_rate_lpm":2,"air_flow_cfm":60,
       "tim1_thickness_um":50,"tim1_conductivity":5,"lid_thickness_mm":2,
       "tim2_thickness_um":100,"tim2_conductivity":5,"plate_base_mm":2,"plate_size_mm":40,
       "fin_enhancement":12,"pump_eff_pct":50,"overhead_budget_pct":5}'</pre>
    Returns JSON with <code>outputs</code> (theta_ja_c_w, r_tim1_c_w, r_lid_c_w, r_tim2_c_w, r_plate_c_w,
    r_conduction_c_w, r_convection_c_w, r_caloric_c_w, dominant_resistance, junction_temp_c, tj_headroom_c,
    meets_thermal, coolant_rise_c, coolant_outlet_c, flow_display, flow_unit, mass_flow_kg_s, film_coeff_w_m2k,
    wetted_area_cm2, pressure_drop_kpa, pump_power_w, cooling_overhead_pct, meets_efficiency, meets_budget,
    verdict), the full <code>profile</code> (48-point <code>tj_vs_flow</code> sweep
    <code>[flow, junction_temp_c]</code> and 48-point <code>tj_vs_tdp</code> sweep
    <code>[tdp_w, junction_temp_c]</code>), the serving <code>node</code>, and <code>compute_ms</code>.
    Endpoint aliases <code>/edge/coldplate</code>, <code>/edge/liquidcooling</code>, <code>/edge/heatsink</code>,
    <code>/edge/immersion</code>, <code>/edge/twophase</code>, <code>/edge/coolingloop</code>,
    <code>/edge/datacentercooling</code>.
  </div>
</div>

<script>
const $ = id => document.getElementById(id);
const fields = ["tdp_w","die_size_mm","coolant_inlet_c","tj_max_c","flow_rate_lpm","air_flow_cfm","tim1_thickness_um","tim1_conductivity","lid_thickness_mm","tim2_thickness_um","tim2_conductivity","plate_base_mm","plate_size_mm","fin_enhancement","pump_eff_pct","overhead_budget_pct"];
const labels = {tdp_w:"v_tdp", die_size_mm:"v_die", coolant_inlet_c:"v_inlet", tj_max_c:"v_tjmax", flow_rate_lpm:"v_flow", air_flow_cfm:"v_cfm", tim1_thickness_um:"v_t1t", tim1_conductivity:"v_t1k", lid_thickness_mm:"v_lid", tim2_thickness_um:"v_t2t", tim2_conductivity:"v_t2k", plate_base_mm:"v_pb", plate_size_mm:"v_ps", fin_enhancement:"v_fin", pump_eff_pct:"v_pe", overhead_budget_pct:"v_ob"};

function syncLabels(){
  for(const f of fields){
    let v = parseFloat($(f).value), out;
    if(f==="tdp_w")                    out = v.toFixed(0) + " W";
    else if(f==="die_size_mm")         out = v.toFixed(0) + " mm";
    else if(f==="coolant_inlet_c")     out = v.toFixed(0) + " &deg;C";
    else if(f==="tj_max_c")            out = v.toFixed(0) + " &deg;C";
    else if(f==="flow_rate_lpm")       out = v.toFixed(1) + " LPM";
    else if(f==="air_flow_cfm")        out = v.toFixed(0) + " CFM";
    else if(f==="tim1_thickness_um")   out = v.toFixed(0) + " &micro;m";
    else if(f==="tim1_conductivity")   out = v.toFixed(0) + " W/mK";
    else if(f==="lid_thickness_mm")    out = v.toFixed(1) + " mm";
    else if(f==="tim2_thickness_um")   out = v.toFixed(0) + " &micro;m";
    else if(f==="tim2_conductivity")   out = v.toFixed(0) + " W/mK";
    else if(f==="plate_base_mm")       out = v.toFixed(1) + " mm";
    else if(f==="plate_size_mm")       out = v.toFixed(0) + " mm";
    else if(f==="fin_enhancement")     out = v.toFixed(0) + "&times;";
    else if(f==="pump_eff_pct")        out = v.toFixed(0) + " %";
    else if(f==="overhead_budget_pct") out = v.toFixed(1) + " % of TDP";
    else out = v;
    $(labels[f]).innerHTML = out;
  }
}
fields.forEach(f => $(f).addEventListener("input", syncLabels));
syncLabels();

const presets = {
  coldplate: {tdp_w:700,  die_size_mm:26, coolant_inlet_c:30, tj_max_c:105, flow_rate_lpm:2,  air_flow_cfm:60,  tim1_thickness_um:50,  tim1_conductivity:5,  lid_thickness_mm:2, tim2_thickness_um:100, tim2_conductivity:5,  plate_base_mm:2, plate_size_mm:40, fin_enhancement:12, pump_eff_pct:50, overhead_budget_pct:5,  cooling_mode:"cold_plate", coolant:"water"},
  twophase:  {tdp_w:1200, die_size_mm:30, coolant_inlet_c:35, tj_max_c:105, flow_rate_lpm:3,  air_flow_cfm:60,  tim1_thickness_um:50,  tim1_conductivity:8,  lid_thickness_mm:2, tim2_thickness_um:100, tim2_conductivity:8,  plate_base_mm:2, plate_size_mm:45, fin_enhancement:18, pump_eff_pct:55, overhead_budget_pct:6,  cooling_mode:"two_phase",  coolant:"dielectric"},
  air:       {tdp_w:150,  die_size_mm:20, coolant_inlet_c:25, tj_max_c:100, flow_rate_lpm:2,  air_flow_cfm:300, tim1_thickness_um:50,  tim1_conductivity:8,  lid_thickness_mm:2, tim2_thickness_um:100, tim2_conductivity:8,  plate_base_mm:2, plate_size_mm:60, fin_enhancement:30, pump_eff_pct:45, overhead_budget_pct:10, cooling_mode:"air",        coolant:"water"},
};
document.querySelectorAll(".preset").forEach(el=>{
  el.onclick = () => { const p = presets[el.dataset.p]; for(const k in p){ if(k==="cooling_mode"||k==="coolant") $(k).value=p[k]; else if($(k)) $(k).value = p[k]; } syncLabels(); run(); };
});

function fmt(n, d=1){ return Number(n).toLocaleString(undefined,{maximumFractionDigits:d}); }

// ================= datacenter cooling-loop drawing block =================

// ---- viz 1: junction temp (C) vs coolant flow ----
function drawTempFlow(r){
  const o = r.outputs, p = r.profile, inp = r.inputs;
  const fv = p.tj_vs_flow;                            // [[flow, Tj_C], ...]
  const tjmax = p.tj_max_c, inlet = p.coolant_inlet_c;
  const unit = o.flow_unit || "LPM";
  const VW=600, VH=360, padL=54, padR=150, padT=20, padB=46;
  const xmin = fv[0][0], xmax = fv[fv.length-1][0];
  let ymax=-1e9, ymin=1e9;
  fv.forEach(d => { ymax=Math.max(ymax,d[1]); ymin=Math.min(ymin,d[1]); });
  ymax = Math.max(ymax, tjmax, o.junction_temp_c);
  ymin = Math.min(ymin, inlet, o.junction_temp_c);
  const span = (ymax-ymin) || 1;
  ymax += span*0.1; ymin -= span*0.08;
  const X = v => padL + (v-xmin)/(xmax-xmin) * (VW-padL-padR);
  const Y = v => padT + (1 - (Math.min(Math.max(v,ymin),ymax)-ymin)/(ymax-ymin)) * (VH-padT-padB);
  const parts = [];
  for(let i=0;i<=5;i++){
    const gv = xmin + (xmax-xmin)*i/5, xx = X(gv);
    parts.push(`<line x1="${xx.toFixed(1)}" y1="${padT}" x2="${xx.toFixed(1)}" y2="${(VH-padB).toFixed(1)}" stroke="#1e2e4a" stroke-width="1"/>`);
    parts.push(`<text x="${xx.toFixed(1)}" y="${(VH-padB+13).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="middle">${fmt(gv,1)}</text>`);
  }
  for(let i=0;i<=4;i++){
    const gv = ymin + (ymax-ymin)*i/4, yy = Y(gv);
    parts.push(`<line x1="${padL}" y1="${yy.toFixed(1)}" x2="${(VW-padR).toFixed(1)}" y2="${yy.toFixed(1)}" stroke="#1e2e4a" stroke-width="1"/>`);
    parts.push(`<text x="${(padL-6).toFixed(1)}" y="${(yy+3).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="end">${gv.toFixed(0)}</text>`);
  }
  parts.push(`<line x1="${padL}" y1="${padT}" x2="${padL}" y2="${(VH-padB).toFixed(1)}" stroke="#334d70" stroke-width="1"/>`);
  parts.push(`<line x1="${padL}" y1="${(VH-padB).toFixed(1)}" x2="${(VW-padR).toFixed(1)}" y2="${(VH-padB).toFixed(1)}" stroke="#334d70" stroke-width="1"/>`);
  // Tj_max threshold (dashed red)
  parts.push(`<line x1="${padL}" y1="${Y(tjmax).toFixed(1)}" x2="${(VW-padR).toFixed(1)}" y2="${Y(tjmax).toFixed(1)}" stroke="#c0392b" stroke-width="1.4" stroke-dasharray="5 3"/>`);
  parts.push(`<text x="${(padL+4).toFixed(1)}" y="${(Y(tjmax)-4).toFixed(1)}" fill="#c0392b" font-size="9">Tj max ${fmt(tjmax,0)} &#176;C &mdash; overheats above</text>`);
  // Tj curve (green)
  { let d=""; fv.forEach((pt,i)=>{ d += (i?"L":"M") + X(pt[0]).toFixed(1) + " " + Y(pt[1]).toFixed(1) + " "; });
    parts.push(`<path d="${d}" fill="none" stroke="#07C160" stroke-width="2.8"/>`); }
  // operating point at current flow
  const xop = Math.min(Math.max(o.flow_display, xmin), xmax);
  const fail = !o.meets_thermal;
  const opColor = fail ? "#c0392b" : "#16181d";
  const yop = Y(o.junction_temp_c);
  parts.push(`<line x1="${X(xop).toFixed(1)}" y1="${padT}" x2="${X(xop).toFixed(1)}" y2="${(VH-padB).toFixed(1)}" stroke="${opColor}" stroke-width="1" stroke-dasharray="3 3"/>`);
  parts.push(`<circle cx="${X(xop).toFixed(1)}" cy="${yop.toFixed(1)}" r="5" fill="${opColor}"/>`);
  parts.push(`<text x="${(X(xop)+8).toFixed(1)}" y="${(yop-6).toFixed(1)}" fill="${opColor}" font-size="10" font-weight="700">${fmt(o.junction_temp_c,1)} &#176;C @ ${fmt(o.flow_display,1)} ${unit}</text>`);
  // legend
  const lx = VW-padR+12; let ly = padT+8;
  const line = (color,dash,txt,wd) => { parts.push(`<line x1="${lx}" y1="${ly}" x2="${lx+20}" y2="${ly}" stroke="${color}" stroke-width="${wd||2.4}" ${dash?'stroke-dasharray="4 3"':''}/>`); parts.push(`<text x="${lx+26}" y="${ly+3}" fill="#94a3b8" font-size="10">${txt}</text>`); ly+=17; };
  line("#07C160", false, "Tj", 2.8);
  line("#c0392b", true, "Tj max");
  ly += 6;
  const kv = (k,v,col) => { parts.push(`<text x="${lx}" y="${ly+3}" fill="#94a3b8" font-size="10">${k}</text>`); ly+=13; parts.push(`<text x="${lx}" y="${ly+3}" fill="${col||'#f1f5f9'}" font-size="12" font-weight="700">${v}</text>`); ly+=20; };
  kv("Tj", fmt(o.junction_temp_c,1)+" &#176;C", fail?"#c0392b":"#34d399");
  kv("headroom", fmt(o.tj_headroom_c,1)+" &#176;C", fail?"#c0392b":"#34d399");
  kv("theta_JA", fmt(o.theta_ja_c_w,4)+" &#176;C/W", "#16181d");
  kv("meets thermal", o.meets_thermal?"yes":"no", fail?"#c0392b":"#34d399");
  // axis labels
  parts.push(`<text x="${((padL+VW-padR)/2).toFixed(1)}" y="${(VH-4).toFixed(1)}" fill="#94a3b8" font-size="11" text-anchor="middle">coolant flow (${unit})</text>`);
  parts.push(`<text x="14" y="${((padT+VH-padB)/2).toFixed(1)}" fill="#94a3b8" font-size="11" text-anchor="middle" transform="rotate(-90 14 ${((padT+VH-padB)/2).toFixed(1)})">junction temp (&#176;C)</text>`);
  $("viz1").innerHTML = '<svg viewBox="0 0 '+VW+' '+VH+'" width="100%" xmlns="http://www.w3.org/2000/svg" id="svg1">'+parts.join("")+'</svg>';
}

// ---- horizontal bars: value as a share of a limit ----
function shareBars(parts, box){
  return function(items){
    const n = items.length, gap = 10;
    const bh = (box.h - gap*(n-1)) / n;
    items.forEach((it,i)=>{
      const y = box.y0 + i*(bh+gap);
      const frac = Math.max(0, Math.min(1, it.v/100));
      parts.push(`<text x="${box.x0}" y="${(y+bh*0.5-2).toFixed(1)}" fill="#16181d" font-size="10" font-weight="600">${it.k}</text>`);
      parts.push(`<rect x="${box.x0}" y="${(y+bh*0.5+2).toFixed(1)}" width="${box.w.toFixed(1)}" height="9" rx="4.5" fill="#eef1f4"/>`);
      parts.push(`<rect x="${box.x0}" y="${(y+bh*0.5+2).toFixed(1)}" width="${(box.w*frac).toFixed(1)}" height="9" rx="4.5" fill="${it.color}"/>`);
      parts.push(`<text x="${(box.x0+box.w).toFixed(1)}" y="${(y+bh*0.5-2).toFixed(1)}" fill="${it.color}" font-size="10.5" font-weight="700" text-anchor="end">${it.lab}</text>`);
    });
  };
}

// ---- viz 2: Tj vs TDP (left) + thermal resistance stack (right) ----
function drawTdpResist(r){
  const o = r.outputs, p = r.profile, inp = r.inputs;
  const tv = p.tj_vs_tdp;                              // [[tdp_w, Tj_C], ...]
  const tjmax = p.tj_max_c;
  const VW=600, VH=240, parts = [];
  const gap=48, w=(VW-gap*3)/2, h=150, y0=34;
  // ---- left panel: Tj vs TDP ----
  const bx = {x0:gap, y0:y0, w:w, h:h};
  const dmin = tv[0][0], dmax = tv[tv.length-1][0];
  let yhi=-1e9, ylo=1e9;
  tv.forEach(d => { yhi=Math.max(yhi,d[1]); ylo=Math.min(ylo,d[1]); });
  yhi = Math.max(yhi, tjmax, o.junction_temp_c);
  ylo = Math.min(ylo, o.junction_temp_c);
  const sp = (yhi-ylo) || 1; yhi += sp*0.12; ylo -= sp*0.12;
  const X = v => bx.x0 + ((v-dmin)/(dmax-dmin)) * bx.w;
  const Y = v => bx.y0 + (1 - (Math.min(Math.max(v,ylo),yhi)-ylo)/(yhi-ylo)) * bx.h;
  parts.push(`<text x="${(bx.x0+bx.w/2).toFixed(1)}" y="${(y0-4).toFixed(1)}" fill="#ffffff" font-size="11" font-weight="700" text-anchor="middle">Tj vs chip power</text>`);
  for(let i=0;i<=4;i++){
    const gv = dmin + (dmax-dmin)*i/4, xx = X(gv);
    parts.push(`<line x1="${xx.toFixed(1)}" y1="${bx.y0}" x2="${xx.toFixed(1)}" y2="${(bx.y0+bx.h).toFixed(1)}" stroke="#1e2e4a" stroke-width="1"/>`);
    parts.push(`<text x="${xx.toFixed(1)}" y="${(bx.y0+bx.h+11).toFixed(1)}" fill="#94a3b8" font-size="8" text-anchor="middle">${fmt(gv,0)}</text>`);
  }
  for(let i=0;i<=3;i++){
    const gv = ylo + (yhi-ylo)*i/3, yy = Y(gv);
    parts.push(`<line x1="${bx.x0}" y1="${yy.toFixed(1)}" x2="${(bx.x0+bx.w).toFixed(1)}" y2="${yy.toFixed(1)}" stroke="#1e2e4a" stroke-width="1"/>`);
    parts.push(`<text x="${(bx.x0-4).toFixed(1)}" y="${(yy+3).toFixed(1)}" fill="#94a3b8" font-size="8" text-anchor="end">${gv.toFixed(0)}</text>`);
  }
  parts.push(`<line x1="${bx.x0}" y1="${bx.y0}" x2="${bx.x0}" y2="${(bx.y0+bx.h).toFixed(1)}" stroke="#334d70" stroke-width="1"/>`);
  parts.push(`<line x1="${bx.x0}" y1="${(bx.y0+bx.h).toFixed(1)}" x2="${(bx.x0+bx.w).toFixed(1)}" y2="${(bx.y0+bx.h).toFixed(1)}" stroke="#334d70" stroke-width="1"/>`);
  // Tj_max line (dashed red)
  if(tjmax>=ylo && tjmax<=yhi){
    parts.push(`<line x1="${bx.x0}" y1="${Y(tjmax).toFixed(1)}" x2="${(bx.x0+bx.w).toFixed(1)}" y2="${Y(tjmax).toFixed(1)}" stroke="#c0392b" stroke-width="1.3" stroke-dasharray="4 3"/>`);
    parts.push(`<text x="${(bx.x0+bx.w-2).toFixed(1)}" y="${(Y(tjmax)-3).toFixed(1)}" fill="#c0392b" font-size="8.5" text-anchor="end">Tj max</text>`);
  }
  // Tj curve (green)
  { let d=""; tv.forEach((pt,i)=>{ d += (i?"L":"M") + X(pt[0]).toFixed(1) + " " + Y(pt[1]).toFixed(1) + " "; });
    parts.push(`<path d="${d}" fill="none" stroke="#07C160" stroke-width="2.4"/>`); }
  const dop = Math.min(Math.max(inp.tdp_w, dmin), dmax);
  const tfail = !o.meets_thermal;
  const c = tfail ? "#c0392b" : "#16181d";
  parts.push(`<line x1="${X(dop).toFixed(1)}" y1="${bx.y0}" x2="${X(dop).toFixed(1)}" y2="${(bx.y0+bx.h).toFixed(1)}" stroke="${c}" stroke-width="1" stroke-dasharray="3 3"/>`);
  parts.push(`<circle cx="${X(dop).toFixed(1)}" cy="${Y(o.junction_temp_c).toFixed(1)}" r="4.5" fill="${c}"/>`);
  parts.push(`<text x="${(bx.x0+bx.w/2).toFixed(1)}" y="${(bx.y0+bx.h+24).toFixed(1)}" fill="#94a3b8" font-size="10" text-anchor="middle">chip power (W) &rarr; hotter junction</text>`);
  // ---- right panel: thermal resistance stack ----
  const rx = gap*2+w;
  parts.push(`<text x="${(rx+w/2).toFixed(1)}" y="${(y0-4).toFixed(1)}" fill="#ffffff" font-size="11" font-weight="700" text-anchor="middle">Thermal resistance stack</text>`);
  const total = o.theta_ja_c_w || 1e-9;
  const stack = [
    {k:"TIM1 (die-lid)",   name:"TIM1 (die-lid)",   v:o.r_tim1_c_w},
    {k:"lid / IHS",        name:"lid/IHS",          v:o.r_lid_c_w},
    {k:"TIM2 (lid-plate)", name:"TIM2 (lid-plate)", v:o.r_tim2_c_w},
    {k:"plate base",       name:"plate base",       v:o.r_plate_c_w},
    {k:"convection",       name:"convection",       v:o.r_convection_c_w},
    {k:"coolant rise",     name:"coolant rise",     v:o.r_caloric_c_w},
  ];
  const dom = o.dominant_resistance;
  const items = stack.map(s => {
    const pct = 100*s.v/total;
    return {k:s.k, v:pct, lab:fmt(pct,0)+"%", color: (s.name===dom) ? "#9a6b00" : "#3f78c4"};
  });
  shareBars(parts, {x0:rx, y0:y0+4, w:w, h:h-4})(items);
  parts.push(`<text x="${(rx+w/2).toFixed(1)}" y="${(y0+h+24).toFixed(1)}" fill="#94a3b8" font-size="10" text-anchor="middle">amber = dominant resistance (attack first)</text>`);
  $("viz2").innerHTML = '<svg viewBox="0 0 '+VW+' '+VH+'" width="100%" xmlns="http://www.w3.org/2000/svg" id="svg2">'+parts.join("")+'</svg>';
}
// ================= end datacenter cooling-loop block =================

function showMetrics(r){
  const o = r.outputs, inp = r.inputs;
  const modeLabel = {cold_plate:"liquid cold plate", two_phase:"two-phase / immersion", air:"air"}[inp.cooling_mode] || inp.cooling_mode;
  const rows = [
    ["Chip TDP", fmt(inp.tdp_w,0)+" W ("+modeLabel+", "+inp.coolant+")"],
    ["Junction temperature Tj", fmt(o.junction_temp_c,2)+" &deg;C"],
    ["Tj headroom", fmt(o.tj_headroom_c,2)+" &deg;C (limit "+fmt(inp.tj_max_c,0)+" &deg;C)"],
    ["Meets thermal", o.meets_thermal ? "yes" : "no &mdash; overheats"],
    ["theta_JA (junction&rarr;ambient)", fmt(o.theta_ja_c_w,4)+" &deg;C/W"],
    ["&nbsp;&nbsp;TIM1 (die&rarr;lid)", fmt(o.r_tim1_c_w,4)+" &deg;C/W"],
    ["&nbsp;&nbsp;lid / IHS", fmt(o.r_lid_c_w,4)+" &deg;C/W"],
    ["&nbsp;&nbsp;TIM2 (lid&rarr;plate)", fmt(o.r_tim2_c_w,4)+" &deg;C/W"],
    ["&nbsp;&nbsp;cold-plate base", fmt(o.r_plate_c_w,4)+" &deg;C/W"],
    ["&nbsp;&nbsp;convective film", fmt(o.r_convection_c_w,4)+" &deg;C/W"],
    ["&nbsp;&nbsp;caloric (coolant rise)", fmt(o.r_caloric_c_w,4)+" &deg;C/W"],
    ["Dominant resistance", o.dominant_resistance],
    ["Coolant flow", fmt(o.flow_display,2)+" "+o.flow_unit+" ("+fmt(o.mass_flow_kg_s,4)+" kg/s)"],
    ["Film coefficient", fmt(o.film_coeff_w_m2k,0)+" W/m&sup2;K"],
    ["Wetted area", fmt(o.wetted_area_cm2,1)+" cm&sup2;"],
    ["Coolant temp rise", fmt(o.coolant_rise_c,2)+" &deg;C (outlet "+fmt(o.coolant_outlet_c,1)+" &deg;C)"],
    ["Pressure drop", fmt(o.pressure_drop_kpa,2)+" kPa"],
    ["Pump / fan power", fmt(o.pump_power_w,2)+" W"],
    ["Cooling overhead", fmt(o.cooling_overhead_pct,2)+" % of TDP"],
    ["Meets efficiency", o.meets_efficiency ? "yes" : "no &mdash; over budget"],
    ["Meets budget (both gates)", o.meets_budget ? "yes" : "no"],
  ];
  $("metrics").innerHTML = "<thead><tr><th>Metric</th><th style='text-align:right'>Value</th></tr></thead><tbody>"
    + rows.map(x=>`<tr><td>${x[0]}</td><td class="num">${x[1]}</td></tr>`).join("") + "</tbody>";

  const v = $("verdict");
  v.style.display = "block";
  v.textContent = "Result: " + o.verdict;
  if(/^Clean/.test(o.verdict)){ v.style.background="#e7f7ee"; v.style.color="#34d399"; }
  else if(/^Marginal/.test(o.verdict)){ v.style.background="#fff6e6"; v.style.color="#9a6b00"; }
  else { v.style.background="#fdecea"; v.style.color="#c0392b"; }

  $("nodeline").innerHTML = `Computed on <b>professional Chip Foundry Services</b> compute in ${fmt(r.compute_ms,0)} ms &middot; ${r.steps} sweep steps &middot; <span style="opacity:.8">${r.model}</span>`;
}

// ---- professional flowchart renderer (ported from the site's chat) ----
const escapeHtml = (t) => String(t).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]));
const applyBold = (raw) => escapeHtml(String(raw)).replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
const renderFlowchart = (jsonStr) => {
    let spec;
    try { spec = JSON.parse(jsonStr); } catch (e) { return `<pre><code>${escapeHtml(jsonStr)}</code></pre>`; }
    const rows = (spec && spec.rows) || [];
    const arrowSvg = '<svg viewBox="0 0 18 30" fill="none"><path d="M9 0 V24" stroke="currentColor" stroke-width="1.6"/><path d="M3 18 L9 26 L15 18" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
    const connector = (count) => {
        if (!count || count <= 1) return `<div class="cfs-arrow">${arrowSvg}</div>`;
        let a = '<div class="cfs-arrow-row">';
        for (let k = 0; k < count; k++) a += `<div class="cfs-arrow-col">${arrowSvg}</div>`;
        return a + '</div>';
    };
    const node = (n) => {
        const tone = ['neutral', 'green', 'orange', 'blue'].indexOf(n.tone) >= 0 ? n.tone : 'neutral';
        const sub = n.sub ? `<span class="cfs-node-sub">${applyBold(String(n.sub))}</span>` : '';
        return `<div class="cfs-node ${tone}"><span class="cfs-node-title">${applyBold(String(n.title || ''))}</span>${sub}</div>`;
    };
    let out = '<div class="cfs-flow">';
    let prevType = null, prevCount = 0;
    rows.forEach((r, idx) => {
        if (r.type === 'arrow') { out += connector(prevCount); prevType = 'arrow'; return; }
        if (idx > 0 && r.type !== 'tier' && (prevType === 'nodes' || prevType === 'group')) out += connector(prevCount);
        if (r.type === 'tier') {
            out += '<div class="cfs-tier">';
            out += `<div class="cfs-tier-h">${applyBold(String(r.title || ''))}</div>`;
            out += '<div class="cfs-tier-row">';
            (r.items || []).forEach(n => { out += node(n); });
            out += '</div></div>';
            prevType = 'tier'; prevCount = 0;
            return;
        }
        if (r.type === 'group') {
            out += '<div class="cfs-group"><div class="cfs-group-head">';
            out += `<div class="cfs-group-title">${applyBold(String(r.title || ''))}</div>`;
            if (r.note) out += `<div class="cfs-group-note">${applyBold(String(r.note))}</div>`;
            out += '</div><div class="cfs-group-grid">';
            (r.items || []).forEach(n => { out += node(n); });
            out += '</div>';
            if (r.loop) out += `<div class="cfs-group-loop">${applyBold(String(r.loop))}</div>`;
            out += '</div>';
            prevType = 'group'; prevCount = 1;
            return;
        }
        out += '<div class="cfs-flow-row">';
        (r.items || []).forEach(n => { out += node(n); });
        out += '</div>';
        prevType = 'nodes'; prevCount = (r.items || []).length;
    });
    out += '</div>';
    return out;
};

// ---- minimal, safe markdown -> HTML for KB text ----
function mdToHtml(s){
  s = String(s);
  s = s.replace(/\\r\\n|\\r|\\n/g, "\n").replace(/\\t/g, "  ");
  const raw = [];
  const RSENT = "\u0000R";
  s = s.replace(/```flowchart[ \t]*\n([\s\S]*?)```/gi, (_, code) => {
    const i = raw.length; raw.push(renderFlowchart(code.trim())); return "\n\n" + RSENT + i + "\u0000\n\n";
  });
  s = s.replace(/```(?:svg|html)[ \t]*\n([\s\S]*?)```/gi, (_, code) => {
    const i = raw.length; raw.push(code.trim()); return "\n\n" + RSENT + i + "\u0000\n\n";
  });
  s = s.replace(/<svg[\s\S]*?<\/svg>/gi, (m) => {
    const i = raw.length; raw.push(m); return "\n\n" + RSENT + i + "\u0000\n\n";
  });
  const math = [];
  const SENT = "\u0000M";
  const patterns = [/\$\$[\s\S]+?\$\$/g, /\\\[[\s\S]+?\\\]/g, /\\\([\s\S]+?\\\)/g, /\$[^\$\n]+?\$/g];
  for(const re of patterns){
    s = s.replace(re, m => { const i = math.length; math.push(m); return SENT + i + "\u0000"; });
  }
  s = s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  const blocks = s.split(/\n\n+/).map(b=>{
    b = b.trim(); if(!b) return "";
    const _tl = b.split(/\n/).map(l=>l.trim()).filter(l=>l);
    if(_tl.length>=2 && _tl.every(l=>l.startsWith("|")) && /-/.test(_tl[1]) && /^\|?[\s:|-]+\|?$/.test(_tl[1])){
      const _bold = c => c.replace(/\*\*(.+?)\*\*/g,"<b>$1</b>");
      const _cells = l => l.replace(/^\|/,"").replace(/\|$/,"").split("|").map(c=>_bold(c.trim()));
      const _head = _cells(_tl[0]).map(c=>`<th>${c}</th>`).join("");
      const _rows = _tl.slice(2).map(l=>"<tr>"+_cells(l).map(c=>`<td>${c}</td>`).join("")+"</tr>").join("");
      return `<div class="kb-tablewrap"><table class="kb-table"><thead><tr>${_head}</tr></thead><tbody>${_rows}</tbody></table></div>`;
    }
    b = b.replace(/\*\*(.+?)\*\*/g,"<b>$1</b>");
    if(/^-\s/m.test(b) && b.split(/\n/).every(l=>/^-\s/.test(l.trim())||l.trim()==="")){
      const items = b.split(/\n/).filter(l=>l.trim()).map(l=>`<li>${l.replace(/^-\s*/,"")}</li>`).join("");
      return `<ul>${items}</ul>`;
    }
    return `<p>${b.replace(/\n/g,"<br>")}</p>`;
  });
  let html = blocks.join("");
  html = html.replace(/\u0000M(\d+)\u0000/g, (_, i) => {
    return math[+i].replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  });
  html = html.replace(/<p>\s*\u0000R(\d+)\u0000\s*<\/p>/g, (_, i) => `<figure class="kb-figure">${raw[+i]}</figure>`);
  html = html.replace(/\u0000R(\d+)\u0000/g, (_, i) => `<figure class="kb-figure">${raw[+i]}</figure>`);
  return html;
}

function typesetKB(){
  const el = document.getElementById("kbBody");
  if(!el) return;
  if(window.MathJax && MathJax.typesetPromise){ MathJax.typesetPromise([el]).catch(()=>{}); }
  else { setTimeout(typesetKB, 200); }
}

async function loadKB(r){
  const queries = [["Thermal management","thermal management"], ["Advanced packaging","advanced packaging"]];
  const seen = {};
  const out = [];
  for(const [title,q] of queries){
    try{
      const res = await fetch("/api.php?q=" + encodeURIComponent(q));
      const j = await res.json();
      let txt = (j && j.response) ? j.response.trim() : "";
      if(!txt) continue;
      const key = txt.slice(0,60);
      if(seen[key]) continue; seen[key] = 1;
      out.push(`<div class="kb-block">${mdToHtml(txt)}</div>`);
    }catch(e){ /* KB optional — skip on failure */ }
  }
  if(out.length){
    $("kbBody").innerHTML = out.join("");
    $("kb").style.display = "block"; typesetKB();
  }
}

async function run(){
  const btn = $("run"); btn.disabled = true; btn.textContent = "Running on pool…";
  $("err").style.display = "none";
  const body = {};
  for(const f of fields) body[f] = parseFloat($(f).value);
  body.cooling_mode = $("cooling_mode").value;
  body.coolant = $("coolant").value;
  try{
    const res = await fetch("/edge/cooling", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(body)});
    if(!res.ok) throw new Error("HTTP "+res.status);
    const r = await res.json();
    if(r.error) throw new Error(r.error);
    drawTempFlow(r); drawTdpResist(r); showMetrics(r);
    loadKB(r);
  }catch(e){
    $("err").style.display = "block";
    $("err").textContent = "Simulation failed: " + e.message + " (the compute pool may be temporarily unavailable — try again).";
  }finally{
    btn.disabled = false; btn.textContent = "Run simulation";
  }
}
$("run").onclick = run;
run();   // run once on load with defaults
</script>
</body>
</html>
