{"id":24929,"date":"2026-10-09T07:54:51","date_gmt":"2026-10-09T07:54:51","guid":{"rendered":"https:\/\/www.mechstream.com\/?p=24929"},"modified":"2026-10-09T08:04:20","modified_gmt":"2026-10-09T08:04:20","slug":"heat-treatment-hardness-change-calculator","status":"publish","type":"post","link":"https:\/\/www.mechstream.com\/ru\/heat-treatment-hardness-change-calculator\/","title":{"rendered":"Heat Treatment Hardness Change Calculator"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The Heat Treatment Hardness Change Calculator helps engineers, metallurgists, and manufacturing professionals estimate changes in steel hardness after heat treatment processes such as quenching, tempering, and annealing. By considering material properties, treatment temperature, holding time, and initial hardness, this tool supports heat treatment planning, hardness evaluation, and material performance optimization. Whether you&#8217;re working with carbon steel, alloy steel, or tool steel, this calculator provides a convenient way to estimate hardness variations and improve heat treatment decisions. Actual results may vary depending on steel composition, cooling conditions, and processing parameters.<\/p>\n\n\n\n<!-- Heat Treatment Hardness Change Calculator - WordPress Custom HTML block snippet. All CSS is scoped to #htc-calculator. Vanilla JS, no jQuery, no external libraries. -->\n<div id=\"htc-calculator\" class=\"htc-root\" data-htc-widget=\"heat-treatment-hardness\"> \n<style>\n#htc-calculator,#htc-calculator *,#htc-calculator *::before,#htc-calculator *::after{box-sizing:border-box}\n#htc-calculator{\n  --htc-bg:#f5f5f7;--htc-card:#ffffff;--htc-text:#1d1d1f;--htc-muted:#6e6e73;--htc-soft:#86868b;\n  --htc-line:#d2d2d7;--htc-line-soft:#e8e8ed;--htc-accent:#0071e3;--htc-accent-dark:#0058b0;--htc-accent-soft:#e8f2fd;\n  --htc-up:#0a7c42;--htc-up-soft:#e6f4ec;--htc-down:#b25000;--htc-down-soft:#fdf0e4;--htc-warn:#8a5a00;--htc-warn-soft:#fff6e0;\n  --htc-err:#c4291c;--htc-err-soft:#fdecea;\n  width:100%;max-width:1040px;margin:24px auto;padding:32px;background:var(--htc-bg);color:var(--htc-text);\n  border-radius:22px;font-family:-apple-system,BlinkMacSystemFont,\"SF Pro Text\",\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;\n  font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased;text-align:left\n}\n#htc-calculator p,#htc-calculator ul{margin:0;padding:0;border:0;background:none;text-transform:none;letter-spacing:normal}\n#htc-calculator .htc-card{background:var(--htc-card);border:1px solid var(--htc-line-soft);border-radius:18px;padding:24px;box-shadow:0 1px 2px rgba(0,0,0,.04)}\n#htc-calculator .htc-card+.htc-card{margin-top:16px}\n#htc-calculator .htc-card-title{margin:0 0 4px;color:var(--htc-text);font-size:1.1rem;font-weight:600;line-height:1.2;display:flex;align-items:center;gap:10px}\n#htc-calculator .htc-step{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:var(--htc-accent-soft);color:var(--htc-accent);font-size:.8rem;font-weight:700}\n#htc-calculator .htc-card-note{color:var(--htc-muted);font-size:.9rem;margin-bottom:18px}\n#htc-calculator .htc-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 20px}\n#htc-calculator .htc-field{min-width:0}\n#htc-calculator .htc-field.htc-span{grid-column:1\/-1}\n#htc-calculator .htc-label{display:block;font-size:.9rem;font-weight:600;color:var(--htc-text);margin:0 0 6px}\n#htc-calculator .htc-hint{display:block;color:var(--htc-soft);font-size:.8rem;margin-top:5px;min-height:1.2em}\n#htc-calculator .htc-error{display:block;color:var(--htc-err);font-size:.82rem;margin-top:4px;min-height:0}\n#htc-calculator .htc-error:empty{display:none}\n#htc-calculator .htc-row{display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px}\n#htc-calculator input[type=number],#htc-calculator select{\n  -webkit-appearance:none;appearance:none;width:100%;min-height:46px;margin:0;padding:10px 14px;font:inherit;font-size:16px;line-height:1.3;\n  color:var(--htc-text);background-color:#fff;border:1px solid var(--htc-line);border-radius:12px;box-shadow:none;outline:none;\n  transition:border-color .15s ease,box-shadow .15s ease;max-width:none;height:auto\n}\n#htc-calculator select{padding-right:34px;background-image:linear-gradient(45deg,transparent 50%,#6e6e73 50%),linear-gradient(135deg,#6e6e73 50%,transparent 50%);background-position:calc(100% - 18px) 20px,calc(100% - 13px) 20px;background-size:5px 5px,5px 5px;background-repeat:no-repeat;cursor:pointer}\n#htc-calculator input[type=number]:focus,#htc-calculator select:focus{border-color:var(--htc-accent);box-shadow:0 0 0 4px rgba(0,113,227,.18)}\n#htc-calculator .htc-invalid{border-color:var(--htc-err)!important;box-shadow:0 0 0 3px rgba(196,41,28,.12)!important}\n#htc-calculator .htc-seg{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));min-width:0;width:100%;gap:4px;padding:4px;background:#ececf0;border-radius:13px;border:0;margin:0}\n#htc-calculator .htc-seg legend{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}\n#htc-calculator .htc-seg input{position:absolute;opacity:0;width:1px;height:1px;margin:0}\n#htc-calculator .htc-seg label{display:flex;align-items:center;justify-content:center;min-height:40px;padding:6px 8px;border-radius:10px;font-size:.93rem;font-weight:600;color:var(--htc-muted);cursor:pointer;text-align:center;transition:background .15s,color .15s,box-shadow .15s;margin:0}\n#htc-calculator .htc-seg input:checked+label{background:#fff;color:var(--htc-text);box-shadow:0 1px 3px rgba(0,0,0,.12)}\n#htc-calculator .htc-seg input:focus-visible+label{outline:3px solid rgba(0,113,227,.35);outline-offset:1px}\n#htc-calculator .htc-grade-info{grid-column:1\/-1;padding:14px 16px;border-radius:12px;background:#f9f9fb;border:1px solid var(--htc-line-soft);font-size:.88rem;color:var(--htc-muted)}\n#htc-calculator .htc-grade-info strong{color:var(--htc-text)}\n#htc-calculator .htc-grade-info.htc-unsupported{background:var(--htc-warn-soft);border-color:#f3dfa8;color:#5c4300}\n#htc-calculator .htc-badge{display:inline-block;padding:2px 9px;border-radius:999px;font-size:.72rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase;vertical-align:middle}\n#htc-calculator .htc-badge-est{background:var(--htc-accent-soft);color:var(--htc-accent-dark)}\n#htc-calculator .htc-badge-in{background:#ececf0;color:var(--htc-muted)}\n#htc-calculator .htc-badge-warn{background:var(--htc-warn-soft);color:var(--htc-warn)}\n#htc-calculator .htc-treat[hidden]{display:none!important}\n#htc-calculator .htc-actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin-top:22px}\n#htc-calculator .htc-btn{-webkit-appearance:none;appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:46px;min-width:150px;margin:0;padding:10px 22px;border-radius:999px;border:1px solid transparent;font:inherit;font-size:.98rem;font-weight:600;line-height:1.2;cursor:pointer;text-decoration:none;text-transform:none;letter-spacing:normal;box-shadow:none;transition:background .15s,border-color .15s,color .15s}\n#htc-calculator .htc-btn-primary{background:var(--htc-accent);color:#fff}\n#htc-calculator .htc-btn-primary:hover{background:var(--htc-accent-dark);color:#fff}\n#htc-calculator .htc-btn-ghost{background:#fff;color:var(--htc-accent);border-color:var(--htc-line)}\n#htc-calculator .htc-btn-ghost:hover{border-color:var(--htc-accent);background:#fff;color:var(--htc-accent-dark)}\n#htc-calculator .htc-btn-small{min-height:38px;min-width:0;padding:7px 16px;font-size:.88rem}\n#htc-calculator .htc-btn:focus-visible{outline:3px solid rgba(0,113,227,.35);outline-offset:2px}\n#htc-calculator .htc-form-msg{text-align:center;color:var(--htc-err);font-size:.88rem;margin-top:10px;min-height:1.3em}\n#htc-calculator .htc-results[hidden]{display:none!important}\n#htc-calculator .htc-results-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}\n#htc-calculator .htc-tools{display:flex;gap:8px;flex-wrap:wrap}\n#htc-calculator .htc-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px}\n#htc-calculator .htc-kpi{background:#f9f9fb;border:1px solid var(--htc-line-soft);border-radius:14px;padding:14px 16px;min-width:0}\n#htc-calculator .htc-kpi-main{background:var(--htc-accent-soft);border-color:#cfe3fa}\n#htc-calculator .htc-kpi-label{font-size:.78rem;font-weight:600;color:var(--htc-muted);text-transform:uppercase;letter-spacing:.04em;display:flex;gap:6px;align-items:center;flex-wrap:wrap}\n#htc-calculator .htc-kpi-value{font-size:clamp(1.35rem,2.6vw,1.9rem);font-weight:700;letter-spacing:-.02em;margin-top:4px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}\n#htc-calculator .htc-kpi-sub{font-size:.8rem;color:var(--htc-muted);margin-top:2px}\n#htc-calculator .htc-up{color:var(--htc-up)}\n#htc-calculator .htc-down{color:var(--htc-down)}\n#htc-calculator .htc-flat{color:var(--htc-muted)}\n#htc-calculator .htc-alert{margin-top:16px;padding:14px 16px;border-radius:12px;font-size:.9rem}\n#htc-calculator .htc-alert ul{margin:6px 0 0 18px;padding:0}\n#htc-calculator .htc-alert li{margin:3px 0}\n#htc-calculator .htc-alert-err{background:var(--htc-err-soft);color:#7a1a12;border:1px solid #f5c6c1}\n#htc-calculator .htc-alert[hidden]{display:none!important}\n#htc-calculator .htc-toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%) translateY(20px);background:#1d1d1f;color:#fff;padding:10px 18px;border-radius:999px;font-size:.88rem;opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;z-index:99999}\n#htc-calculator .htc-toast.htc-show{opacity:1;transform:translateX(-50%) translateY(0)}\n#htc-calculator .htc-sr{position:absolute!important;width:1px!important;height:1px!important;overflow:hidden!important;clip:rect(0 0 0 0)!important;white-space:nowrap!important}\n@media (max-width:860px){#htc-calculator .htc-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}}\n@media (max-width:700px){\n  #htc-calculator{padding:18px 16px;border-radius:16px;margin:12px auto}\n  #htc-calculator .htc-card{padding:18px 16px;border-radius:16px}\n  #htc-calculator .htc-grid{grid-template-columns:1fr}\n  #htc-calculator .htc-btn{flex:1 1 auto}\n  #htc-calculator .htc-seg label{font-size:.84rem;padding:6px 4px}\n}\n@media (max-width:380px){#htc-calculator .htc-kpis{grid-template-columns:1fr}#htc-calculator .htc-row{grid-template-columns:minmax(0,1fr) 84px}}\n@media (prefers-reduced-motion:reduce){#htc-calculator *{transition:none!important}}\n<\/style>\n <form class=\"htc-form\" novalidate onsubmit=\"return false;\"><section class=\"htc-card\" aria-labelledby=\"htc-h-material\"><div class=\"htc-card-title\" id=\"htc-h-material\"><span class=\"htc-step\" aria-hidden=\"true\">1<\/span>Material &amp; Starting Condition<\/div><p class=\"htc-card-note\">Select the steel grade and enter the hardness you measured before heat treatment.<\/p><div class=\"htc-grid\"><div class=\"htc-field\"><label class=\"htc-label\" for=\"htc-grade\">Steel grade<\/label><select id=\"htc-grade\" name=\"grade\"><option value=\"1045\">AISI 1045 &#8212; medium-carbon steel<\/option><option value=\"4140\" selected>AISI 4140 &#8212; Cr-Mo alloy steel<\/option><option value=\"4340\">AISI 4340 &#8212; Ni-Cr-Mo alloy steel<\/option><option value=\"52100\">AISI 52100 &#8212; bearing steel<\/option><option value=\"H13\">AISI H13 &#8212; hot-work tool steel<\/option><option value=\"D2\">AISI D2 &#8212; cold-work tool steel<\/option><option value=\"custom\">Custom steel<\/option><\/select><\/div><div class=\"htc-field\"><label class=\"htc-label\" for=\"htc-condition\">Initial material condition<\/label><select id=\"htc-condition\" name=\"condition\" aria-describedby=\"htc-condition-hint\"><option value=\"annealed\">Annealed<\/option><option value=\"normalized\">Normalized<\/option><option value=\"quenched\" selected>Quenched (as-hardened)<\/option><option value=\"tempered\">Previously tempered<\/option><\/select><span class=\"htc-hint\" id=\"htc-condition-hint\"><\/span><\/div><div class=\"htc-field\"><label class=\"htc-label\" for=\"htc-initial\">Initial hardness (measured)<\/label><div class=\"htc-row\"><input id=\"htc-initial\" name=\"initial\" type=\"number\" inputmode=\"decimal\" step=\"any\" value=\"55\" aria-describedby=\"htc-initial-hint htc-initial-err\"><select id=\"htc-scale\" name=\"scale\" aria-label=\"Hardness scale\"><option value=\"HRC\" selected>HRC<\/option><option value=\"HBW\">HBW<\/option><option value=\"HV\">HV<\/option><\/select><\/div><span class=\"htc-hint\" id=\"htc-initial-hint\">HRC valid range 20&#8211;68. Use HBW or HV for softer material.<\/span><span class=\"htc-error\" id=\"htc-initial-err\" role=\"alert\"><\/span><\/div><div class=\"htc-grade-info\" id=\"htc-grade-info\" aria-live=\"polite\"><\/div><\/div><\/section><section class=\"htc-card\" aria-labelledby=\"htc-h-treat\"><div class=\"htc-card-title\" id=\"htc-h-treat\"><span class=\"htc-step\" aria-hidden=\"true\">2<\/span>Heat Treatment<\/div><p class=\"htc-card-note\">Only the parameters that matter for the selected process are shown.<\/p><fieldset class=\"htc-seg\"><legend>Heat treatment type<\/legend><input type=\"radio\" id=\"htc-t-quench\" name=\"htc-treatment\" value=\"quench\"><label for=\"htc-t-quench\">Quenching<\/label><input type=\"radio\" id=\"htc-t-temper\" name=\"htc-treatment\" value=\"temper\" checked><label for=\"htc-t-temper\">Tempering<\/label><input type=\"radio\" id=\"htc-t-anneal\" name=\"htc-treatment\" value=\"anneal\"><label for=\"htc-t-anneal\">Annealing<\/label><\/fieldset><div class=\"htc-grid\" style=\"margin-top:20px\"><div class=\"htc-field\"><label class=\"htc-label\" for=\"htc-temp\" id=\"htc-temp-label\">Tempering temperature<\/label><div class=\"htc-row\"><input id=\"htc-temp\" name=\"temp\" type=\"number\" inputmode=\"decimal\" step=\"any\" value=\"540\" aria-describedby=\"htc-temp-hint htc-temp-err\"><select id=\"htc-temp-unit\" name=\"tempUnit\" aria-label=\"Temperature unit\"><option value=\"C\" selected>&#176;C<\/option><option value=\"F\">&#176;F<\/option><\/select><\/div><span class=\"htc-hint\" id=\"htc-temp-hint\"><\/span><span class=\"htc-error\" id=\"htc-temp-err\" role=\"alert\"><\/span><\/div><div class=\"htc-field\"><label class=\"htc-label\" for=\"htc-time\" id=\"htc-time-label\">Holding time at temperature<\/label><div class=\"htc-row\"><input id=\"htc-time\" name=\"time\" type=\"number\" inputmode=\"decimal\" step=\"any\" value=\"2\" aria-describedby=\"htc-time-hint htc-time-err\"><select id=\"htc-time-unit\" name=\"timeUnit\" aria-label=\"Time unit\"><option value=\"h\" selected>hours<\/option><option value=\"min\">min<\/option><\/select><\/div><span class=\"htc-hint\" id=\"htc-time-hint\"><\/span><span class=\"htc-error\" id=\"htc-time-err\" role=\"alert\"><\/span><\/div><div class=\"htc-field htc-treat\" data-htc-for=\"quench anneal\"><label class=\"htc-label\" for=\"htc-cooling\">Cooling method<\/label><select id=\"htc-cooling\" name=\"cooling\" aria-describedby=\"htc-cooling-hint\"><\/select><span class=\"htc-hint\" id=\"htc-cooling-hint\"><\/span><\/div><div class=\"htc-field htc-treat\" data-htc-for=\"quench anneal\"><label class=\"htc-label\" for=\"htc-thick\">Section thickness (ruling section)<\/label><div class=\"htc-row\"><input id=\"htc-thick\" name=\"thick\" type=\"number\" inputmode=\"decimal\" step=\"any\" value=\"20\" aria-describedby=\"htc-thick-hint htc-thick-err\"><select id=\"htc-thick-unit\" name=\"thickUnit\" aria-label=\"Thickness unit\"><option value=\"mm\" selected>mm<\/option><option value=\"in\">in<\/option><\/select><\/div><span class=\"htc-hint\" id=\"htc-thick-hint\">Thickness of the heaviest section, or diameter for round bar.<\/span><span class=\"htc-error\" id=\"htc-thick-err\" role=\"alert\"><\/span><\/div><\/div><div class=\"htc-actions\"><button type=\"button\" class=\"htc-btn htc-btn-primary\" id=\"htc-calc\">Calculate<\/button><\/div><p class=\"htc-form-msg\" id=\"htc-form-msg\" role=\"alert\" aria-live=\"polite\"><\/p><\/section><section class=\"htc-card htc-results\" id=\"htc-results\" hidden aria-labelledby=\"htc-h-results\" tabindex=\"-1\"><div class=\"htc-results-head\"><div class=\"htc-card-title\" id=\"htc-h-results\"><span class=\"htc-step\" aria-hidden=\"true\">3<\/span>Results<\/div><div class=\"htc-tools\"><button type=\"button\" class=\"htc-btn htc-btn-ghost htc-btn-small\" id=\"htc-copy\">Copy results<\/button><button type=\"button\" class=\"htc-btn htc-btn-ghost htc-btn-small\" id=\"htc-csv\">Download CSV<\/button><\/div><\/div><div class=\"htc-alert htc-alert-err\" id=\"htc-unsupported\" hidden><\/div><div id=\"htc-ok-block\"><div class=\"htc-kpis\"><div class=\"htc-kpi\"><div class=\"htc-kpi-label\">Initial hardness <span class=\"htc-badge htc-badge-in\">Entered<\/span><\/div><div class=\"htc-kpi-value\" id=\"htc-k-initial\">&#8212;<\/div><div class=\"htc-kpi-sub\" id=\"htc-k-initial-sub\"><\/div><\/div><div class=\"htc-kpi htc-kpi-main\"><div class=\"htc-kpi-label\">Est. final hardness <span class=\"htc-badge htc-badge-est\">Estimate<\/span><\/div><div class=\"htc-kpi-value\" id=\"htc-k-final\">&#8212;<\/div><div class=\"htc-kpi-sub\" id=\"htc-k-final-sub\"><\/div><\/div><div class=\"htc-kpi\"><div class=\"htc-kpi-label\">Hardness change (&#916;H)<\/div><div class=\"htc-kpi-value\" id=\"htc-k-delta\">&#8212;<\/div><div class=\"htc-kpi-sub\" id=\"htc-k-delta-sub\"><\/div><\/div><div class=\"htc-kpi\"><div class=\"htc-kpi-label\">Percentage change<\/div><div class=\"htc-kpi-value\" id=\"htc-k-pct\">&#8212;<\/div><div class=\"htc-kpi-sub\" id=\"htc-k-pct-sub\"><\/div><\/div><\/div><\/div><\/section><\/form><div class=\"htc-toast\" id=\"htc-toast\" role=\"status\" aria-live=\"polite\"><\/div> \n<script>\n(function () {\n  \"use strict\";\n  var root = document.getElementById(\"htc-calculator\");\n  if (!root || root.getAttribute(\"data-htc-init\") === \"1\") { return; }\n  root.setAttribute(\"data-htc-init\", \"1\");\n  \/* =====================================================================\n     REFERENCE DATA\n     Each grade lists its data basis, applicable ranges and assumptions.\n     Values are representative typical values from published tempering \/\n     annealing data for these grades. Replace them with your own verified\n     source data (mill certificates, supplier data sheets) if available.\n     ===================================================================== *\/\n  \/* Approximate hardness conversion for non-austenitic steels after ASTM E140 Table 1:\n     [HRC, HV, HBW (10 mm tungsten-carbide ball, 3000 kgf)] \\u2014 HBW not defined above HRC 65. *\/\n  var CONV = [\n    [20,238,226],[21,243,231],[22,248,237],[23,254,243],[24,260,247],[25,266,253],[26,272,258],[27,279,264],\n    [28,286,271],[29,294,279],[30,302,286],[31,310,294],[32,318,301],[33,327,311],[34,336,319],[35,345,327],\n    [36,354,336],[37,363,344],[38,372,353],[39,382,362],[40,392,371],[41,402,381],[42,412,390],[43,423,400],\n    [44,434,409],[45,446,421],[46,458,432],[47,471,443],[48,484,455],[49,498,469],[50,513,481],[51,528,496],\n    [52,544,512],[53,560,525],[54,577,543],[55,595,560],[56,613,577],[57,633,595],[58,653,615],[59,674,634],\n    [60,697,654],[61,720,670],[62,746,688],[63,772,706],[64,800,722],[65,832,739],[66,865,null],[67,900,null],[68,940,null]\n  ];\n  var COL = { HRC: 0, HV: 1, HBW: 2 };\n  var SCALE_INFO = {\n    HRC: { min: 20, max: 68, dec: 1, label: \"HRC\" },\n    HBW: { min: 80, max: 750, dec: 0, label: \"HBW\" },\n    HV:  { min: 80, max: 1000, dec: 0, label: \"HV\" }\n  };\n  var DATA_NOTE = \"Representative typical values compiled from published tempering and annealing data for this grade (e.g., ASM Heat Treater's Guide, steel-producer data sheets). Not certified values for a specific heat \\u2014 verify against your supplier's data before critical use.\";\n  var HJ_NOTE = \"Hollomon\\u2013Jaffe constant from the published carbon relation C \\u2248 21.3 \\u2212 5.8\\u00b7(%C) using nominal carbon.\";\n  var STEELS = {\n    \"1045\": {\n      name: \"AISI 1045\", family: \"Plain medium-carbon steel\", carbon: 0.45,\n      temper: { refH: 2, C: 18.7, band: 2, start: \"water-quenched, fully hardened\",\n        pts: [[205,55],[260,53],[315,50],[370,47],[425,43],[480,38],[540,33],[595,27],[650,22]] },\n      quench: { aust: [820, 860], aq: [55, 58], media: { water: 12, polymer: 8 },\n        notes: { oil: \"Plain-carbon 1045 has low hardenability; oil quenching does not reliably produce full hardness except in very thin sections, so no estimate is given.\", air: \"1045 is not air-hardening; air cooling from austenite is normalizing, not hardening.\" } },\n      anneal: { range: [790, 870], hbw: [156, 187], kind: \"Full anneal\" }\n    },\n    \"4140\": {\n      name: \"AISI 4140\", family: \"Cr-Mo low-alloy steel\", carbon: 0.40,\n      temper: { refH: 2, C: 19.0, band: 2, start: \"oil-quenched, fully hardened\",\n        pts: [[205,54],[260,52],[315,50],[370,48],[425,45],[480,41],[540,37],[595,32],[650,27]] },\n      quench: { aust: [830, 870], aq: [53, 58], media: { oil: 25, polymer: 25 },\n        notes: { water: \"Water quenching 4140 carries a high cracking risk and is not covered by the reference data.\", air: \"4140 does not through-harden in still air; hardness would depend on hardenability data not included here.\" } },\n      anneal: { range: [815, 870], hbw: [187, 217], kind: \"Full anneal\" }\n    },\n    \"4340\": {\n      name: \"AISI 4340\", family: \"Ni-Cr-Mo low-alloy steel\", carbon: 0.40,\n      temper: { refH: 2, C: 19.0, band: 2, start: \"oil-quenched, fully hardened\",\n        pts: [[205,54],[260,53],[315,51],[370,49],[425,46],[480,43],[540,39],[595,35],[650,30]] },\n      quench: { aust: [815, 860], aq: [54, 58], media: { oil: 75, polymer: 50 },\n        notes: { water: \"Water quenching 4340 carries a high cracking risk and is not covered by the reference data.\", air: \"Air cooling of 4340 gives a partially martensitic\/bainitic structure whose hardness depends on section size; not modelled.\" } },\n      anneal: { range: [830, 870], hbw: [207, 235], kind: \"Full anneal\" }\n    },\n    \"52100\": {\n      name: \"AISI 52100\", family: \"High-carbon Cr bearing steel\", carbon: 1.00,\n      temper: { refH: 2, C: 15.5, band: 2, start: \"oil-quenched, fully hardened\",\n        pts: [[150,63],[175,62],[205,61],[230,60],[260,59],[290,57],[315,56]] },\n      quench: { aust: [815, 870], aq: [62, 66], media: { oil: 15 },\n        notes: { water: \"Water quenching 52100 carries a high cracking risk and is not covered by the reference data.\", polymer: \"Polymer quench response for 52100 is not included in the reference data.\", air: \"52100 is not air-hardening.\" } },\n      anneal: { range: [750, 800], hbw: [179, 207], kind: \"Spheroidize anneal\" }\n    },\n    \"H13\": {\n      name: \"AISI H13\", family: \"Cr-Mo-V hot-work tool steel\", carbon: 0.40,\n      temper: { refH: 2, C: null, band: 2, start: \"air-hardened from ~1010 \\u00b0C\",\n        pts: [[540,52],[565,50],[595,47],[620,43],[650,37]],\n        secondary: true },\n      quench: { aust: [995, 1040], aq: [51, 55], media: { air: 100 },\n        notes: { oil: \"Oil quenching of H13 is used for heavy sections but is not covered by the reference data.\", water: \"Water quenching is not appropriate for H13 (severe cracking risk).\", polymer: \"Polymer quenching of H13 is not covered by the reference data.\" } },\n      anneal: { range: [845, 900], hbw: [192, 229], kind: \"Full anneal\" }\n    },\n    \"D2\": {\n      name: \"AISI D2\", family: \"High-C high-Cr cold-work tool steel\", carbon: 1.55,\n      temper: { refH: 2, C: null, band: 2, start: \"air-hardened from ~1010 \\u00b0C\",\n        pts: [[205,61],[260,59],[315,58],[370,57],[425,57],[480,58],[510,57],[540,55]],\n        secondary: true },\n      quench: { aust: [980, 1025], aq: [62, 64], media: { air: 100 },\n        notes: { oil: \"Oil quenching D2 is not covered by the reference data.\", water: \"Water quenching is not appropriate for D2 (severe cracking risk).\", polymer: \"Polymer quenching of D2 is not covered by the reference data.\" } },\n      anneal: { range: [870, 900], hbw: [217, 255], kind: \"Full anneal\" }\n    }\n  };\n  var COOLING_LABEL = { furnace: \"Furnace cooling\", air: \"Air cooling\", oil: \"Oil quenching\", water: \"Water quenching\", polymer: \"Polymer quenching\" };\n  var COOLING_BY_TREAT = { quench: [\"oil\", \"water\", \"polymer\", \"air\"], anneal: [\"furnace\", \"air\"] };\n  var CONDITION_LABEL = { annealed: \"Annealed\", normalized: \"Normalized\", quenched: \"Quenched (as-hardened)\", tempered: \"Previously tempered\" };\n  var TREAT_LABEL = { quench: \"Quenching (hardening)\", temper: \"Tempering\", anneal: \"Annealing\" };\n  \/* =====================================================================\n     ENGINE (pure functions; no DOM)\n     ===================================================================== *\/\n  function interpCols(v, fromIdx, toIdx) {\n    var rows = [], i;\n    for (i = 0; i < CONV.length; i++) { if (CONV[i][fromIdx] !== null ? CONV[i][toIdx] !== null : false) { rows.push(CONV[i]); } }\n    if (!rows.length) { return null; }\n    var lo = rows[0][fromIdx], hi = rows[rows.length - 1][fromIdx];\n    if (v < lo - 1e-9 || v > hi + 1e-9) { return null; }\n    for (i = 0; i < rows.length - 1; i++) {\n      var a = rows[i], b = rows[i + 1];\n      if (v <= b[fromIdx] + 1e-9) {\n        return a[toIdx] + (b[toIdx] - a[toIdx]) * (v - a[fromIdx]) \/ (b[fromIdx] - a[fromIdx]);\n      }\n    }\n    return rows[rows.length - 1][toIdx];\n  }\n  function convert(v, from, to) {\n    if (v === null || v === undefined || !isFinite(v)) { return null; }\n    if (from === to) { return v; }\n    return interpCols(v, COL[from], COL[to]);\n  }\n  function convRange(scale) {\n    var idx = COL[scale], vals = [];\n    for (var i = 0; i < CONV.length; i++) { if (CONV[i][idx] !== null) { vals.push(CONV[i][idx]); } }\n    return [vals[0], vals[vals.length - 1]];\n  }\n  function cToF(c) { return c * 9 \/ 5 + 32; }\n  function fToC(f) { return (f - 32) * 5 \/ 9; }\n  function interpCurve(pts, x) {\n    if (x < pts[0][0] - 1e-9 || x > pts[pts.length - 1][0] + 1e-9) { return null; }\n    for (var i = 0; i < pts.length - 1; i++) {\n      if (x <= pts[i + 1][0] + 1e-9) {\n        var a = pts[i], b = pts[i + 1];\n        return a[1] + (b[1] - a[1]) * (x - a[0]) \/ (b[0] - a[0]);\n      }\n    }\n    return pts[pts.length - 1][1];\n  }\n  \/* Equivalent temperature at the curve's reference time (Hollomon\\u2013Jaffe). *\/\n  function hjEquivalentC(tC, tH, C, refH) {\n    var TK = tC + 273.15;\n    var P = TK * (C + Math.log(tH) \/ Math.LN10);\n    return { P: P, eqC: P \/ (C + Math.log(refH) \/ Math.LN10) - 273.15 };\n  }\n  function temperEstimate(steel, tC, tH) {\n    var t = steel.temper;\n    if (t.C) {\n      var hj = hjEquivalentC(tC, tH, t.C, t.refH);\n      return { hrc: interpCurve(t.pts, hj.eqC), eqC: hj.eqC, P: hj.P };\n    }\n    return { hrc: interpCurve(t.pts, tC), eqC: tC, P: null };\n  }\n  function calculate(inp) {\n    var R = { ok: false, reasons: [], warnings: [], assumptions: [], source: \"\", native: \"HRC\", nominal: null, low: null, high: null, model: \"\", chart: null, extra: {} };\n    if (inp.grade === \"custom\") {\n      R.reasons.push(\"No validated reference dataset is available for a custom steel, so the calculator will not produce a number.\");\n      R.reasons.push(\"A reliable estimate would need: chemical composition (at least C, Mn, Cr, Mo, Ni, V); a measured tempering curve (hardness vs. temperature at a known time); hardenability (Jominy) data for quenching; and the typical annealed hardness.\");\n      R.source = \"No dataset \\u2014 custom steel is marked unsupported.\";\n      return R;\n    }\n    var s = STEELS[inp.grade];\n    if (!s) { R.reasons.push(\"Unknown steel grade.\"); return R; }\n    var initHRC = convert(inp.initial, inp.scale, \"HRC\");\n    R.extra.initHRC = initHRC;\n    if (inp.treatment === \"temper\") {\n      var t = s.temper, minC = t.pts[0][0], maxC = t.pts[t.pts.length - 1][0];\n      R.model = \"Grade-specific tempering curve, linear interpolation\" + (t.C ? \" with Hollomon\\u2013Jaffe time correction\" : \"\");\n      R.source = DATA_NOTE + (t.C ? \" \" + HJ_NOTE : \"\");\n      if (inp.condition === \"annealed\" || inp.condition === \"normalized\") {\n        R.reasons.push(\"Tempering estimates require a hardened (quenched) starting structure. Tempering \" + CONDITION_LABEL[inp.condition].toLowerCase() + \" steel changes hardness little, and this case is not covered by tempering-curve data.\");\n        R.reasons.push(\"Select 'Quenched' or 'Previously tempered', or run the Quenching calculation first.\");\n        return R;\n      }\n      if (initHRC === null) {\n        R.reasons.push(\"The initial hardness cannot be expressed on the HRC scale (valid ASTM E140 range: 20\\u201368 HRC, 238\\u2013940 HV, 226\\u2013739 HBW). Tempering curves are defined in HRC for hardened steel, so no estimate is given.\");\n        return R;\n      }\n      if (inp.tempC < minC || inp.tempC > maxC) {\n        R.reasons.push(\"Tempering temperature \" + fmtT(inp.tempC, inp.tempUnit) + \" is outside the reference data range for \" + s.name + \" (\" + fmtT(minC, inp.tempUnit) + \" \\u2013 \" + fmtT(maxC, inp.tempUnit) + \"). The calculator does not extrapolate.\");\n        R.outOfRange = true;\n        return R;\n      }\n      if (t.C ? (inp.timeH < 0.25 || inp.timeH > 24) : false) {\n        R.reasons.push(\"Holding time \" + fmtNum(inp.timeH, 2) + \" h is outside the range where the Hollomon\\u2013Jaffe time correction is applied in this tool (0.25\\u201324 h).\");\n        R.outOfRange = true;\n        return R;\n      }\n      var est = temperEstimate(s, inp.tempC, inp.timeH);\n      if (est.hrc === null) {\n        R.reasons.push(\"For \" + fmtNum(inp.timeH, 2) + \" h at \" + fmtT(inp.tempC, inp.tempUnit) + \", the equivalent tempering temperature at the \" + t.refH + \" h reference time is \" + fmtT(est.eqC, inp.tempUnit) + \", which falls outside the reference curve (\" + fmtT(minC, inp.tempUnit) + \" \\u2013 \" + fmtT(maxC, inp.tempUnit) + \"). No extrapolated value is given.\");\n        R.outOfRange = true;\n        return R;\n      }\n      var nominal = est.hrc;\n      if (t.C ? false : Math.abs(inp.timeH - t.refH) > 1e-6) {\n        R.warnings.push(\"Holding-time effects are not modelled for secondary-hardening \" + s.name + \"; the result uses the ~\" + t.refH + \" h reference curve regardless of the \" + fmtNum(inp.timeH, 2) + \" h entered.\");\n        if (inp.timeH < 1 || inp.timeH > 4) { R.warnings.push(\"The entered time is far from the reference time, so the estimate is less reliable.\"); }\n      }\n      if (inp.condition === \"quenched\" ? initHRC < s.quench.aq[0] - 3 : false) {\n        R.warnings.push(\"Initial hardness (\" + fmtNum(initHRC, 1) + \" HRC) is well below typical fully hardened \" + s.name + \" (\" + s.quench.aq[0] + \"\\u2013\" + s.quench.aq[1] + \" HRC). The part may not be fully hardened, and the curve may overstate the tempered hardness.\");\n      }\n      var capped = false;\n      if (nominal > initHRC) {\n        nominal = initHRC; capped = true;\n        if (inp.condition === \"tempered\") {\n          R.warnings.push(\"The estimate is at or above the current hardness, which suggests the new tempering is no more severe than the previous one. Little or no further softening is expected, so the estimate is limited to the initial hardness.\");\n        } else {\n          R.warnings.push(\"The reference curve value is above the measured initial hardness. Tempering does not raise hardness above the as-quenched value (outside secondary-hardening effects), so the estimate is limited to the initial hardness.\");\n        }\n      }\n      if (t.secondary ? inp.tempC >= 450 : false) {\n        R.warnings.push(s.name + \" shows secondary hardening in this range. Hardness depends strongly on austenitizing temperature and on multiple tempers.\");\n      }\n      R.ok = true; R.native = \"HRC\"; R.nominal = nominal;\n      R.low = Math.max(20, nominal - t.band);\n      R.high = capped ? nominal : Math.min(nominal + t.band, initHRC);\n      if (R.high < R.nominal) { R.high = R.nominal; }\n      R.extra.eqC = est.eqC; R.extra.P = est.P;\n      R.assumptions.push(\"Curve basis: \" + s.name + \", \" + t.start + \", tempered ~\" + t.refH + \" h, single temper, air cooled.\");\n      R.assumptions.push(\"Data range: \" + fmtT(minC, inp.tempUnit) + \" \\u2013 \" + fmtT(maxC, inp.tempUnit) + \"; no extrapolation.\");\n      if (t.C) {\n        R.assumptions.push(\"Hollomon\\u2013Jaffe: P = T(K)\\u00b7(C + log\\u2081\\u2080 t[h]), C = \" + t.C + \". P = \" + fmtNum(est.P, 0) + \", equivalent to \" + fmtT(est.eqC, inp.tempUnit) + \" at \" + t.refH + \" h.\");\n      } else {\n        R.assumptions.push(\"No time correction is applied (Hollomon\\u2013Jaffe not calibrated for secondary hardening).\");\n      }\n      R.assumptions.push(\"Indicative band \\u00b1\" + t.band + \" HRC (assumed typical heat-to-heat scatter), limited by the initial hardness.\");\n      R.chart = { type: \"temper\" };\n      return R;\n    }\n    if (inp.treatment === \"quench\") {\n      var q = s.quench;\n      R.model = \"Grade-specific as-quenched hardness range, gated by austenitizing temperature, quench medium and section size\";\n      R.source = DATA_NOTE + \" Through-hardening section limits are conservative indicative values; for heavier sections use Jominy (ASTM A255) hardenability data.\";\n      if (inp.cooling === \"furnace\") {\n        R.reasons.push(\"Furnace cooling from the austenitizing temperature is annealing, not hardening. Choose the Annealing treatment instead.\");\n        return R;\n      }\n      if (!(inp.cooling in q.media)) {\n        R.reasons.push(q.notes[inp.cooling] || (COOLING_LABEL[inp.cooling] + \" is not covered by the reference data for \" + s.name + \".\"));\n        return R;\n      }\n      if (inp.tempC < q.aust[0]) {\n        R.reasons.push(\"Austenitizing temperature \" + fmtT(inp.tempC, inp.tempUnit) + \" is below the recommended range for \" + s.name + \" (\" + fmtT(q.aust[0], inp.tempUnit) + \" \\u2013 \" + fmtT(q.aust[1], inp.tempUnit) + \"). Austenitizing may be incomplete, so the as-quenched hardness cannot be estimated reliably.\");\n        R.outOfRange = true; return R;\n      }\n      if (inp.tempC > q.aust[1]) {\n        R.reasons.push(\"Austenitizing temperature \" + fmtT(inp.tempC, inp.tempUnit) + \" is above the recommended range for \" + s.name + \" (\" + fmtT(q.aust[0], inp.tempUnit) + \" \\u2013 \" + fmtT(q.aust[1], inp.tempUnit) + \"). Overheating causes grain growth, more retained austenite or decarburization, which the reference data does not cover.\");\n        R.outOfRange = true; return R;\n      }\n      var maxMM = q.media[inp.cooling];\n      if (inp.sectionMM > maxMM) {\n        R.reasons.push(\"Section \" + fmtNum(inp.sectionMM, 1) + \" mm exceeds the indicative through-hardening limit for \" + COOLING_LABEL[inp.cooling].toLowerCase() + \" of \" + s.name + \" (\\u2248\" + maxMM + \" mm). Core and even surface hardness depend on hardenability (Jominy) data and the actual cooling rate, which this tool does not have, so no value is predicted.\");\n        R.outOfRange = true; return R;\n      }\n      if (inp.timeH < 10 \/ 60) { R.warnings.push(\"Soak time under 10 minutes may be too short to austenitize and dissolve carbides fully, especially in thicker sections.\"); }\n      if (inp.condition === \"quenched\" || inp.condition === \"tempered\") { R.warnings.push(\"Re-hardening already hardened material without an intermediate anneal or normalize increases distortion and cracking risk.\"); }\n      if (s.temper.secondary) { R.warnings.push(\"As-quenched hardness of high-alloy tool steels depends strongly on the exact austenitizing temperature (carbide dissolution and retained austenite).\"); }\n      R.ok = true; R.native = \"HRC\"; R.low = q.aq[0]; R.high = q.aq[1]; R.nominal = (q.aq[0] + q.aq[1]) \/ 2;\n      R.assumptions.push(\"Typical surface hardness of a correctly austenitized, fully hardened \" + s.name + \" section (\" + q.aq[0] + \"\\u2013\" + q.aq[1] + \" HRC). The nominal value is the midpoint.\");\n      R.assumptions.push(\"Recommended austenitizing range: \" + fmtT(q.aust[0], inp.tempUnit) + \" \\u2013 \" + fmtT(q.aust[1], inp.tempUnit) + \".\");\n      R.assumptions.push(\"Section \\u2264 \\u2248\" + maxMM + \" mm for \" + COOLING_LABEL[inp.cooling].toLowerCase() + \" (indicative through-hardening limit).\");\n      R.assumptions.push(\"Prior structure is fully replaced on austenitizing, so the initial hardness does not affect the as-quenched result. Assumes no decarburization.\");\n      R.chart = { type: \"range\" };\n      return R;\n    }\n    if (inp.treatment === \"anneal\") {\n      var a = s.anneal;\n      R.model = \"Grade-specific typical annealed hardness range (\" + a.kind.toLowerCase() + \", slow furnace cooling)\";\n      R.source = DATA_NOTE;\n      if (inp.cooling === \"air\") {\n        R.reasons.push(\"Air cooling from the annealing temperature is normalizing, not annealing. Normalized hardness data is not included for \" + s.name + \", so no estimate is given.\");\n        return R;\n      }\n      if (inp.cooling !== \"furnace\") {\n        R.reasons.push(COOLING_LABEL[inp.cooling] + \" from this temperature is a hardening operation. Choose the Quenching treatment.\");\n        return R;\n      }\n      if (inp.tempC < a.range[0]) {\n        R.reasons.push(\"Temperature \" + fmtT(inp.tempC, inp.tempUnit) + \" is below the \" + a.kind.toLowerCase() + \" range for \" + s.name + \" (\" + fmtT(a.range[0], inp.tempUnit) + \" \\u2013 \" + fmtT(a.range[1], inp.tempUnit) + \"). Subcritical annealing and stress relieving are not modelled.\");\n        R.outOfRange = true; return R;\n      }\n      if (inp.tempC > a.range[1]) {\n        R.reasons.push(\"Temperature \" + fmtT(inp.tempC, inp.tempUnit) + \" is above the \" + a.kind.toLowerCase() + \" range for \" + s.name + \" (\" + fmtT(a.range[0], inp.tempUnit) + \" \\u2013 \" + fmtT(a.range[1], inp.tempUnit) + \"). Overheating effects are not covered by the data.\");\n        R.outOfRange = true; return R;\n      }\n      var needH = Math.max(1, inp.sectionMM \/ 25);\n      if (inp.timeH < needH) { R.warnings.push(\"Holding time is shorter than the common rule of thumb of about 1 h per 25 mm of section (minimum 1 h), here \\u2248\" + fmtNum(needH, 1) + \" h. The part may not fully soften.\"); }\n      if (s.anneal.kind === \"Spheroidize anneal\") { R.warnings.push(\"Spheroidize annealing of 52100 normally uses a controlled cycle around Ac1 with slow cooling; a simple hold may give a harder result.\"); }\n      R.ok = true; R.native = \"HBW\"; R.low = a.hbw[0]; R.high = a.hbw[1]; R.nominal = (a.hbw[0] + a.hbw[1]) \/ 2;\n      R.assumptions.push(a.kind + \" range \" + fmtT(a.range[0], inp.tempUnit) + \" \\u2013 \" + fmtT(a.range[1], inp.tempUnit) + \", slow furnace cooling.\");\n      R.assumptions.push(\"Typical annealed hardness \" + a.hbw[0] + \"\\u2013\" + a.hbw[1] + \" HBW; the nominal value is the midpoint.\");\n      R.assumptions.push(\"Annealed hardness is below 20 HRC, so the result is reported in HBW. HRC is not valid there.\");\n      R.chart = { type: \"range\" };\n      return R;\n    }\n    R.reasons.push(\"Select a heat treatment type.\");\n    return R;\n  }\n  function fmtNum(v, d) {\n    if (v === null || v === undefined || !isFinite(v)) { return \"\\u2014\"; }\n    var r = Number(v.toFixed(d));\n    if (r === 0) { r = 0; }\n    return r.toLocaleString(\"en-US\", { minimumFractionDigits: d, maximumFractionDigits: d });\n  }\n  function fmtT(c, unit) {\n    return unit === \"F\" ? fmtNum(cToF(c), 0) + \" \\u00b0F\" : fmtNum(c, 0) + \" \\u00b0C\";\n  }\n  function fmtH(v, scale) { return v === null ? \"\\u2014\" : fmtNum(v, SCALE_INFO[scale].dec) + \" \" + scale; }\n  \/* Expose engine for testing \/ extension (read-only use). *\/\n  root.htcEngine = { calculate: calculate, convert: convert, temperEstimate: temperEstimate, STEELS: STEELS, CONV: CONV };\n  \/* =====================================================================\n     UI\n     ===================================================================== *\/\n  function $(sel) { return root.querySelector(sel); }\n  function mk(tag, cls, text) { var n = document.createElement(tag); if (cls) { n.className = cls; } if (text) { n.textContent = text; } return n; }\n  function fill(target, parts) {\n    while (target.firstChild) { target.removeChild(target.firstChild); }\n    parts.forEach(function (p) { target.appendChild(typeof p === \"string\" ? document.createTextNode(p) : p); });\n  }\n  var el = {\n    grade: $(\"#htc-grade\"), condition: $(\"#htc-condition\"), conditionHint: $(\"#htc-condition-hint\"),\n    initial: $(\"#htc-initial\"), scale: $(\"#htc-scale\"), initialHint: $(\"#htc-initial-hint\"), initialErr: $(\"#htc-initial-err\"),\n    info: $(\"#htc-grade-info\"), temp: $(\"#htc-temp\"), tempUnit: $(\"#htc-temp-unit\"), tempLabel: $(\"#htc-temp-label\"),\n    tempHint: $(\"#htc-temp-hint\"), tempErr: $(\"#htc-temp-err\"), time: $(\"#htc-time\"), timeUnit: $(\"#htc-time-unit\"),\n    timeLabel: $(\"#htc-time-label\"), timeHint: $(\"#htc-time-hint\"), timeErr: $(\"#htc-time-err\"),\n    cooling: $(\"#htc-cooling\"), coolingHint: $(\"#htc-cooling-hint\"), thick: $(\"#htc-thick\"), thickUnit: $(\"#htc-thick-unit\"),\n    thickErr: $(\"#htc-thick-err\"), calc: $(\"#htc-calc\"), formMsg: $(\"#htc-form-msg\"),\n    results: $(\"#htc-results\"), unsupported: $(\"#htc-unsupported\"), okBlock: $(\"#htc-ok-block\"),\n    kInitial: $(\"#htc-k-initial\"), kInitialSub: $(\"#htc-k-initial-sub\"), kFinal: $(\"#htc-k-final\"), kFinalSub: $(\"#htc-k-final-sub\"),\n    kDelta: $(\"#htc-k-delta\"), kDeltaSub: $(\"#htc-k-delta-sub\"), kPct: $(\"#htc-k-pct\"), kPctSub: $(\"#htc-k-pct-sub\"),\n    copy: $(\"#htc-copy\"), csv: $(\"#htc-csv\"), toast: $(\"#htc-toast\")\n  };\n  var state = { treatment: \"temper\", lastRows: null, lastInput: null, lastResult: null };\n  function treatment() {\n    var r = root.querySelector('input[name=\"htc-treatment\"]:checked');\n    return r ? r.value : \"temper\";\n  }\n  function numVal(input) {\n    var s = String(input.value).trim();\n    if (s === \"\") { return null; }\n    var v = Number(s);\n    return isFinite(v) ? v : NaN;\n  }\n  function setErr(input, errEl, msg) {\n    errEl.textContent = msg || \"\";\n    if (msg) { input.classList.add(\"htc-invalid\"); input.setAttribute(\"aria-invalid\", \"true\"); }\n    else { input.classList.remove(\"htc-invalid\"); input.removeAttribute(\"aria-invalid\"); }\n    return !msg;\n  }\n  function validateInitial() {\n    var v = numVal(el.initial), sc = SCALE_INFO[el.scale.value];\n    if (v === null) { return setErr(el.initial, el.initialErr, \"Enter the initial hardness.\"); }\n    if (isNaN(v)) { return setErr(el.initial, el.initialErr, \"Enter a valid number.\"); }\n    if (v < sc.min || v > sc.max) {\n      var msg = sc.label + \" must be between \" + sc.min + \" and \" + sc.max + \".\";\n      if (el.scale.value === \"HRC\" ? (v > 0 ? v < 20 : false) : false) { msg = \"HRC below 20 is not a valid Rockwell C reading. Use HBW or HV for softer steel.\"; }\n      return setErr(el.initial, el.initialErr, msg);\n    }\n    return setErr(el.initial, el.initialErr, \"\");\n  }\n  function validateTemp() {\n    var v = numVal(el.temp);\n    if (v === null) { return setErr(el.temp, el.tempErr, \"Enter a temperature.\"); }\n    if (isNaN(v)) { return setErr(el.temp, el.tempErr, \"Enter a valid number.\"); }\n    var c = el.tempUnit.value === \"F\" ? fToC(v) : v;\n    if (c < 20 || c > 1300) { return setErr(el.temp, el.tempErr, \"Temperature must be between \" + fmtT(20, el.tempUnit.value) + \" and \" + fmtT(1300, el.tempUnit.value) + \".\"); }\n    return setErr(el.temp, el.tempErr, \"\");\n  }\n  function validateTime() {\n    var v = numVal(el.time);\n    if (v === null) { return setErr(el.time, el.timeErr, \"Enter a holding time.\"); }\n    if (isNaN(v) || v <= 0) { return setErr(el.time, el.timeErr, \"Holding time must be greater than zero.\"); }\n    var h = el.timeUnit.value === \"min\" ? v \/ 60 : v;\n    if (h > 100) { return setErr(el.time, el.timeErr, \"Holding time above 100 h is not realistic for this tool.\"); }\n    return setErr(el.time, el.timeErr, \"\");\n  }\n  function validateThick() {\n    if (state.treatment === \"temper\") { return setErr(el.thick, el.thickErr, \"\"); }\n    var v = numVal(el.thick);\n    if (v === null) { return setErr(el.thick, el.thickErr, \"Enter the section thickness.\"); }\n    if (isNaN(v) || v <= 0) { return setErr(el.thick, el.thickErr, \"Thickness must be greater than zero.\"); }\n    var mm = el.thickUnit.value === \"in\" ? v * 25.4 : v;\n    if (mm > 1000) { return setErr(el.thick, el.thickErr, \"Thickness above 1000 mm is outside this tool's scope.\"); }\n    return setErr(el.thick, el.thickErr, \"\");\n  }\n  function populateCooling() {\n    var list = COOLING_BY_TREAT[state.treatment];\n    if (!list) { return; }\n    var prev = el.cooling.value;\n    fill(el.cooling, []);\n    list.forEach(function (k) { var o = document.createElement(\"option\"); o.value = k; o.textContent = COOLING_LABEL[k]; el.cooling.appendChild(o); });\n    var s = STEELS[el.grade.value];\n    var pick = list.indexOf(prev) >= 0 ? prev : list[0];\n    if (state.treatment === \"quench\" ? !!s : false) {\n      var supported = Object.keys(s.quench.media);\n      if (supported.indexOf(pick) < 0) { pick = supported[0]; }\n    }\n    el.cooling.value = pick;\n    updateCoolingHint();\n  }\n  function updateCoolingHint() {\n    var s = STEELS[el.grade.value];\n    if (!s) { el.coolingHint.textContent = \"\"; return; }\n    if (state.treatment === \"quench\") {\n      var m = s.quench.media, parts = [];\n      Object.keys(m).forEach(function (k) { parts.push(COOLING_LABEL[k].toLowerCase() + \" \\u2264 \\u2248\" + m[k] + \" mm\"); });\n      el.coolingHint.textContent = \"Modelled for \" + s.name + \": \" + parts.join(\", \") + \".\";\n    } else if (state.treatment === \"anneal\") {\n      el.coolingHint.textContent = \"Annealing requires slow furnace cooling. Air cooling is normalizing.\";\n    }\n  }\n  function updateTreatmentUI() {\n    state.treatment = treatment();\n    var t = state.treatment;\n    var nodes = root.querySelectorAll(\".htc-treat\");\n    for (var i = 0; i < nodes.length; i++) {\n      var show = (\" \" + nodes[i].getAttribute(\"data-htc-for\") + \" \").indexOf(\" \" + t + \" \") >= 0;\n      nodes[i].hidden = !show;\n    }\n    el.tempLabel.textContent = t === \"quench\" ? \"Austenitizing temperature\" : t === \"temper\" ? \"Tempering temperature\" : \"Annealing temperature\";\n    el.timeLabel.textContent = t === \"quench\" ? \"Soak time at austenitizing temperature\" : t === \"temper\" ? \"Tempering time at temperature\" : \"Holding time at annealing temperature\";\n    populateCooling();\n    updateHints();\n  }\n  function updateHints() {\n    var s = STEELS[el.grade.value], t = state.treatment, u = el.tempUnit.value;\n    var cond = el.condition.value;\n    if (t === \"temper\") {\n      el.conditionHint.textContent = (cond === \"quenched\" || cond === \"tempered\") ? \"Tempering starts from this hardened condition.\" : \"Tempering estimates need a Quenched or Previously tempered start.\";\n    } else if (t === \"quench\") {\n      el.conditionHint.textContent = \"Prior condition is replaced on austenitizing.\";\n    } else {\n      el.conditionHint.textContent = \"Usually applied to hardened, cold-worked or normalized material.\";\n    }\n    if (!s) {\n      el.tempHint.textContent = \"\"; el.timeHint.textContent = \"\";\n      el.info.className = \"htc-grade-info htc-unsupported\";\n      fill(el.info, [mk(\"strong\", \"\", \"Custom steel \\u2014 unsupported.\"), \" No validated reference dataset is available. A reliable estimate would need composition, a measured tempering curve, hardenability (Jominy) data and annealed hardness. The calculator will explain this rather than give a number.\"]);\n      return;\n    }\n    el.info.className = \"htc-grade-info\";\n    var tp = s.temper.pts;\n    fill(el.info, [\n      mk(\"strong\", \"\", s.name), \" \\u00b7 \" + s.family + \" \\u00b7 nominal C \\u2248 \" + s.carbon.toFixed(2) + \"%\", mk(\"br\"),\n      \"Tempering data: \" + fmtT(tp[0][0], u) + \" \\u2013 \" + fmtT(tp[tp.length - 1][0], u) + \" (\" + s.temper.start + \")\" + (s.temper.C ? \"; time-corrected (Hollomon\\u2013Jaffe C = \" + s.temper.C + \")\" : \"; secondary hardening, time effect not modelled\"), mk(\"br\"),\n      \"Austenitizing: \" + fmtT(s.quench.aust[0], u) + \" \\u2013 \" + fmtT(s.quench.aust[1], u) + \" \\u2192 \" + s.quench.aq[0] + \"\\u2013\" + s.quench.aq[1] + \" HRC \\u00b7 \" +\n        s.anneal.kind + \": \" + fmtT(s.anneal.range[0], u) + \" \\u2013 \" + fmtT(s.anneal.range[1], u) + \" \\u2192 \" + s.anneal.hbw[0] + \"\\u2013\" + s.anneal.hbw[1] + \" HBW\", mk(\"br\"),\n      mk(\"span\", \"htc-badge htc-badge-warn\", \"Representative data\"), \" Verify against supplier data before critical use.\"\n    ]);\n    var r = t === \"temper\" ? [tp[0][0], tp[tp.length - 1][0]] : t === \"quench\" ? s.quench.aust : s.anneal.range;\n    el.tempHint.textContent = \"Supported for \" + s.name + \": \" + fmtT(r[0], u) + \" \\u2013 \" + fmtT(r[1], u) + \".\";\n    if (t === \"temper\") {\n      el.timeHint.textContent = s.temper.C ? \"Time-corrected between 0.25 and 24 h (reference \" + s.temper.refH + \" h).\" : \"Reference curve ~\" + s.temper.refH + \" h; time effect not modelled.\";\n    } else if (t === \"quench\") {\n      el.timeHint.textContent = \"Time at temperature after the part is heated through.\";\n    } else {\n      el.timeHint.textContent = \"Rule of thumb: about 1 h per 25 mm of section, minimum 1 h.\";\n    }\n  }\n  function readInput() {\n    var tempV = numVal(el.temp), timeV = numVal(el.time), thickV = numVal(el.thick);\n    return {\n      grade: el.grade.value, condition: el.condition.value, initial: numVal(el.initial), scale: el.scale.value,\n      treatment: state.treatment, tempUnit: el.tempUnit.value,\n      tempC: el.tempUnit.value === \"F\" ? fToC(tempV) : tempV, tempRaw: tempV,\n      timeH: el.timeUnit.value === \"min\" ? timeV \/ 60 : timeV, timeRaw: timeV, timeUnit: el.timeUnit.value,\n      cooling: state.treatment === \"temper\" ? null : el.cooling.value,\n      sectionMM: el.thickUnit.value === \"in\" ? thickV * 25.4 : thickV, thickRaw: thickV, thickUnit: el.thickUnit.value\n    };\n  }\n  function onCalculate() {\n    el.formMsg.textContent = \"\";\n    var ok = [validateInitial(), validateTemp(), validateTime(), validateThick()].every(Boolean);\n    if (!ok) {\n      el.formMsg.textContent = \"Please correct the highlighted fields.\";\n      var first = root.querySelector(\".htc-invalid\");\n      if (first) { first.focus(); }\n      return;\n    }\n    var inp = readInput();\n    var R = calculate(inp);\n    state.lastInput = inp; state.lastResult = R;\n    render(inp, R);\n  }\n  function li(text) { var n = document.createElement(\"li\"); n.textContent = text; return n; }\n  function conditionRows(inp) {\n    var s = STEELS[inp.grade];\n    var rows = [\n      [\"Steel grade\", s ? s.name : \"Custom steel\"],\n      [\"Initial condition\", CONDITION_LABEL[inp.condition]],\n      [\"Treatment\", TREAT_LABEL[inp.treatment]],\n      [inp.treatment === \"quench\" ? \"Austenitizing temperature\" : inp.treatment === \"temper\" ? \"Tempering temperature\" : \"Annealing temperature\",\n        fmtT(inp.tempC, \"C\") + \" \/ \" + fmtT(inp.tempC, \"F\")],\n      [\"Holding time\", fmtNum(inp.timeH, 2) + \" h (\" + fmtNum(inp.timeH * 60, 0) + \" min)\"]\n    ];\n    if (inp.treatment !== \"temper\") {\n      rows.push([\"Cooling method\", COOLING_LABEL[inp.cooling]]);\n      rows.push([\"Section thickness\", fmtNum(inp.sectionMM, 1) + \" mm (\" + fmtNum(inp.sectionMM \/ 25.4, 2) + \" in)\"]);\n    }\n    return rows;\n  }\n  function render(inp, R) {\n    el.results.hidden = false;\n    var rows = [[\"Initial hardness (entered)\", fmtH(inp.initial, inp.scale)]];\n    if (!R.ok) {\n      el.okBlock.hidden = true;\n      el.unsupported.hidden = false;\n      fill(el.unsupported, [mk(\"strong\", \"\", R.outOfRange ? \"Outside the model's validated range \\u2014 no estimate shown.\" : \"Unsupported calculation \\u2014 no estimate shown.\")]);\n      var ul = document.createElement(\"ul\");\n      R.reasons.forEach(function (t) { ul.appendChild(li(t)); });\n      el.unsupported.appendChild(ul);\n      rows.push([\"Status\", R.outOfRange ? \"Outside validated range\" : \"Unsupported\"]);\n      R.reasons.forEach(function (t) { rows.push([\"Reason\", t]); });\n      conditionRows(inp).forEach(function (r) { rows.push(r); });\n      rows.push([\"Data source\", R.source || \"\\u2014\"]);\n      state.lastRows = rows;\n      focusResults();\n      return;\n    }\n    el.unsupported.hidden = true;\n    el.okBlock.hidden = false;\n    \/* Choose display scale: the user's scale when the estimate can be expressed in it; otherwise the native scale. *\/\n    var disp = inp.scale;\n    var fN = convert(R.nominal, R.native, disp), fL = convert(R.low, R.native, disp), fH = convert(R.high, R.native, disp);\n    var iD = inp.initial;\n    var note = \"\";\n    if (fN === null || fL === null || fH === null) {\n      disp = R.native; fN = R.nominal; fL = R.low; fH = R.high;\n      iD = convert(inp.initial, inp.scale, disp);\n      note = \"The estimate cannot be expressed in \" + inp.scale + \" within the ASTM E140 range, so results are shown in \" + disp + \".\";\n    }\n    var dec = SCALE_INFO[disp].dec;\n    el.kInitial.textContent = fmtH(inp.initial, inp.scale);\n    el.kInitialSub.textContent = inp.scale !== disp ? (iD !== null ? \"\\u2248 \" + fmtH(iD, disp) + \" (converted)\" : \"Not convertible to \" + disp) : (function () {\n      var alt = inp.scale === \"HRC\" ? convert(inp.initial, \"HRC\", \"HV\") : convert(inp.initial, inp.scale, \"HRC\");\n      return alt === null ? \"Measured value\" : \"\\u2248 \" + fmtH(alt, inp.scale === \"HRC\" ? \"HV\" : \"HRC\") + \" (converted)\";\n    })();\n    el.kFinal.textContent = fmtH(fN, disp);\n    el.kFinalSub.textContent = \"Range \" + fmtNum(fL, dec) + \"\\u2013\" + fmtNum(fH, dec) + \" \" + disp;\n    var delta = iD !== null ? fN - iD : null;\n    var cls = delta === null ? \"htc-flat\" : Math.abs(delta) < Math.pow(10, -dec) \/ 2 ? \"htc-flat\" : delta > 0 ? \"htc-up\" : \"htc-down\";\n    el.kDelta.className = \"htc-kpi-value \" + cls;\n    el.kDelta.textContent = delta === null ? \"n\/a\" : (delta > 0 ? \"+\" : delta < 0 ? \"\\u2212\" : \"\") + fmtNum(Math.abs(delta), dec) + \" \" + disp;\n    el.kDeltaSub.textContent = delta === null ? \"Initial value not convertible to \" + disp : \"\\u0394H = H_final \\u2212 H_initial\";\n    var pct = null, pctNote = \"\";\n    if (delta !== null ? (disp === \"HV\" || disp === \"HBW\") : false) {\n      pct = (fN - iD) \/ iD * 100; pctNote = \"Based on \" + disp + \" values\";\n    } else if (disp === \"HRC\") {\n      var iHV = convert(iD, \"HRC\", \"HV\"), fHV = convert(fN, \"HRC\", \"HV\");\n      if (iHV !== null ? fHV !== null : false) { pct = (fHV - iHV) \/ iHV * 100; pctNote = \"HRC is not a ratio scale; calculated on converted HV values\"; }\n      else { pctNote = \"Not valid on HRC scale\"; }\n    } else { pctNote = \"Not available\"; }\n    el.kPct.className = \"htc-kpi-value \" + (pct === null ? \"htc-flat\" : cls);\n    el.kPct.textContent = pct === null ? \"n\/a\" : (pct > 0 ? \"+\" : pct < 0 ? \"\\u2212\" : \"\") + fmtNum(Math.abs(pct), 1) + \"%\";\n    el.kPctSub.textContent = pctNote;\n    var warns = R.warnings.slice();\n    if (note) { warns.unshift(note); }\n    rows.push([\"Initial hardness (\" + disp + \")\", iD === null ? \"n\/a\" : fmtH(iD, disp)]);\n    rows.push([\"Estimated final hardness\", fmtH(fN, disp)]);\n    rows.push([\"Estimated range\", fmtNum(fL, dec) + \"\\u2013\" + fmtNum(fH, dec) + \" \" + disp]);\n    rows.push([\"Hardness change\", el.kDelta.textContent]);\n    rows.push([\"Percentage change\", el.kPct.textContent + (pctNote ? \" (\" + pctNote + \")\" : \"\")]);\n    if (R.native !== disp) { rows.push([\"Estimate (native scale)\", fmtH(R.nominal, R.native) + \" (\" + fmtNum(R.low, SCALE_INFO[R.native].dec) + \"\\u2013\" + fmtNum(R.high, SCALE_INFO[R.native].dec) + \")\"]); }\n    conditionRows(inp).forEach(function (r) { rows.push(r); });\n    rows.push([\"Model\", R.model]);\n    R.assumptions.forEach(function (a) { rows.push([\"Assumption\", a]); });\n    warns.forEach(function (w) { rows.push([\"Warning\", w]); });\n    rows.push([\"Data source\", R.source]);\n    rows.push([\"Note\", \"Engineering estimate only \\u2014 confirm with standardized hardness testing.\"]);\n    state.lastRows = rows;\n    focusResults();\n  }\n  function focusResults() {\n    try { el.results.focus({ preventScroll: true }); } catch (e) { \/* ignore *\/ }\n    if (el.results.scrollIntoView) { el.results.scrollIntoView({ behavior: \"smooth\", block: \"nearest\" }); }\n  }\n  \/* ---------- Copy \/ CSV ---------- *\/\n  function toast(msg) {\n    el.toast.textContent = msg; el.toast.classList.add(\"htc-show\");\n    clearTimeout(state.toastT); state.toastT = setTimeout(function () { el.toast.classList.remove(\"htc-show\"); }, 2200);\n  }\n  function resultsText() {\n    if (!state.lastRows) { return \"\"; }\n    return \"Heat Treatment Hardness Change Calculator \\u2014 results\\n\" + state.lastRows.map(function (r) { return r[0] + \": \" + r[1]; }).join(\"\\n\");\n  }\n  function onCopy() {\n    var txt = resultsText();\n    if (!txt) { toast(\"Calculate first.\"); return; }\n    var fallback = function () {\n      var ta = document.createElement(\"textarea\");\n      ta.value = txt; ta.setAttribute(\"readonly\", \"\"); ta.style.position = \"fixed\"; ta.style.opacity = \"0\";\n      root.appendChild(ta); ta.select();\n      var ok = false; try { ok = document.execCommand(\"copy\"); } catch (e) { ok = false; }\n      root.removeChild(ta);\n      toast(ok ? \"Results copied\" : \"Copy failed \\u2014 please select and copy manually\");\n    };\n    if (navigator.clipboard ? !!window.isSecureContext : false) {\n      navigator.clipboard.writeText(txt).then(function () { toast(\"Results copied\"); }, fallback);\n    } else { fallback(); }\n  }\n  function onCSV() {\n    if (!state.lastRows) { toast(\"Calculate first.\"); return; }\n    var q = function (s) { return \"\\\"\" + String(s).split(\"\\\"\").join(\"\\\"\\\"\") + \"\\\"\"; };\n    var csv = String.fromCharCode(65279) + [[\"Parameter\", \"Value\"]].concat(state.lastRows).map(function (r) { return q(r[0]) + \",\" + q(r[1]); }).join(\"\\r\\n\");\n    var blob = new Blob([csv], { type: \"text\/csv;charset=utf-8\" });\n    var url = URL.createObjectURL(blob);\n    var a = document.createElement(\"a\");\n    a.href = url; a.download = \"heat-treatment-hardness-results.csv\";\n    a.style.display = \"none\"; root.appendChild(a); a.click(); root.removeChild(a);\n    setTimeout(function () { URL.revokeObjectURL(url); }, 1000);\n    toast(\"CSV downloaded\");\n  }\n  \/* ---------- Unit switching converts the entered value ---------- *\/\n  function bindUnitConvert(sel, input, fn, dec, validate) {\n    sel.setAttribute(\"data-prev\", sel.value);\n    sel.addEventListener(\"change\", function () {\n      var prev = sel.getAttribute(\"data-prev\"), v = numVal(input);\n      if (v !== null ? !isNaN(v) : false) { input.value = String(Number(fn(v, prev, sel.value).toFixed(dec))); }\n      sel.setAttribute(\"data-prev\", sel.value);\n      updateHints(); if (validate) { validate(); }\n    });\n  }\n  bindUnitConvert(el.tempUnit, el.temp, function (v, a, b) { return a === b ? v : b === \"F\" ? cToF(v) : fToC(v); }, 1, validateTemp);\n  bindUnitConvert(el.timeUnit, el.time, function (v, a, b) { return a === b ? v : b === \"min\" ? v * 60 : v \/ 60; }, 3, validateTime);\n  bindUnitConvert(el.thickUnit, el.thick, function (v, a, b) { return a === b ? v : b === \"in\" ? v \/ 25.4 : v * 25.4; }, 3, validateThick);\n  function updateInitialHint() {\n    var sc = el.scale.value;\n    el.initialHint.textContent = sc === \"HRC\" ? \"HRC valid range 20\\u201368. Use HBW or HV for softer material.\" :\n      sc === \"HBW\" ? \"Conversions to HRC\/HV valid for \" + convRange(\"HBW\").join(\"\\u2013\") + \" HBW (ASTM E140).\" :\n      \"Conversions to HRC\/HBW valid for \" + convRange(\"HV\").join(\"\\u2013\") + \" HV (ASTM E140).\";\n  }\n  el.scale.addEventListener(\"change\", function () {\n    var prev = el.scale.getAttribute(\"data-prev\") || \"HRC\", v = numVal(el.initial);\n    if (v !== null ? !isNaN(v) : false) {\n      var c = convert(v, prev, el.scale.value);\n      if (c !== null) { el.initial.value = String(Number(c.toFixed(SCALE_INFO[el.scale.value].dec))); }\n    }\n    el.scale.setAttribute(\"data-prev\", el.scale.value);\n    updateInitialHint(); validateInitial();\n  });\n  el.scale.setAttribute(\"data-prev\", el.scale.value);\n  \/* ---------- Real-time validation and events ---------- *\/\n  el.initial.addEventListener(\"input\", validateInitial);\n  el.temp.addEventListener(\"input\", validateTemp);\n  el.time.addEventListener(\"input\", validateTime);\n  el.thick.addEventListener(\"input\", validateThick);\n  \/* Suggest an in-range temperature (and time) when the meaning of the fields changes. *\/\n  function supportedRangeC() {\n    var s = STEELS[el.grade.value];\n    if (!s) { return null; }\n    var tp = s.temper.pts;\n    return state.treatment === \"temper\" ? [tp[0][0], tp[tp.length - 1][0]] : state.treatment === \"quench\" ? s.quench.aust : s.anneal.range;\n  }\n  function suggestTemp(force) {\n    var r = supportedRangeC();\n    if (!r) { return; }\n    var v = numVal(el.temp), c = (v === null || isNaN(v)) ? null : (el.tempUnit.value === \"F\" ? fToC(v) : v);\n    if (!force) { if (c !== null) { if (c >= r[0] ? c <= r[1] : false) { return; } } }\n    var mid = Math.round((r[0] + r[1]) \/ 2 \/ 5) * 5;\n    el.temp.value = String(el.tempUnit.value === \"F\" ? Math.round(cToF(mid)) : mid);\n    validateTemp();\n  }\n  function suggestTime() {\n    var h = state.treatment === \"quench\" ? 0.5 : 2;\n    el.time.value = String(el.timeUnit.value === \"min\" ? h * 60 : h);\n    validateTime();\n  }\n  el.grade.addEventListener(\"change\", function () { populateCooling(); suggestTemp(false); updateHints(); });\n  el.condition.addEventListener(\"change\", updateHints);\n  el.cooling.addEventListener(\"change\", updateCoolingHint);\n  var radios = root.querySelectorAll('input[name=\"htc-treatment\"]');\n  for (var i = 0; i < radios.length; i++) {\n    radios[i].addEventListener(\"change\", function () {\n      updateTreatmentUI(); suggestTemp(true); suggestTime(); validateThick();\n      if (state.treatment === \"temper\" ? (el.condition.value === \"annealed\" || el.condition.value === \"normalized\") : false) { el.condition.value = \"quenched\"; }\n      if (state.treatment === \"quench\" ? el.condition.value === \"quenched\" : false) { el.condition.value = \"annealed\"; }\n      updateHints();\n    });\n  }\n  el.calc.addEventListener(\"click\", onCalculate);\n  el.copy.addEventListener(\"click\", onCopy);\n  el.csv.addEventListener(\"click\", onCSV);\n  root.querySelector(\".htc-form\").addEventListener(\"keydown\", function (e) {\n    if (e.key === \"Enter\" ? (e.target ? e.target.tagName === \"INPUT\" : false) : false) { e.preventDefault(); onCalculate(); }\n  });\n  updateTreatmentUI();\n  updateInitialHint();\n})();\n<\/script>\n <\/div>\n","protected":false},"excerpt":{"rendered":"<p>The Heat Treatment Hardness Change Calculator helps engineers, metallurgists, and manufacturing professionals estimate changes in steel hardness after heat treatment processes such as quenching, tempering,&#8230;<\/p>","protected":false},"author":2,"featured_media":24930,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pmpro_default_level":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[8647],"tags":[8729,8732,4921,8510,8731],"class_list":["post-24929","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-materials-science-and-properties","tag-hardness-calculator","tag-hardness-estimation","tag-heat-treatment","tag-material-properties","tag-steel-hardness","pmpro-has-access"],"acf":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/www.mechstream.com\/wp-content\/uploads\/2026\/10\/Heat-Treatment-Hardness-Change-Calculator.png","_links":{"self":[{"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/posts\/24929","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/comments?post=24929"}],"version-history":[{"count":2,"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/posts\/24929\/revisions"}],"predecessor-version":[{"id":24932,"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/posts\/24929\/revisions\/24932"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/media\/24930"}],"wp:attachment":[{"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/media?parent=24929"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/categories?post=24929"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mechstream.com\/ru\/wp-json\/wp\/v2\/tags?post=24929"}],"curies":[{"name":"WP","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}