<?php
// Chip power & thermal-budget simulator — customer-facing page.
// Heavy lifting runs on the ChipFoundryServices compute pool: this page POSTs
// the device geometry (transistor count, die area, switched capacitance), the
// operating point (core voltage, clock, activity factor, interconnect fraction)
// and the thermal/power envelope (leakage density, junction-to-ambient thermal
// resistance, TDP) to /edge/power (same-origin), NGINX routes it to the compute
// node, the node computes dynamic power (a·C·V²·f) plus interconnect power plus
// thermally-coupled leakage via a junction-temperature fixed-point solve,
// checks the design against TDP and Tjmax, detects thermal runaway, and finds
// the thermal-limited maximum clock, and returns JSON, and we render it here.
// 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>Chip Power &amp; Thermal-Budget Simulator — Dynamic aCV²f, Leakage, Junction Temp, TDP &amp; Max Clock | ChipFoundryServices</title>
<link rel="stylesheet" href="/cfs_theme.css?v=2">
<meta name="description" content="Run a chip power & thermal-budget simulation in your browser — computed on the ChipFoundryServices compute pool. Model where a processor's watts go: dynamic switching power (activity × capacitance × voltage² × frequency), interconnect/wire power, and thermally-coupled leakage that doubles roughly every 15 °C. Solve the junction-temperature fixed point (Tj = ambient + θ_JA × total power), check the design against its TDP and Tjmax, detect thermal runaway when the leakage–temperature feedback loop diverges, and find the thermal-limited maximum clock — the same dynamic-vs-leakage, voltage-vs-frequency trade-offs that set an AI accelerator's power envelope.">
<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); }
  .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;
            /* Anthropic/Claude.ai UI font — Styrene, NOT the serif answer body */
            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; }
        /* tones */
        .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; }
        /* connecting arrow */
        .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; }
        /* parallel arrows — one under each node column, matching the node row layout */
        .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; }
        /* tiered landscape row: labeled dashed band of peer cards, not a flow step */
        .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;
        }
        /* chip floorplan row: fixed-function blocks with memory feeder */
        .kb-body .cfs-die {
            border: 1px solid #3d3d3a; border-radius: 18px;
            background: rgba(255,255,255,0.03);
            padding: 20px 22px 22px; margin: 0;
        }
        .kb-body .cfs-die-h {
            text-align: center; font-size: 18px; font-weight: 600; color: #f2f1ec;
            font-family: 'Styrene B','Styrene A', ui-sans-serif, -apple-system, sans-serif;
        }
        .kb-body .cfs-die-note {
            text-align: center; font-size: 14px; color: #b8b7b1; margin: 2px 0 18px;
            font-family: 'Styrene B','Styrene A', ui-sans-serif, -apple-system, sans-serif;
        }
        .kb-body .cfs-die-top { display: flex; align-items: stretch; justify-content: center; gap: 0; }
        .kb-body .cfs-die-top .cfs-node { flex: 1 1 0; text-align: center; margin: 0; }
        .kb-body .cfs-die-in {
            display: flex; align-items: center; gap: 6px; white-space: nowrap;
            color: #b8b7b1; font-size: 14px; padding-right: 10px;
            font-family: 'Styrene B','Styrene A', ui-sans-serif, sans-serif;
        }
        .kb-body .cfs-die-ar, .kb-body .cfs-die-inar { display: flex; align-items: center; padding: 0 10px; color: #8a8880; }
        .kb-body .cfs-die-ar svg, .kb-body .cfs-die-inar svg { width: 34px; height: 16px; flex: 0 0 34px; }
        .kb-body .cfs-die-downs { display: flex; justify-content: space-around; padding: 6px 0 2px; }
        .kb-body .cfs-die-dar { display: flex; color: #8a8880; }
        .kb-body .cfs-die-dar svg { width: 16px; height: 28px; }
        .kb-body .cfs-die-base { margin-top: 2px; }
        .kb-body .cfs-die-base .cfs-node { width: 100%; max-width: 100%; text-align: center; margin: 0; }
        .kb-body .cfs-die-uar { display: flex; justify-content: center; padding: 4px 0; color: #8a8880; }
        .kb-body .cfs-die-uar svg { width: 16px; height: 28px; }
        .kb-body .cfs-die-feed {
            margin: 10px auto 0; max-width: 62%; text-align: center;
            padding: 12px 18px; background: #0f0f0e; border: 1px solid #3d3d3a;
            border-radius: 12px; color: #f2f1ec; font-weight: 600;
            font-family: 'Styrene B','Styrene A', ui-sans-serif, sans-serif;
        }
        /* grouped stage (fab loop) */
        .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;
        }
        /* loop caption sits as a centered footer BELOW the grid, not wedged between rows */
        .kb-body .cfs-group-loop {
            text-align: center;
            font-size: 13.5px;
            color: #c3bdf0;
            margin-top: 14px;
        }
        /* fab-loop CYCLE: 4 stages in a clockwise ring with internal arrows */
        .kb-body .cfs-cycle {
            display: grid;
            grid-template-columns: 1fr auto 1fr;
            grid-template-rows: auto auto auto;
            grid-template-areas:
                "tl ar tr"
                "lp x  dn"
                "bl al br";
            column-gap: 14px;
            row-gap: 10px;
            align-items: center;
        }
        .kb-body .cfs-cyc { display: flex; }
        .kb-body .cfs-cyc .cfs-node { width: 100%; max-width: 100%; }
        .kb-body .cfs-cyc.cyc-tl { grid-area: tl; }
        .kb-body .cfs-cyc.cyc-tr { grid-area: tr; }
        .kb-body .cfs-cyc.cyc-bl { grid-area: bl; }
        .kb-body .cfs-cyc.cyc-br { grid-area: br; }
        .kb-body .cfs-cyc-ar { display: flex; justify-content: center; align-items: center; color: #9c97c8; }
        .kb-body .cfs-cyc-ar.cyc-r { grid-area: ar; }
        .kb-body .cfs-cyc-ar.cyc-l { grid-area: al; }
        .kb-body .cfs-cyc-ar.cyc-d { grid-area: dn; }
        .kb-body .cfs-cyc-ar.cyc-r svg,
        .kb-body .cfs-cyc-ar.cyc-l svg { width: 30px; height: 16px; }
        .kb-body .cfs-cyc-ar.cyc-d svg { width: 16px; height: 28px; }
        .kb-body .cfs-cyc-loop {
            grid-area: lp;
            text-align: center;
            font-size: 13.5px;
            color: #c3bdf0;
        }
        @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-die-top { flex-direction: column; }
            .kb-body .cfs-die-top .cfs-node { width: 100%; }
            .kb-body .cfs-die-ar { transform: rotate(90deg); align-self: center; }
            .kb-body .cfs-die-in { justify-content: center; padding: 0 0 8px; }
            .kb-body .cfs-die-feed { 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 .cfs-cycle {
                grid-template-columns: 1fr;
                grid-template-areas: "tl" "tr" "br" "bl" "lp";
                row-gap: 12px;
            }
            .kb-body .cfs-cyc-ar { display: none; }
            .kb-body .cfs-cyc-loop { margin-top: 2px; }
        }

  .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>Chip Power &amp; Thermal-Budget Simulator</h1>
  <p class="lede">
    Model <b>where a processor&rsquo;s watts go</b> &mdash; then run it: the simulation executes on
    the ChipFoundryServices distributed compute pool. Every switching transistor burns <b>dynamic power</b>
    &asymp; <code>activity &times; C &times; V&sup2; &times; f</code> &mdash; quadratic in voltage, linear in clock &mdash;
    and the wires add <b>interconnect power</b> on top. But the design also leaks: <b>static leakage</b> rises
    with temperature and <b>doubles roughly every 15&nbsp;&deg;C</b>, so leakage heats the die, which raises
    leakage further. The node solves that <b>junction-temperature fixed point</b>
    (<code>Tj = ambient + &theta;<sub>JA</sub> &times; P<sub>total</sub></code>), checks the design against its
    <b>TDP</b> and <b>Tjmax</b>, flags <b>thermal runaway</b> when the leakage&ndash;temperature loop diverges, and
    finds the headline number: the <b>thermal-limited maximum clock</b> the cooling solution can sustain. These
    are the same dynamic-vs-leakage and voltage-vs-frequency trade-offs that set an <b>AI accelerator&rsquo;s power
    envelope</b>. Reduced-order educational model. See also the
    <a href="/training">training-memory</a>, <a href="/kvcache">KV-cache</a>, <a href="/hbm">HBM bandwidth</a>,
    <a href="/systolic">systolic array</a>, <a href="/transistor">transistor I-V</a>,
    <a href="/thermal">thermal</a>, <a href="/interconnect">interconnect RC</a>,
    <a href="/yield">die-yield</a>, <a href="/sram">6T SRAM</a>, <a href="/cmp">CMP planarization</a> and
    <a href="/lithography">lithography</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="datacenter">Data-center GPU · 700 W</span>
        <span class="preset" data-p="mobile">Mobile SoC · low-V</span>
        <span class="preset" data-p="runaway">Leakage runaway</span>
      </div>

      <label>Transistor count <span class="val" id="v_xtor">20 B</span></label>
      <input type="range" id="transistor_count_b" min="0.5" max="200" step="0.5" value="20">

      <label>Core voltage <span class="val" id="v_volt">0.85 V</span></label>
      <input type="range" id="core_voltage_v" min="0.5" max="1.2" step="0.01" value="0.85">

      <label>Clock frequency <span class="val" id="v_clock">2.0 GHz</span></label>
      <input type="range" id="clock_ghz" min="0.5" max="5.0" step="0.1" value="2.0">

      <label>Activity factor <span class="val" id="v_act">0.15</span></label>
      <input type="range" id="activity_factor" min="0.02" max="0.6" step="0.01" value="0.15">

      <label>Cap / transistor <span class="val" id="v_cap">0.10 fF</span></label>
      <input type="range" id="cap_per_transistor_ff" min="0.02" max="0.5" step="0.01" value="0.10">

      <label>Interconnect fraction <span class="val" id="v_wire">0.40</span></label>
      <input type="range" id="interconnect_fraction" min="0" max="1.5" step="0.05" value="0.40">

      <label>Die area <span class="val" id="v_area">600 mm²</span></label>
      <input type="range" id="die_area_mm2" min="10" max="900" step="10" value="600">

      <label>Leakage density <span class="val" id="v_leak">0.015 W/mm²</span></label>
      <input type="range" id="leakage_density_w_mm2" min="0.002" max="0.5" step="0.001" value="0.015">

      <label>Thermal resistance θ<sub>JA</sub> <span class="val" id="v_theta">0.06 °C/W</span></label>
      <input type="range" id="thermal_resistance_c_w" min="0.03" max="2.0" step="0.01" value="0.06">

      <label>TDP budget <span class="val" id="v_tdp">700 W</span></label>
      <input type="range" id="tdp_w" min="25" max="1500" step="25" value="700">

      <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">Total power vs clock frequency &mdash; dynamic power climbs linearly with clock, so raising the frequency drives the design toward its <b>TDP</b> ceiling (dashed red). Your operating clock (&#9679;) sits on the curve; the vertical marker is the <b>thermal-limited max clock</b> &mdash; the fastest the cooling solution can sustain under Tjmax and TDP</div>
      <div class="viz" id="viz2"><svg viewBox="0 0 600 240" id="svg2"></svg></div>
      <div class="vizcap">Left: total power vs core voltage &mdash; dynamic power is <b>quadratic in voltage</b>, so voltage is the biggest lever (dashed line = TDP). Right: the power budget &mdash; logic dynamic + interconnect + leakage as a share of TDP, and junction temperature as a share of Tjmax. Marker is your operating voltage</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/power \
  -H "Content-Type: application/json" \
  -d '{"transistor_count_b":20,"core_voltage_v":0.85,"clock_ghz":2.0,
       "activity_factor":0.15,"cap_per_transistor_ff":0.10,"interconnect_fraction":0.4,
       "die_area_mm2":600,"leakage_density_w_mm2":0.015,
       "thermal_resistance_c_w":0.06,"tdp_w":700}'</pre>
    Returns JSON with <code>outputs</code> (dynamic_power_w, logic_dynamic_w, interconnect_power_w,
    leakage_power_w, total_power_w, junction_temp_c, tjmax_c, tdp_w, power_density_w_mm2,
    leakage_fraction_percent, within_tdp, within_tjmax, thermal_runaway, max_clock_ghz,
    energy_per_cycle_nj, gops_per_w, verdict), the full <code>profile</code> (48-point
    <code>power_vs_freq</code> and <code>power_vs_volt</code> sweeps, plus <code>dynamic_w</code>,
    <code>leakage_w</code>, <code>tdp_w</code>, <code>total_w</code>, <code>clock_ghz</code>,
    <code>core_voltage_v</code>, <code>junction_temp_c</code>, <code>within_tdp</code>), the serving
    <code>node</code>, and <code>compute_ms</code>. Endpoint aliases <code>/edge/powerbudget</code>,
    <code>/edge/tdp</code>, <code>/edge/dvfs</code>, <code>/edge/leakage</code>.
  </div>
</div>

<script>
const $ = id => document.getElementById(id);
const fields = ["transistor_count_b","core_voltage_v","clock_ghz","activity_factor","cap_per_transistor_ff","interconnect_fraction","die_area_mm2","leakage_density_w_mm2","thermal_resistance_c_w","tdp_w"];
const labels = {transistor_count_b:"v_xtor", core_voltage_v:"v_volt", clock_ghz:"v_clock", activity_factor:"v_act", cap_per_transistor_ff:"v_cap", interconnect_fraction:"v_wire", die_area_mm2:"v_area", leakage_density_w_mm2:"v_leak", thermal_resistance_c_w:"v_theta", tdp_w:"v_tdp"};

function syncLabels(){
  for(const f of fields){
    let v = parseFloat($(f).value), out;
    if(f==="transistor_count_b")          out = (v % 1 === 0 ? v.toLocaleString() : v.toFixed(1)) + " B";
    else if(f==="core_voltage_v")         out = v.toFixed(2) + " V";
    else if(f==="clock_ghz")              out = v.toFixed(1) + " GHz";
    else if(f==="activity_factor")        out = v.toFixed(2);
    else if(f==="cap_per_transistor_ff")  out = v.toFixed(2) + " fF";
    else if(f==="interconnect_fraction")  out = v.toFixed(2);
    else if(f==="die_area_mm2")           out = v.toLocaleString() + " mm²";
    else if(f==="leakage_density_w_mm2")  out = v.toFixed(3) + " W/mm²";
    else if(f==="thermal_resistance_c_w") out = v.toFixed(2) + " °C/W";
    else if(f==="tdp_w")                  out = v.toLocaleString() + " W";
    else out = v;
    $(labels[f]).innerHTML = out;
  }
}
fields.forEach(f => $(f).addEventListener("input", syncLabels));
syncLabels();

const presets = {
  // transistor_count_b, core_voltage_v, clock_ghz, activity_factor, cap_per_transistor_ff, interconnect_fraction, die_area_mm2, leakage_density_w_mm2, thermal_resistance_c_w, tdp_w
  datacenter: {transistor_count_b:80,  core_voltage_v:0.88, clock_ghz:2.2, activity_factor:0.035, cap_per_transistor_ff:0.08, interconnect_fraction:0.5, die_area_mm2:814, leakage_density_w_mm2:0.018, thermal_resistance_c_w:0.05, tdp_w:700},
  mobile:     {transistor_count_b:6,   core_voltage_v:0.65, clock_ghz:1.0, activity_factor:0.06,  cap_per_transistor_ff:0.04, interconnect_fraction:0.4, die_area_mm2:110, leakage_density_w_mm2:0.008, thermal_resistance_c_w:0.5,  tdp_w:15},
  runaway:    {transistor_count_b:60,  core_voltage_v:1.15, clock_ghz:3.5, activity_factor:0.25, cap_per_transistor_ff:0.12, interconnect_fraction:0.5, die_area_mm2:800, leakage_density_w_mm2:0.35,  thermal_resistance_c_w:1.2,  tdp_w:500},
};
document.querySelectorAll(".preset").forEach(el=>{
  el.onclick = () => { const p = presets[el.dataset.p]; for(const k in p) $(k).value = p[k]; syncLabels(); run(); };
});

function fmt(n, d=1){ return Number(n).toLocaleString(undefined,{maximumFractionDigits:d}); }
function fmtSig(n){
  const a = Math.abs(n);
  if(a===0) return "0";
  if(a>=1000) return fmt(n,0);
  if(a>=1) return fmt(n,1);
  if(a>=0.01) return fmt(n,3);
  return n.toExponential(1);
}

// ================= REPLACED-FOR-POWER drawing block =================
function wLabel(w){ return w>=1000 ? fmt(w/1000,2)+" kW" : w>=100 ? fmt(w,0)+" W" : fmt(w,1)+" W"; }
function ctxLabel(t){ return t>=1024 ? fmt(t/1024,0)+"K" : fmt(t,0); }

// ---- viz 1: total power vs clock frequency (linear-x), TDP ceiling + thermal-limited max clock ----
function drawPower(r){
  const o = r.outputs, p = r.profile, inp = r.inputs;
  const pf = p.power_vs_freq;                    // [[ghz, total_w], ...]
  const tdp = o.tdp_w;
  const opF = inp.clock_ghz, opW = o.total_power_w;
  const maxClk = o.max_clock_ghz;
  const VW=600, VH=360, padL=58, padR=150, padT=20, padB=46;
  const xmin = pf[0][0], xmax = pf[pf.length-1][0];
  const curveMax = Math.max.apply(null, pf.map(d=>d[1]));
  const ymin = 0, ymax = Math.max(tdp, curveMax, opW) * 1.12;
  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 = [];
  // vertical gridlines every 0.5 GHz
  for(let g=Math.ceil(xmin*2)/2; g<=xmax+1e-6; g+=0.5){
    const xx = X(g);
    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(g,1)}</text>`);
  }
  // horizontal power gridlines (quarters of ymax)
  for(let i=0;i<=4;i++){
    const gy = ymax*i/4, yy = Y(gy);
    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">${fmt(gy,0)}</text>`);
  }
  // axes
  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"/>`);
  // TDP ceiling (dashed red)
  parts.push(`<line x1="${padL}" y1="${Y(tdp).toFixed(1)}" x2="${(VW-padR).toFixed(1)}" y2="${Y(tdp).toFixed(1)}" stroke="#c0392b" stroke-width="1.4" stroke-dasharray="5 3"/>`);
  parts.push(`<text x="${(padL+4).toFixed(1)}" y="${(Y(tdp)-4).toFixed(1)}" fill="#c0392b" font-size="9">TDP ${wLabel(tdp)}</text>`);
  // total-power curve
  let d=""; pf.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.6"/>`);
  // thermal-limited max clock (dashed blue vertical) — only if within the plotted window and < top clock
  if(maxClk > xmin && maxClk < xmax - 1e-6){
    const mcx = X(maxClk);
    parts.push(`<line x1="${mcx.toFixed(1)}" y1="${padT}" x2="${mcx.toFixed(1)}" y2="${(VH-padB).toFixed(1)}" stroke="#2f74b8" stroke-width="1.4" stroke-dasharray="5 3"/>`);
    parts.push(`<text x="${(mcx+4).toFixed(1)}" y="${(padT+11).toFixed(1)}" fill="#2f74b8" font-size="9">max clock ${fmt(maxClk,2)} GHz</text>`);
  }
  // operating point: vertical at op clock, marker at total power
  const opx = X(Math.min(Math.max(opF,xmin),xmax));
  const over = (opW > tdp) || o.thermal_runaway || (o.junction_temp_c > o.tjmax_c);
  const opColor = over ? "#c0392b" : "#16181d";
  parts.push(`<line x1="${opx.toFixed(1)}" y1="${Y(Math.min(opW,ymax)).toFixed(1)}" x2="${opx.toFixed(1)}" y2="${(VH-padB).toFixed(1)}" stroke="${opColor}" stroke-width="1" stroke-dasharray="3 3"/>`);
  parts.push(`<circle cx="${opx.toFixed(1)}" cy="${Y(Math.min(opW,ymax)).toFixed(1)}" r="5" fill="${opColor}"/>`);
  parts.push(`<text x="${(opx+8).toFixed(1)}" y="${(Y(Math.min(opW,ymax))+4).toFixed(1)}" fill="${opColor}" font-size="10" font-weight="700">${wLabel(opW)}${over?" · over":""}</text>`);
  // legend
  const lx = VW-padR+12; let ly = padT+8;
  const line = (color,dash,txt) => { parts.push(`<line x1="${lx}" y1="${ly}" x2="${lx+20}" y2="${ly}" stroke="${color}" stroke-width="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, "total power");
  line("#c0392b", true, "TDP");
  line("#2f74b8", true, "max clock");
  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("junction temp", fmt(o.junction_temp_c,0)+" °C", o.thermal_runaway?"#c0392b":(o.junction_temp_c>o.tjmax_c?"#c0392b":(o.junction_temp_c>0.85*o.tjmax_c?"#9a6b00":"#34d399")));
  kv("leakage frac", fmt(o.leakage_fraction_percent,0)+" %", o.leakage_fraction_percent>50?"#9a6b00":"#34d399");
  kv("max clock", o.thermal_runaway?"—":fmt(maxClk,2)+" GHz", o.thermal_runaway?"#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">clock frequency (GHz)</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)})">total power (W)</text>`);
  $("viz1").innerHTML = '<svg viewBox="0 0 '+VW+' '+VH+'" width="100%" xmlns="http://www.w3.org/2000/svg" id="svg1">'+parts.join("")+'</svg>';
}

// ---- small line-chart helper (utilization-vs-dimension panel) ----
function sweepPanel(parts, box, data, curX, curY, opt){
  const xs = data.map(d=>d[0]), ys = data.map(d=>d[1]*opt.scale);
  const xmin = xs[0], xmax = xs[xs.length-1];
  const ymax = opt.ymax != null ? opt.ymax : Math.max(1e-6, Math.max(...ys), opt.cap||0) * 1.12;
  const ymin = 0;
  const xfmt = opt.xfmt || (v=>fmt(v,0));
  const X = v => box.x0 + ((v-xmin)/(xmax-xmin)) * box.w;
  const Y = v => box.y0 + (1 - (Math.min(Math.max(v,ymin),ymax)-ymin)/(ymax-ymin)) * box.h;
  parts.push(`<line x1="${box.x0}" y1="${box.y0}" x2="${box.x0}" y2="${(box.y0+box.h).toFixed(1)}" stroke="#334d70" stroke-width="1"/>`);
  parts.push(`<line x1="${box.x0}" y1="${(box.y0+box.h).toFixed(1)}" x2="${(box.x0+box.w).toFixed(1)}" y2="${(box.y0+box.h).toFixed(1)}" stroke="#334d70" stroke-width="1"/>`);
  parts.push(`<text x="${(box.x0-5).toFixed(1)}" y="${(box.y0+8).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="end">${fmt(ymax,0)}</text>`);
  parts.push(`<text x="${(box.x0-5).toFixed(1)}" y="${(Y(0)+3).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="end">0</text>`);
  parts.push(`<text x="${box.x0.toFixed(1)}" y="${(box.y0+box.h+12).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="middle">${xfmt(xmin)}</text>`);
  parts.push(`<text x="${(box.x0+box.w).toFixed(1)}" y="${(box.y0+box.h+12).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="middle">${xfmt(xmax)}</text>`);
  // optional capacity/TDP ceiling (dashed red)
  if(opt.cap){
    const cy = Y(opt.cap);
    parts.push(`<line x1="${box.x0}" y1="${cy.toFixed(1)}" x2="${(box.x0+box.w).toFixed(1)}" y2="${cy.toFixed(1)}" stroke="#c0392b" stroke-width="1.2" stroke-dasharray="5 3"/>`);
    parts.push(`<text x="${(box.x0+box.w).toFixed(1)}" y="${(cy-3).toFixed(1)}" fill="#c0392b" font-size="8.5" text-anchor="end">TDP</text>`);
  }
  let d=""; data.forEach((pt,i)=>{ d += (i?"L":"M") + X(pt[0]).toFixed(1) + " " + Y(pt[1]*opt.scale).toFixed(1) + " "; });
  parts.push(`<path d="${d}" fill="none" stroke="${opt.color}" stroke-width="2.2"/>`);
  const mx = X(Math.min(Math.max(curX,xmin),xmax)), my = Y(Math.min(curY,ymax));
  parts.push(`<line x1="${mx.toFixed(1)}" y1="${box.y0}" x2="${mx.toFixed(1)}" y2="${(box.y0+box.h).toFixed(1)}" stroke="#38bdf8" stroke-width="1" stroke-dasharray="4 3"/>`);
  parts.push(`<circle cx="${mx.toFixed(1)}" cy="${my.toFixed(1)}" r="4" fill="#38bdf8"/>`);
  parts.push(`<text x="${(mx+5).toFixed(1)}" y="${(box.y0+10).toFixed(1)}" fill="#38bdf8" font-size="9.5" font-weight="700">${opt.markLabel}</text>`);
  parts.push(`<text x="${(box.x0+box.w/2).toFixed(1)}" y="${(box.y0-4).toFixed(1)}" fill="#ffffff" font-size="11" font-weight="700" text-anchor="middle">${opt.title}</text>`);
  parts.push(`<text x="${(box.x0+box.w/2).toFixed(1)}" y="${(box.y0+box.h+24).toFixed(1)}" fill="#94a3b8" font-size="10" text-anchor="middle">${opt.xlab}</text>`);
}

// ---- horizontal efficiency-budget bars: spatial x temporal = utilization ----
function budgetBars(parts, box){
  return function(items){
    const n = items.length, gap = 16;
    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="#e2e8f0" font-size="10.5" 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="12" rx="6" fill="#142138"/>`);
      parts.push(`<rect x="${box.x0}" y="${(y+bh*0.5+2).toFixed(1)}" width="${(box.w*frac).toFixed(1)}" height="12" rx="6" 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="11" font-weight="700" text-anchor="end">${fmt(it.v,1)} %</text>`);
    });
  };
}

// ---- log-x throughput panel (context spans 3+ decades, so map x with log2) ----
function tpsPanel(parts, box, data, curX, curY, opt){
  const lg = Math.log2;
  const xs = data.map(d=>d[0]);
  const xmin = xs[0], xmax = xs[xs.length-1];
  const ys = data.map(d=>d[1]);
  const ymax = Math.max(1e-6, Math.max(...ys), opt.cap||0) * 1.12, ymin = 0;
  const X = v => box.x0 + (lg(v)-lg(xmin))/(lg(xmax)-lg(xmin)) * box.w;
  const Y = v => box.y0 + (1 - (Math.min(Math.max(v,ymin),ymax)-ymin)/(ymax-ymin)) * box.h;
  parts.push(`<line x1="${box.x0}" y1="${box.y0}" x2="${box.x0}" y2="${(box.y0+box.h).toFixed(1)}" stroke="#334d70" stroke-width="1"/>`);
  parts.push(`<line x1="${box.x0}" y1="${(box.y0+box.h).toFixed(1)}" x2="${(box.x0+box.w).toFixed(1)}" y2="${(box.y0+box.h).toFixed(1)}" stroke="#334d70" stroke-width="1"/>`);
  parts.push(`<text x="${(box.x0-5).toFixed(1)}" y="${(box.y0+8).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="end">${fmt(ymax,0)}</text>`);
  parts.push(`<text x="${(box.x0-5).toFixed(1)}" y="${(Y(0)+3).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="end">0</text>`);
  parts.push(`<text x="${box.x0.toFixed(1)}" y="${(box.y0+box.h+12).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="middle">${ctxLabel(xmin)}</text>`);
  parts.push(`<text x="${(box.x0+box.w).toFixed(1)}" y="${(box.y0+box.h+12).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="middle">${ctxLabel(xmax)}</text>`);
  // optional capacity ceiling (dashed red)
  if(opt.cap){
    const cy = Y(opt.cap);
    parts.push(`<line x1="${box.x0}" y1="${cy.toFixed(1)}" x2="${(box.x0+box.w).toFixed(1)}" y2="${cy.toFixed(1)}" stroke="#c0392b" stroke-width="1.2" stroke-dasharray="5 3"/>`);
    parts.push(`<text x="${(box.x0+box.w).toFixed(1)}" y="${(cy-3).toFixed(1)}" fill="#c0392b" font-size="8.5" text-anchor="end">capacity</text>`);
  }
  let d=""; data.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="${opt.color}" stroke-width="2.2"/>`);
  const mx = X(Math.min(Math.max(curX,xmin),xmax)), my = Y(Math.min(curY,ymax));
  parts.push(`<line x1="${mx.toFixed(1)}" y1="${box.y0}" x2="${mx.toFixed(1)}" y2="${(box.y0+box.h).toFixed(1)}" stroke="#38bdf8" stroke-width="1" stroke-dasharray="4 3"/>`);
  parts.push(`<circle cx="${mx.toFixed(1)}" cy="${my.toFixed(1)}" r="4" fill="#38bdf8"/>`);
  parts.push(`<text x="${(mx+5).toFixed(1)}" y="${(box.y0+10).toFixed(1)}" fill="#38bdf8" font-size="9.5" font-weight="700">${opt.markLabel}</text>`);
  parts.push(`<text x="${(box.x0+box.w/2).toFixed(1)}" y="${(box.y0-4).toFixed(1)}" fill="#ffffff" font-size="11" font-weight="700" text-anchor="middle">${opt.title}</text>`);
  parts.push(`<text x="${(box.x0+box.w/2).toFixed(1)}" y="${(box.y0+box.h+24).toFixed(1)}" fill="#94a3b8" font-size="10" text-anchor="middle">${opt.xlab}</text>`);
}

// ---- viz 2: total power vs core voltage sweep + power/thermal budget bars ----
function drawBudget(r){
  const o = r.outputs, p = r.profile, inp = r.inputs;
  const VW=600, VH=240;
  const parts = [];
  const gap=48, w=(VW-gap*3)/2, h=150, y0=34;
  // left: total power vs core voltage (linear-x, quadratic-rising) with TDP ceiling
  sweepPanel(parts,
    {x0:gap, y0:y0, w:w, h:h},
    p.power_vs_volt, inp.core_voltage_v, o.total_power_w,
    {scale:1, color:"#07C160", title:"Total power vs voltage", xlab:"core voltage (V)", markLabel:`${fmt(inp.core_voltage_v,2)} V`, cap:o.tdp_w, xfmt:v=>fmt(v,2)});
  parts.push(`<text x="${gap}" y="18" fill="#94a3b8" font-size="10">W</text>`);
  // right: power/thermal budget bars
  const rx = gap*2+w;
  const tot = o.total_power_w, tdp = o.tdp_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">Power &amp; thermal budget</text>`);
  budgetBars(parts, {x0:rx, y0:y0+6, w:w, h:h-6})([
    {k:"Logic dynamic",  v:o.logic_dynamic_w/tdp*100,     color:"#07C160"},
    {k:"Interconnect",   v:o.interconnect_power_w/tdp*100, color:"#3f78c4"},
    {k:"Leakage",        v:o.leakage_power_w/tdp*100,      color:"#9a6bd4"},
    {k:"Junction / Tjmax", v:o.junction_temp_c/o.tjmax_c*100, color:(o.thermal_runaway||o.junction_temp_c>o.tjmax_c)?"#c0392b":(o.junction_temp_c>0.85*o.tjmax_c?"#e0821a":"#07C160")},
  ]);
  parts.push(`<text x="${(rx+w/2).toFixed(1)}" y="${(y0+h+24).toFixed(1)}" fill="#94a3b8" font-size="10" text-anchor="middle">power components as a share of TDP; last bar = Tj / Tjmax</text>`);
  $("viz2").innerHTML = '<svg viewBox="0 0 '+VW+' '+VH+'" width="100%" xmlns="http://www.w3.org/2000/svg" id="svg2">'+parts.join("")+'</svg>';
}
// ================= end replaced block =================

function showMetrics(r){
  const o = r.outputs;
  const rows = [
    ["Dynamic power", fmtSig(o.dynamic_power_w)+" W"],
    ["&nbsp;&nbsp;&ndash; logic switching", fmtSig(o.logic_dynamic_w)+" W"],
    ["&nbsp;&nbsp;&ndash; interconnect / wire", fmtSig(o.interconnect_power_w)+" W"],
    ["Leakage (static) power", fmtSig(o.leakage_power_w)+" W"],
    ["Leakage fraction", fmt(o.leakage_fraction_percent,1)+" %"],
    ["Total power", fmtSig(o.total_power_w)+" W"],
    ["TDP budget", fmtSig(o.tdp_w)+" W"],
    ["Within TDP", o.within_tdp ? "yes" : "no &mdash; over budget"],
    ["Power density", fmt(o.power_density_w_mm2,2)+" W/mm²"],
    ["Junction temp Tj", fmt(o.junction_temp_c,1)+" °C"],
    ["Tjmax limit", fmt(o.tjmax_c,0)+" °C"],
    ["Within Tjmax", o.within_tjmax ? "yes" : "no &mdash; too hot"],
    ["Thermal runaway", o.thermal_runaway ? "yes &mdash; diverges" : "no"],
    ["Thermal-limited max clock", o.thermal_runaway ? "&mdash;" : fmt(o.max_clock_ghz,2)+" GHz"],
    ["Energy / cycle", fmtSig(o.energy_per_cycle_nj)+" nJ"],
    ["Efficiency (Gops/W, proxy)", fmtSig(o.gops_per_w)],
  ];
  $("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(/within budget/.test(o.verdict)){ v.style.background="#e7f7ee"; v.style.color="#34d399"; }
  else if(/thermal runaway|over budget/.test(o.verdict)){ v.style.background="#fdecea"; v.style.color="#c0392b"; }
  else { v.style.background="#fff6e6"; v.style.color="#9a6b00"; }

  $("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 arrowRight = '<svg viewBox="0 0 34 16" fill="none"><path d="M0 8 H27" stroke="currentColor" stroke-width="1.6"/><path d="M21 2 L29 8 L21 14" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                const arrowLeft = '<svg viewBox="0 0 34 16" fill="none"><path d="M34 8 H7" stroke="currentColor" stroke-width="1.6"/><path d="M13 2 L5 8 L13 14" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                const arrowDownSm = '<svg viewBox="0 0 16 28" fill="none"><path d="M8 0 V22" stroke="currentColor" stroke-width="1.6"/><path d="M2 16 L8 24 L14 16" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                const arrowUpSm = '<svg viewBox="0 0 16 28" fill="none"><path d="M8 28 V6" stroke="currentColor" stroke-width="1.6"/><path d="M2 12 L8 4 L14 12" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                // One arrow per column of the preceding node row (parallel arrows, Claude.ai-style).
                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) => {
                    // explicit arrow row: draw the connector, don't double up
                    if (r.type === 'arrow') { out += connector(prevCount); prevType = 'arrow'; return; }
                    // auto-connect sequential flow rows only; tier/die rows are diagrams, not steps
                    if (idx > 0 && r.type !== 'tier' && r.type !== 'die' && (prevType === 'nodes' || prevType === 'group')) out += connector(prevCount);
                    if (r.type === 'die') {
                        out += '<div class="cfs-die">';
                        out += `<div class="cfs-die-h">${applyBold(String(r.title || ''))}</div>`;
                        if (r.note) out += `<div class="cfs-die-note">${applyBold(String(r.note))}</div>`;
                        out += '<div class="cfs-die-top">';
                        if (r.input) out += `<div class="cfs-die-in">${applyBold(String(r.input))}<span class="cfs-die-inar">${arrowRight}</span></div>`;
                        (r.top || []).forEach((n, i) => {
                            if (i > 0) out += `<span class="cfs-die-ar">${arrowRight}</span>`;
                            out += node(n);
                        });
                        out += '</div>';
                        if (r.base) {
                            out += '<div class="cfs-die-downs">';
                            (r.top || []).forEach(() => { out += `<span class="cfs-die-dar">${arrowDownSm}</span>`; });
                            out += '</div>';
                            out += `<div class="cfs-die-base">${node(r.base)}</div>`;
                        }
                        out += '</div>';
                        if (r.feed) {
                            out += `<div class="cfs-die-uar">${arrowUpSm}</div>`;
                            out += `<div class="cfs-die-feed">${applyBold(String(r.feed.title || ''))}</div>`;
                        }
                        prevType = 'die'; prevCount = 0;
                        return;
                    }
                    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>';
                        const gi = r.items || [];
                        if (r.cycle && gi.length >= 4) {
                            // clockwise ring: items = [top-left, top-right, bottom-right, bottom-left]
                            out += '<div class="cfs-cycle">';
                            out += `<div class="cfs-cyc cyc-tl">${node(gi[0])}</div>`;
                            out += `<div class="cfs-cyc-ar cyc-r">${arrowRight}</div>`;
                            out += `<div class="cfs-cyc cyc-tr">${node(gi[1])}</div>`;
                            out += `<div class="cfs-cyc-loop">${applyBold(String(r.loop || ''))}</div>`;
                            out += `<div class="cfs-cyc-ar cyc-d">${arrowDownSm}</div>`;
                            out += `<div class="cfs-cyc cyc-bl">${node(gi[3])}</div>`;
                            out += `<div class="cfs-cyc-ar cyc-l">${arrowLeft}</div>`;
                            out += `<div class="cfs-cyc cyc-br">${node(gi[2])}</div>`;
                            out += '</div>';
                        } else {
                            out += '<div class="cfs-group-grid">';
                            gi.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;
                    }
                    // default: a row of nodes
                    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);
  // KB stores newlines/tabs as literal backslash sequences — restore real
  // breaks so paragraphs and diagrams separate instead of running together.
  s = s.replace(/\\r\\n|\\r|\\n/g, "\n").replace(/\\t/g, "  ");
  // Hold aside authored diagram blocks (fenced ```svg / ```html, or a bare
  // <svg>…</svg>) so they render as real figures rather than escaped tag text.
  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;");
  });
  // Restore authored diagrams as real figures (unwrap the stray <p> the block
  // splitter puts around a lone sentinel so we don't nest a figure inside <p>).
  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 o = r.outputs;
  const queries = [["Dynamic vs leakage power","dynamic power"], ["", "leakage power"]];
  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);
  try{
    const res = await fetch("/edge/power", {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);
    drawPower(r); drawBudget(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>
