{"id":25114,"date":"2026-11-13T03:24:41","date_gmt":"2026-11-13T03:24:41","guid":{"rendered":"https:\/\/www.mechstream.com\/?p=25114"},"modified":"2026-11-13T03:30:53","modified_gmt":"2026-11-13T03:30:53","slug":"welding-residual-stress-calculator","status":"publish","type":"post","link":"https:\/\/www.mechstream.com\/fr\/welding-residual-stress-calculator\/","title":{"rendered":"Welding Residual Stress Calculator"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The <strong>Welding Residual Stress Calculator<\/strong> helps estimate relative residual stress risk based on welding heat input, material properties, plate thickness, joint type, weld sequence, and restraint level. Use it as a preliminary reference to compare welding conditions and consider ways to manage residual stress. Actual results vary with the material, joint design, and fabrication process, so engineering review may be needed for precise analysis.<\/p>\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Welding Residual Stress Calculator<\/title>\n<meta name=\"description\" content=\"Preliminary welding residual stress risk calculator: relative stress index and screening stress range from process, material, geometry, heat input, sequence, restraint, preheat and PWHT.\">\n<\/head>\n<body>\n\n<div id=\"wrsc\" class=\"wrsc-calculator\">\n<style>\n  \/* ---- Root container: all styles are scoped to #wrsc so the host page (e.g. WordPress theme) is not affected ---- *\/\n  #wrsc{max-width:980px;margin:0 auto;padding:1.25rem 0;font-family:system-ui,-apple-system,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;color:var(--ink);line-height:1.5;font-size:16px;text-align:left}\n  #wrsc *,#wrsc *::before,#wrsc *::after{box-sizing:border-box}\n  \/* Neutralise common theme styles inside the calculator only *\/\n  #wrsc h2,#wrsc h3{font-family:inherit;letter-spacing:normal;text-transform:none;color:var(--ink);line-height:1.3;padding:0;border:0;background:none}\n  #wrsc p{padding:0}\n  #wrsc input,#wrsc select,#wrsc button{margin:0;box-shadow:none;text-transform:none;letter-spacing:normal;line-height:1.4;height:auto;max-width:none;font-family:inherit}\n  #wrsc fieldset{background:none}\n  #wrsc legend{float:none;width:auto;border:0;background:none;text-transform:none}\n  #wrsc table,#wrsc th,#wrsc td{background:transparent;border-left:0;border-right:0;border-top:0}\n  #wrsc table{margin:0;border:0;table-layout:auto}\n  #wrsc ul{list-style:disc}\n  #wrsc ul.bars{list-style:none}\n  #wrsc [hidden]{display:none!important}\n  #wrsc{\n    --ink:#1b2430; --muted:#4a5563; --bg:#f3f4f6; --panel:#ffffff; --line:#d5dae1;\n    --accent:#1d4ed8; --accent-dark:#1e40af; --accent-soft:#eff6ff;\n    --low:#15803d; --low-bg:#e9f7ee; --mod:#a15c07; --mod-bg:#fdf3e1; --high:#b91c1c; --high-bg:#fdecec;\n    --err:#b91c1c; --warn:#8a4b06; --focus:#1d4ed8;\n    --radius:10px; --shadow:0 1px 2px rgba(16,24,40,.06),0 4px 14px rgba(16,24,40,.06);\n  }\n  #wrsc a{color:var(--accent-dark)}\n  #wrsc .layout{display:grid;grid-template-columns:minmax(0,1fr);gap:1.25rem;max-width:980px;margin:0 auto}\n  #wrsc .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}\n  #wrsc form.card{padding:1.1rem 1.15rem 1.25rem}\n  #wrsc fieldset{border:0;margin:0 0 1rem;padding:0;min-width:0}\n  #wrsc fieldset.section{border-top:1px solid var(--line);padding-top:1rem}\n  #wrsc fieldset.section:first-of-type{border-top:0;padding-top:0}\n  #wrsc legend{font-weight:700;font-size:1.02rem;padding:0;margin-bottom:.6rem;color:var(--ink)}\n  #wrsc .section > legend{display:flex;align-items:center;gap:.5rem}\n  #wrsc .section > legend .num{display:inline-grid;place-items:center;width:1.5rem;height:1.5rem;border-radius:50%;background:var(--ink);color:#fff;font-size:.8rem}\n  #wrsc .grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem 1rem}\n  #wrsc .grid.three{grid-template-columns:repeat(3,minmax(0,1fr))}\n  @media (max-width:640px){#wrsc .grid,#wrsc .grid.three{grid-template-columns:minmax(0,1fr)}}\n  #wrsc .field{display:flex;flex-direction:column;min-width:0}\n  #wrsc .field label,#wrsc .field .lbl{font-weight:600;font-size:.92rem;margin-bottom:.3rem}\n  #wrsc .opt{font-weight:400;color:var(--muted);font-size:.85rem}\n  #wrsc input[type=text],#wrsc select{font:inherit;color:var(--ink);background:#fff;border:1px solid #9aa4b2;border-radius:8px;padding:.55rem .65rem;min-height:44px;width:100%}\n  #wrsc input[type=text]:focus,#wrsc select:focus,#wrsc button:focus-visible,#wrsc summary:focus-visible,#wrsc input[type=radio]:focus-visible+span{outline:3px solid var(--focus);outline-offset:2px}\n  #wrsc input[aria-invalid=true]{border-color:var(--err);box-shadow:0 0 0 1px var(--err)}\n  #wrsc input.is-warn{border-color:#c47a12}\n  #wrsc .with-unit{display:flex;gap:.4rem}\n  #wrsc .with-unit input{flex:1 1 auto;min-width:0}\n  #wrsc .with-unit select{flex:0 0 auto;width:auto;min-width:5.6rem;background:#f8fafc}\n  #wrsc .hint{font-size:.83rem;color:var(--muted);margin:.25rem 0 0}\n  #wrsc .msg{font-size:.83rem;margin:.25rem 0 0;min-height:0}\n  #wrsc .msg.error{color:var(--err);font-weight:600}\n  #wrsc .msg.warn{color:var(--warn)}\n  #wrsc .msg:empty{display:none}\n  #wrsc .seg{display:flex;flex-wrap:wrap;gap:.5rem}\n  #wrsc .seg label{position:relative;cursor:pointer;margin:0}\n  #wrsc .seg input{position:absolute;opacity:0;width:1px;height:1px}\n  #wrsc .seg span{display:inline-block;padding:.55rem .95rem;border:1px solid #9aa4b2;border-radius:999px;background:#fff;font-weight:600;font-size:.92rem;min-height:44px;line-height:1.6}\n  #wrsc .seg input:checked+span{background:var(--ink);border-color:var(--ink);color:#fff}\n  #wrsc .mode-desc{font-size:.86rem;color:var(--muted);margin:.5rem 0 0}\n  #wrsc .subpanel{background:#f8fafc;border:1px solid var(--line);border-radius:8px;padding:.85rem;margin-top:.75rem}\n  #wrsc .formula{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.82rem;background:#eef1f5;border-radius:6px;padding:.45rem .6rem;margin:.6rem 0 0;overflow-x:auto;white-space:nowrap}\n  #wrsc .actions{display:flex;justify-content:center;margin-top:1.25rem}\n  #wrsc .actions .btn-primary{min-width:12rem;padding:.75rem 2rem;font-size:1.05rem}\n  #wrsc button{font:inherit;font-weight:700;border-radius:8px;padding:.65rem 1.2rem;min-height:44px;cursor:pointer;border:1px solid transparent}\n  #wrsc .btn-primary{background:var(--accent);color:#fff}\n  #wrsc .btn-primary:hover{background:var(--accent-dark)}\n  #wrsc .btn-secondary{background:#fff;color:var(--ink);border-color:#9aa4b2}\n  #wrsc .btn-secondary:hover{background:#f1f3f6}\n  #wrsc .btn-ghost{background:transparent;color:var(--accent-dark);border-color:transparent;text-decoration:underline;padding-left:.4rem;padding-right:.4rem}\n  #wrsc details.cfg{border:1px solid var(--line);border-radius:8px;margin-top:1rem;background:#fbfcfd}\n  #wrsc details.cfg summary{cursor:pointer;padding:.75rem .9rem;font-weight:700}\n  #wrsc .cfg-body{padding:0 .9rem .9rem}\n  #wrsc .cfg-group{border:1px solid var(--line);border-radius:8px;padding:.7rem .8rem .4rem;margin:0 0 .75rem;background:#fff}\n  #wrsc .cfg-group legend{font-size:.92rem;padding:0 .3rem;margin:0}\n  #wrsc .cfg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:.5rem .75rem;margin-top:.4rem}\n  #wrsc .cfg-item label{display:block;font-size:.8rem;font-weight:600;margin-bottom:.15rem}\n  #wrsc .cfg-item input{min-height:38px;padding:.35rem .5rem;font-size:.9rem}\n  #wrsc #wrsc-cfgStatus{font-size:.86rem;margin:.25rem 0 .6rem}\n  #wrsc \/* Results *\/\n  .results{padding:1.1rem 1.15rem 1.25rem;scroll-margin-top:1rem}\n  #wrsc .results h2{margin:0 0 .75rem;font-size:1.2rem}\n  #wrsc .risk{border-radius:10px;padding:1rem;margin-bottom:1rem;border:1px solid}\n  #wrsc .risk.low{background:var(--low-bg);border-color:#a7dcb9}\n  #wrsc .risk.moderate{background:var(--mod-bg);border-color:#efcf95}\n  #wrsc .risk.high{background:var(--high-bg);border-color:#f2b2b2}\n  #wrsc .risk .label{font-size:.82rem;text-transform:uppercase;letter-spacing:.08em;font-weight:700;color:var(--muted)}\n  #wrsc .risk .level{font-size:1.7rem;font-weight:800;line-height:1.2;margin:.15rem 0}\n  #wrsc .risk.low .level{color:var(--low)} #wrsc .risk.moderate .level{color:var(--mod)} #wrsc .risk.high .level{color:var(--high)}\n  #wrsc .risk .idx{font-size:.95rem}\n  #wrsc .gauge{position:relative;height:12px;border-radius:999px;margin:.8rem 0 .3rem;overflow:visible}\n  #wrsc .gauge .zones{display:flex;height:100%;border-radius:999px;overflow:hidden}\n  #wrsc .gauge .z1{background:#4ade80}#wrsc .gauge .z2{background:#fbbf24}#wrsc .gauge .z3{background:#f87171}\n  #wrsc .gauge .marker{position:absolute;top:-5px;width:4px;height:22px;background:var(--ink);border-radius:2px;transform:translateX(-2px)}\n  #wrsc .gauge-scale{display:flex;justify-content:space-between;font-size:.75rem;color:var(--muted)}\n  #wrsc .stress-box{border:1px solid var(--line);border-radius:10px;padding:.9rem 1rem;margin-bottom:1rem;background:#fff}\n  #wrsc .stress-box h3,#wrsc .res-block h3{margin:0 0 .45rem;font-size:1rem}\n  #wrsc .stress-val{font-size:1.45rem;font-weight:800;color:var(--ink)}\n  #wrsc .stress-alt{color:var(--muted);font-size:.9rem}\n  #wrsc .tag{display:inline-block;font-size:.72rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;background:#eef1f5;color:#374151;border-radius:4px;padding:.1rem .4rem;margin-left:.35rem;vertical-align:middle}\n  #wrsc .res-block{margin-bottom:1rem}\n  #wrsc table.kv{width:100%;border-collapse:collapse;font-size:.9rem}\n  #wrsc table.kv th,#wrsc table.kv td{text-align:left;padding:.38rem .2rem;border-bottom:1px solid #e7eaee;vertical-align:top}\n  #wrsc table.kv th{font-weight:600;color:var(--muted);width:44%}\n  #wrsc .bars{list-style:none;margin:0;padding:0}\n  #wrsc .bars li{display:grid;grid-template-columns:8.5rem 1fr 3.2rem;gap:.5rem;align-items:center;font-size:.86rem;margin:.28rem 0}\n  #wrsc .bars .track{background:#eef1f5;border-radius:4px;height:10px;overflow:hidden}\n  #wrsc .bars .fill{height:100%;background:var(--accent)}\n  #wrsc .bars .pts{text-align:right;font-variant-numeric:tabular-nums}\n  @media (max-width:420px){#wrsc .bars li{grid-template-columns:6.8rem 1fr 2.8rem}}\n  #wrsc ul.tips{margin:.2rem 0 0;padding-left:1.15rem;font-size:.92rem}\n  #wrsc ul.tips li{margin:.3rem 0}\n  #wrsc .alert{border-radius:8px;padding:.7rem .85rem;font-size:.9rem;margin-bottom:1rem}\n  #wrsc .alert.error{background:var(--high-bg);border:1px solid #f2b2b2;color:#7f1d1d}\n  #wrsc .alert.warn{background:var(--mod-bg);border:1px solid #efcf95;color:#6b3b04}\n  #wrsc .alert.info{background:#eef4fb;border:1px solid #c5d7ee;color:#1e3a5f}\n  #wrsc .alert ul{margin:.3rem 0 0;padding-left:1.1rem}\n  #wrsc .fine{font-size:.82rem;color:var(--muted)}\n  #wrsc [hidden]{display:none!important}\n  @media print{#wrsc .actions,#wrsc details.cfg{display:none}}\n<\/style>\n\n  <div class=\"layout\">\n    <!-- ================= INPUT FORM ================= -->\n    <form id=\"wrsc-calc\" class=\"card\" novalidate aria-describedby=\"wrsc-formHelp\">\n      <p id=\"wrsc-formHelp\" class=\"hint\" style=\"margin:0 0 1rem\">All fields are required unless marked optional. Choose units next to each value.<\/p>\n\n      <fieldset class=\"section\">\n        <legend><span class=\"num\" aria-hidden=\"true\">1<\/span>Calculation mode<\/legend>\n        <div class=\"seg\" role=\"radiogroup\" aria-label=\"Calculation mode\">\n          <label><input type=\"radio\" name=\"wrsc-mode\" value=\"relative\" checked><span>Relative Stress Risk<\/span><\/label>\n          <label><input type=\"radio\" name=\"wrsc-mode\" value=\"range\"><span>Approximate Stress Range<\/span><\/label>\n        <\/div>\n        <p class=\"mode-desc\" id=\"wrsc-modeDesc\">Rates residual stress risk as Low, Moderate or High using a configurable Residual Stress Index (0\u2013100).<\/p>\n      <\/fieldset>\n\n      <fieldset class=\"section\">\n        <legend><span class=\"num\" aria-hidden=\"true\">2<\/span>Process &amp; material<\/legend>\n        <div class=\"grid\">\n          <div class=\"field\">\n            <label for=\"wrsc-process\">Welding process<\/label>\n            <select id=\"wrsc-process\" aria-describedby=\"wrsc-process-msg\">\n              <option value=\"gmaw\">MIG\/MAG (GMAW)<\/option>\n              <option value=\"smaw\">Stick (SMAW)<\/option>\n              <option value=\"gtaw\">TIG (GTAW)<\/option>\n              <option value=\"fcaw\">Flux-Cored (FCAW)<\/option>\n              <option value=\"saw\">Submerged Arc (SAW)<\/option>\n              <option value=\"custom\">Custom<\/option>\n            <\/select>\n            <p class=\"msg\" id=\"wrsc-process-msg\"><\/p>\n          <\/div>\n          <div class=\"field\" id=\"wrsc-processNameWrap\" hidden>\n            <label for=\"wrsc-processName\">Custom process name <span class=\"opt\">(optional)<\/span><\/label>\n            <input type=\"text\" id=\"wrsc-processName\" maxlength=\"60\" autocomplete=\"off\">\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-material\">Base material<\/label>\n            <select id=\"wrsc-material\" aria-describedby=\"wrsc-material-hint\">\n              <option value=\"carbon\">Carbon steel<\/option>\n              <option value=\"stainless\">Stainless steel<\/option>\n              <option value=\"aluminum\">Aluminum alloy<\/option>\n              <option value=\"custom\">Custom<\/option>\n            <\/select>\n            <p class=\"hint\" id=\"wrsc-material-hint\"><\/p>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-yield\">Material yield strength<\/label>\n            <div class=\"with-unit\">\n              <input type=\"text\" id=\"wrsc-yield\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-yield-msg wrsc-yield-hint\">\n              <select id=\"wrsc-yieldUnit\" aria-label=\"Yield strength unit\"><option value=\"MPa\">MPa<\/option><option value=\"ksi\">ksi<\/option><\/select>\n            <\/div>\n            <p class=\"hint\" id=\"wrsc-yield-hint\">Use the higher of base and weld metal yield if known.<\/p>\n            <p class=\"msg\" id=\"wrsc-yield-msg\"><\/p>\n          <\/div>\n        <\/div>\n        <div class=\"grid\" id=\"wrsc-materialCustomWrap\" hidden style=\"margin-top:.85rem\">\n          <div class=\"field\">\n            <label for=\"wrsc-materialName\">Custom material name <span class=\"opt\">(optional)<\/span><\/label>\n            <input type=\"text\" id=\"wrsc-materialName\" maxlength=\"60\" autocomplete=\"off\">\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-materialScore\">Material sensitivity factor (0\u20131)<\/label>\n            <input type=\"text\" id=\"wrsc-materialScore\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-materialScore-msg wrsc-materialScore-hint\">\n            <p class=\"hint\" id=\"wrsc-materialScore-hint\">0 = low residual stress tendency, 1 = high. Reference: carbon steel 0.6, stainless 0.8.<\/p>\n            <p class=\"msg\" id=\"wrsc-materialScore-msg\"><\/p>\n          <\/div>\n        <\/div>\n      <\/fieldset>\n\n      <fieldset class=\"section\">\n        <legend><span class=\"num\" aria-hidden=\"true\">3<\/span>Joint geometry<\/legend>\n        <div class=\"grid three\">\n          <div class=\"field\">\n            <label for=\"wrsc-thickness\">Plate thickness<\/label>\n            <div class=\"with-unit\">\n              <input type=\"text\" id=\"wrsc-thickness\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-thickness-msg\">\n              <select id=\"wrsc-thicknessUnit\" aria-label=\"Thickness unit\"><option value=\"mm\">mm<\/option><option value=\"in\">in<\/option><\/select>\n            <\/div>\n            <p class=\"msg\" id=\"wrsc-thickness-msg\"><\/p>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-length\">Weld length<\/label>\n            <div class=\"with-unit\">\n              <input type=\"text\" id=\"wrsc-length\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-length-msg\">\n              <select id=\"wrsc-lengthUnit\" aria-label=\"Weld length unit\"><option value=\"mm\">mm<\/option><option value=\"in\">in<\/option><\/select>\n            <\/div>\n            <p class=\"msg\" id=\"wrsc-length-msg\"><\/p>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-joint\">Joint type<\/label>\n            <select id=\"wrsc-joint\">\n              <option value=\"butt\">Butt<\/option>\n              <option value=\"fillet\">Fillet<\/option>\n              <option value=\"tjoint\">T-joint<\/option>\n              <option value=\"custom\">Custom<\/option>\n            <\/select>\n          <\/div>\n        <\/div>\n        <div class=\"grid\" id=\"wrsc-jointCustomWrap\" hidden style=\"margin-top:.85rem\">\n          <div class=\"field\">\n            <label for=\"wrsc-jointName\">Custom joint name <span class=\"opt\">(optional)<\/span><\/label>\n            <input type=\"text\" id=\"wrsc-jointName\" maxlength=\"60\" autocomplete=\"off\">\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-jointScore\">Joint severity factor (0\u20131)<\/label>\n            <input type=\"text\" id=\"wrsc-jointScore\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-jointScore-msg wrsc-jointScore-hint\">\n            <p class=\"hint\" id=\"wrsc-jointScore-hint\">Reference: fillet 0.4, butt 0.6, T-joint 0.7.<\/p>\n            <p class=\"msg\" id=\"wrsc-jointScore-msg\"><\/p>\n          <\/div>\n        <\/div>\n      <\/fieldset>\n\n      <fieldset class=\"section\">\n        <legend><span class=\"num\" aria-hidden=\"true\">4<\/span>Heat input<\/legend>\n        <div class=\"seg\" role=\"radiogroup\" aria-label=\"Heat input method\">\n          <label><input type=\"radio\" name=\"wrsc-hiMethod\" value=\"calc\" checked><span>Calculate from parameters<\/span><\/label>\n          <label><input type=\"radio\" name=\"wrsc-hiMethod\" value=\"direct\"><span>Enter heat input directly<\/span><\/label>\n        <\/div>\n\n        <div id=\"wrsc-hiCalcWrap\" class=\"subpanel\">\n          <div class=\"grid\">\n            <div class=\"field\">\n              <label for=\"wrsc-current\">Welding current (A)<\/label>\n              <input type=\"text\" id=\"wrsc-current\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-current-msg\">\n              <p class=\"msg\" id=\"wrsc-current-msg\"><\/p>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"wrsc-voltage\">Arc voltage (V)<\/label>\n              <input type=\"text\" id=\"wrsc-voltage\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-voltage-msg\">\n              <p class=\"msg\" id=\"wrsc-voltage-msg\"><\/p>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"wrsc-speed\">Travel speed<\/label>\n              <div class=\"with-unit\">\n                <input type=\"text\" id=\"wrsc-speed\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-speed-msg\">\n                <select id=\"wrsc-speedUnit\" aria-label=\"Travel speed unit\"><option value=\"mm\/min\">mm\/min<\/option><option value=\"in\/min\">in\/min<\/option><\/select>\n              <\/div>\n              <p class=\"msg\" id=\"wrsc-speed-msg\"><\/p>\n            <\/div>\n            <div class=\"field\">\n              <label for=\"wrsc-efficiency\">Thermal efficiency, k (0\u20131)<\/label>\n              <input type=\"text\" id=\"wrsc-efficiency\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-efficiency-msg wrsc-efficiency-hint\">\n              <p class=\"hint\" id=\"wrsc-efficiency-hint\"><\/p>\n              <p class=\"msg\" id=\"wrsc-efficiency-msg\"><\/p>\n            <\/div>\n          <\/div>\n          <p class=\"formula\" aria-label=\"Heat input formula\">HI (kJ\/mm) = V \u00d7 I \u00d7 k \u00d7 60 \u00f7 (1000 \u00d7 travel speed in mm\/min)<\/p>\n        <\/div>\n\n        <div id=\"wrsc-hiDirectWrap\" class=\"subpanel\" hidden>\n          <div class=\"field\" style=\"max-width:340px\">\n            <label for=\"wrsc-heatInput\">Heat input<\/label>\n            <div class=\"with-unit\">\n              <input type=\"text\" id=\"wrsc-heatInput\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-heatInput-msg wrsc-heatInput-hint\">\n              <select id=\"wrsc-hiUnit\" aria-label=\"Heat input unit\"><option value=\"kJ\/mm\">kJ\/mm<\/option><option value=\"kJ\/in\">kJ\/in<\/option><\/select>\n            <\/div>\n            <p class=\"hint\" id=\"wrsc-heatInput-hint\">Enter the heat input from your WPS (with thermal efficiency already applied).<\/p>\n            <p class=\"msg\" id=\"wrsc-heatInput-msg\"><\/p>\n          <\/div>\n        <\/div>\n      <\/fieldset>\n\n      <fieldset class=\"section\">\n        <legend><span class=\"num\" aria-hidden=\"true\">5<\/span>Procedure &amp; restraint<\/legend>\n        <div class=\"grid\">\n          <div class=\"field\">\n            <label for=\"wrsc-sequence\">Weld sequence<\/label>\n            <select id=\"wrsc-sequence\">\n              <option value=\"single\">Single-sided<\/option>\n              <option value=\"alternating\">Alternating sides (balanced)<\/option>\n              <option value=\"multipass\">Multi-pass<\/option>\n            <\/select>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-restraint\">Restraint level<\/label>\n            <select id=\"wrsc-restraint\" aria-describedby=\"wrsc-restraint-hint\">\n              <option value=\"low\">Low \u2013 free to shrink \/ distort<\/option>\n              <option value=\"medium\" selected>Medium \u2013 tacked or partly fixtured<\/option>\n              <option value=\"high\">High \u2013 rigid fixture or closed structure<\/option>\n            <\/select>\n          <\/div>\n        <\/div>\n      <\/fieldset>\n\n      <fieldset class=\"section\">\n        <legend><span class=\"num\" aria-hidden=\"true\">6<\/span>Thermal treatment<\/legend>\n        <div class=\"grid\">\n          <div class=\"field\">\n            <label for=\"wrsc-preheat\">Preheat temperature <span class=\"opt\">(optional)<\/span><\/label>\n            <div class=\"with-unit\">\n              <input type=\"text\" id=\"wrsc-preheat\" inputmode=\"decimal\" autocomplete=\"off\" aria-describedby=\"wrsc-preheat-msg wrsc-preheat-hint\">\n              <select id=\"wrsc-preheatUnit\" aria-label=\"Preheat temperature unit\"><option value=\"C\">\u00b0C<\/option><option value=\"F\">\u00b0F<\/option><\/select>\n            <\/div>\n            <p class=\"hint\" id=\"wrsc-preheat-hint\">Leave blank if no preheat is applied.<\/p>\n            <p class=\"msg\" id=\"wrsc-preheat-msg\"><\/p>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-pwht\">Post-weld heat treatment (PWHT)<\/label>\n            <select id=\"wrsc-pwht\">\n              <option value=\"none\">None (as-welded)<\/option>\n              <option value=\"planned\">Planned \/ not yet decided<\/option>\n              <option value=\"yes\">Stress-relief PWHT performed<\/option>\n            <\/select>\n          <\/div>\n          <div class=\"field\">\n            <label for=\"wrsc-outUnit\">Show stress results in<\/label>\n            <select id=\"wrsc-outUnit\"><option value=\"MPa\">MPa<\/option><option value=\"ksi\">ksi<\/option><\/select>\n          <\/div>\n        <\/div>\n      <\/fieldset>\n\n      <div class=\"actions\">\n        <button type=\"submit\" class=\"btn-primary\">Calculate<\/button>\n      <\/div>\n\n      <details class=\"cfg\" id=\"wrsc-cfgPanel\">\n        <summary>Model assumptions &amp; editable coefficients<\/summary>\n        <div class=\"cfg-body\">\n          <p class=\"hint\" style=\"margin-top:0\">These coefficients drive the Residual Stress Index, the stress-range screening and the practical-range warnings. Change them to match your company practice. Changes apply to the next calculation (and update a displayed result immediately).<\/p>\n          <p id=\"wrsc-cfgStatus\" role=\"status\" aria-live=\"polite\"><\/p>\n          <div id=\"wrsc-cfgHost\"><\/div>\n          <button type=\"button\" class=\"btn-ghost\" id=\"wrsc-btnCfgDefaults\">Restore default coefficients<\/button>\n        <\/div>\n      <\/details>\n    <\/form>\n\n    <!-- ================= RESULTS ================= -->\n    <section class=\"card results\" id=\"wrsc-results\" aria-labelledby=\"wrsc-resultsTitle\" hidden>\n      <h2 id=\"wrsc-resultsTitle\">Results<\/h2>\n      <div id=\"wrsc-resultsBody\" aria-live=\"polite\"><\/div>\n    <\/section>\n  <\/div>\n\n<\/div>\n\n\n<script>\n(function () {\n  'use strict';\n\n  \/* =====================================================================\n     UNIT CONVERSIONS\n     All calculations use internal base units: MPa, mm, mm\/min, kJ\/mm, \u00b0C.\n     ===================================================================== *\/\n  var MPA_PER_KSI = 6.894757;   \/\/ 1 ksi = 6.894757 MPa\n  var MM_PER_IN = 25.4;         \/\/ 1 in  = 25.4 mm (exact)\n  var conv = {\n    stressToBase: function (v, u) { return u === 'ksi' ? v * MPA_PER_KSI : v; },\n    stressFromBase: function (v, u) { return u === 'ksi' ? v \/ MPA_PER_KSI : v; },\n    lenToBase: function (v, u) { return u === 'in' ? v * MM_PER_IN : v; },\n    lenFromBase: function (v, u) { return u === 'in' ? v \/ MM_PER_IN : v; },\n    speedToBase: function (v, u) { return u === 'in\/min' ? v * MM_PER_IN : v; },     \/\/ in\/min -> mm\/min\n    speedFromBase: function (v, u) { return u === 'in\/min' ? v \/ MM_PER_IN : v; },\n    hiToBase: function (v, u) { return u === 'kJ\/in' ? v \/ MM_PER_IN : v; },          \/\/ kJ\/in -> kJ\/mm\n    hiFromBase: function (v, u) { return u === 'kJ\/in' ? v * MM_PER_IN : v; },\n    tempToBase: function (v, u) { return u === 'F' ? (v - 32) * 5 \/ 9 : v; },         \/\/ \u00b0F -> \u00b0C\n    tempFromBase: function (v, u) { return u === 'F' ? v * 9 \/ 5 + 32 : v; }\n  };\n\n  \/* =====================================================================\n     EDITABLE COEFFICIENTS (defaults)\n     These are judgement-based screening values, NOT validated constants.\n     Every value here is exposed in the \"Model assumptions\" panel.\n     ===================================================================== *\/\n  var DEFAULT_CONFIG = {\n    \/\/ Thermal efficiency factor k per process (EN 1011-1 \/ ISO\/TR 17671 typical values)\n    efficiency: { gmaw: 0.8, smaw: 0.8, gtaw: 0.6, fcaw: 0.8, saw: 1.0 },\n\n    \/\/ Relative weights of each factor in the index. Normalised to sum to 1 at run time,\n    \/\/ so they do not have to add up exactly.\n    weights: { restraint: 0.25, heatInput: 0.15, thickness: 0.14, yield: 0.10,\n               joint: 0.10, sequence: 0.10, material: 0.08, length: 0.08 },\n\n    \/\/ Categorical factor scores: 0 = favourable (low residual stress tendency), 1 = adverse.\n    scores: {\n      restraint: { low: 0.2, medium: 0.55, high: 0.9 },\n      joint: { butt: 0.6, fillet: 0.4, tjoint: 0.7 },\n      \/\/ single-sided: asymmetric shrinkage; alternating: balanced; multi-pass: accumulated cycles\n      sequence: { single: 0.8, alternating: 0.35, multipass: 0.6 },\n      \/\/ stainless: higher expansion \/ lower conductivity; aluminum: lower yield but high expansion\n      material: { carbon: 0.6, stainless: 0.8, aluminum: 0.5 }\n    },\n\n    \/\/ Linear normalisation limits for numeric factors: score = (x - min)\/(max - min), limited to 0..1.\n    \/\/ Only the SCORE is limited; user inputs are never clamped.\n    norm: {\n      heatInput: [0.3, 3.0],   \/\/ kJ\/mm\n      thickness: [3, 50],      \/\/ mm\n      yield: [200, 900],       \/\/ MPa\n      length: [50, 1000]       \/\/ mm\n    },\n\n    \/\/ Preheat lowers cooling rate and thermal gradients; modelled as a modest reduction.\n    \/\/ factor = 1 - maxReduction \u00d7 score(preheat between refC and fullEffectC)\n    preheat: { refC: 20, fullEffectC: 250, maxReduction: 0.10 },\n\n    \/\/ Stress-relief PWHT multiplier on the index\n    pwhtFactor: 0.40,\n\n    \/\/ Index (0\u2013100) boundaries between Low \/ Moderate \/ High\n    thresholds: { moderate: 35, high: 60 },\n\n    \/\/ Stress-range screening coefficients, as fractions of yield strength.\n    \/\/ As-welded: upper = awUpper \u00d7 \u03c3y (code-style upper bound, BS 7910 \/ API 579 approach);\n    \/\/            lower interpolated by index from awLowAtMin to awLowAtMax.\n    \/\/ PWHT:      pwhtLower \u2026 pwhtUpper \u00d7 \u03c3y (codes commonly use ~0.2\u20130.3 \u03c3y after PWHT).\n    stress: { awLowAtMin: 0.5, awLowAtMax: 0.9, awUpper: 1.0, pwhtLower: 0.1, pwhtUpper: 0.3 },\n\n    \/\/ Practical ranges (base units) \u2014 values outside trigger a warning only; never clamped.\n    ranges: {\n      yield: [150, 1200],       \/\/ MPa\n      thickness: [1, 150],      \/\/ mm\n      length: [10, 20000],      \/\/ mm\n      current: [20, 1200],      \/\/ A\n      voltage: [8, 50],         \/\/ V\n      speed: [50, 2000],        \/\/ mm\/min\n      heatInput: [0.1, 6],      \/\/ kJ\/mm\n      efficiency: [0.4, 1.0],   \/\/ \u2013\n      preheat: [0, 350]         \/\/ \u00b0C\n    }\n  };\n\n  var config = clone(DEFAULT_CONFIG);\n\n  \/\/ Labels\/limits for the configuration editor\n  var CONFIG_SCHEMA = [\n    { group: 'Index weights', note: 'Relative importance of each factor (normalised to sum to 1).', items: [\n      ['weights.restraint', 'Restraint'], ['weights.heatInput', 'Heat input'], ['weights.thickness', 'Thickness'],\n      ['weights.yield', 'Yield strength'], ['weights.joint', 'Joint type'], ['weights.sequence', 'Weld sequence'],\n      ['weights.material', 'Material'], ['weights.length', 'Weld length']], max: 10 },\n    { group: 'Factor scores (0 = favourable, 1 = adverse)', items: [\n      ['scores.restraint.low', 'Restraint \u2013 low'], ['scores.restraint.medium', 'Restraint \u2013 medium'], ['scores.restraint.high', 'Restraint \u2013 high'],\n      ['scores.joint.butt', 'Joint \u2013 butt'], ['scores.joint.fillet', 'Joint \u2013 fillet'], ['scores.joint.tjoint', 'Joint \u2013 T-joint'],\n      ['scores.sequence.single', 'Seq. \u2013 single-sided'], ['scores.sequence.alternating', 'Seq. \u2013 alternating'], ['scores.sequence.multipass', 'Seq. \u2013 multi-pass'],\n      ['scores.material.carbon', 'Carbon steel'], ['scores.material.stainless', 'Stainless steel'], ['scores.material.aluminum', 'Aluminum']], max: 1 },\n    { group: 'Normalisation limits (score 0 at min, 1 at max)', items: [\n      ['norm.heatInput.0', 'Heat input min (kJ\/mm)'], ['norm.heatInput.1', 'Heat input max (kJ\/mm)'],\n      ['norm.thickness.0', 'Thickness min (mm)'], ['norm.thickness.1', 'Thickness max (mm)'],\n      ['norm.yield.0', 'Yield min (MPa)'], ['norm.yield.1', 'Yield max (MPa)'],\n      ['norm.length.0', 'Weld length min (mm)'], ['norm.length.1', 'Weld length max (mm)']] },\n    { group: 'Modifiers', items: [\n      ['preheat.maxReduction', 'Preheat max reduction (0\u20131)'], ['preheat.refC', 'Preheat no-effect at (\u00b0C)'],\n      ['preheat.fullEffectC', 'Preheat full effect at (\u00b0C)'], ['pwhtFactor', 'PWHT index multiplier (0\u20131)']] },\n    { group: 'Risk thresholds (index 0\u2013100)', items: [\n      ['thresholds.moderate', 'Moderate from'], ['thresholds.high', 'High from']], max: 100 },\n    { group: 'Stress range coefficients (\u00d7 yield strength)', items: [\n      ['stress.awLowAtMin', 'As-welded lower @ index 0'], ['stress.awLowAtMax', 'As-welded lower @ index 100'],\n      ['stress.awUpper', 'As-welded upper bound'], ['stress.pwhtLower', 'After PWHT \u2013 lower'], ['stress.pwhtUpper', 'After PWHT \u2013 upper']], max: 2 },\n    { group: 'Thermal efficiency k', items: [\n      ['efficiency.gmaw', 'GMAW'], ['efficiency.smaw', 'SMAW'], ['efficiency.gtaw', 'GTAW'],\n      ['efficiency.fcaw', 'FCAW'], ['efficiency.saw', 'SAW']], max: 1 },\n    { group: 'Practical-range warnings (base units)', items: [\n      ['ranges.yield.0', 'Yield min (MPa)'], ['ranges.yield.1', 'Yield max (MPa)'],\n      ['ranges.thickness.0', 'Thickness min (mm)'], ['ranges.thickness.1', 'Thickness max (mm)'],\n      ['ranges.length.0', 'Length min (mm)'], ['ranges.length.1', 'Length max (mm)'],\n      ['ranges.current.0', 'Current min (A)'], ['ranges.current.1', 'Current max (A)'],\n      ['ranges.voltage.0', 'Voltage min (V)'], ['ranges.voltage.1', 'Voltage max (V)'],\n      ['ranges.speed.0', 'Speed min (mm\/min)'], ['ranges.speed.1', 'Speed max (mm\/min)'],\n      ['ranges.heatInput.0', 'Heat input min (kJ\/mm)'], ['ranges.heatInput.1', 'Heat input max (kJ\/mm)'],\n      ['ranges.efficiency.0', 'Efficiency min'], ['ranges.efficiency.1', 'Efficiency max'],\n      ['ranges.preheat.0', 'Preheat min (\u00b0C)'], ['ranges.preheat.1', 'Preheat max (\u00b0C)']] }\n  ];\n\n  var PROCESS_NAMES = { gmaw: 'MIG\/MAG (GMAW)', smaw: 'Stick (SMAW)', gtaw: 'TIG (GTAW)', fcaw: 'Flux-Cored (FCAW)', saw: 'Submerged Arc (SAW)', custom: 'Custom' };\n  var MATERIAL_NAMES = { carbon: 'Carbon steel', stainless: 'Stainless steel', aluminum: 'Aluminum alloy', custom: 'Custom material' };\n  var MATERIAL_HINTS = {\n    carbon: 'Typical yield: 235\u2013460 MPa (34\u201367 ksi) for structural grades.',\n    stainless: 'Typical yield: ~205\u2013310 MPa (30\u201345 ksi) for 304\/316 annealed; duplex ~450+ MPa.',\n    aluminum: 'Typical yield: 6061-T6 ~275 MPa; welded HAZ often ~110\u2013140 MPa. Consider HAZ yield.',\n    custom: 'Enter the yield strength and a sensitivity factor for your material.'\n  };\n  var JOINT_NAMES = { butt: 'Butt', fillet: 'Fillet', tjoint: 'T-joint', custom: 'Custom joint' };\n  var SEQ_NAMES = { single: 'Single-sided', alternating: 'Alternating sides', multipass: 'Multi-pass' };\n  var RESTRAINT_NAMES = { low: 'Low', medium: 'Medium', high: 'High' };\n  var PWHT_NAMES = { none: 'None (as-welded)', planned: 'Planned \/ not yet decided', yes: 'Stress-relief PWHT performed' };\n  var FACTOR_NAMES = { restraint: 'Restraint level', heatInput: 'Heat input', thickness: 'Plate thickness', yield: 'Yield strength',\n    joint: 'Joint type', sequence: 'Weld sequence', material: 'Base material', length: 'Weld length' };\n\n  var NUM_RE = \/^[-+]?(\\d+\\.?\\d*|\\.\\d+)([eE][-+]?\\d+)?$\/;\n\n  \/* ------------------------------- helpers ------------------------------- *\/\n  function $(id) { return document.getElementById('wrsc-' + id); }   \/\/ all element ids are prefixed with 'wrsc-' to avoid clashes with the host page\n  function clone(o) { return JSON.parse(JSON.stringify(o)); }\n  function clamp01(x) { return Math.max(0, Math.min(1, x)); }\n  function norm(x, lim) { return clamp01((x - lim[0]) \/ (lim[1] - lim[0])); }   \/\/ score only; input untouched\n  function getPath(o, p) { return p.split('.').reduce(function (a, k) { return a[k]; }, o); }\n  function setPath(o, p, v) { var ks = p.split('.'), last = ks.pop(); ks.reduce(function (a, k) { return a[k]; }, o)[last] = v; }\n  function fmt(v, d) {\n    if (!isFinite(v)) return '\u2013';\n    return Number(v).toLocaleString('en-US', { minimumFractionDigits: d, maximumFractionDigits: d });\n  }\n  function sig(v) { \/\/ compact display for range limits\n    var a = Math.abs(v);\n    return fmt(v, a >= 100 ? 0 : a >= 10 ? 1 : a >= 1 ? 2 : 3);\n  }\n  \/\/ WordPress may rewrite a literal ampersand character inside inline scripts, which breaks JavaScript.\n  \/\/ This script therefore contains no ampersand: it is built at run time (AMP) and all() replaces the logical AND operator.\n  var AMP = String.fromCharCode(38);\n  function esc(s) {\n    var map = {}; map[AMP] = AMP + 'amp;'; map['<'] = AMP + 'lt;'; map['>'] = AMP + 'gt;'; map['\"'] = AMP + 'quot;'; map[\"'\"] = AMP + '#39;';\n    return String(s).replace(\/[\\u0026<>\"']\/g, function (c) { return map[c]; });\n  }\n  function all() { for (var i = 0; i < arguments.length; i++) { if (!arguments[i]) return false; } return true; }\n  function radio(name) { var r = document.querySelector('#wrsc input[name=\"wrsc-' + name + '\"]:checked'); return r ? r.value : null; }\n\n  \/* ---------------------------- field messages --------------------------- *\/\n  var errors = [], warnings = [];\n  function setMsg(id, text, kind) {\n    var m = $(id + '-msg'), el = $(id);\n    if (m) { m.textContent = text || ''; m.className = 'msg' + (kind ? ' ' + kind : ''); }\n    if (el) {\n      if (kind === 'error') el.setAttribute('aria-invalid', 'true'); else el.removeAttribute('aria-invalid');\n      el.classList.toggle('is-warn', kind === 'warn');\n    }\n  }\n  function clearMsgs() {\n    errors = []; warnings = [];\n    ['process', 'yield', 'materialScore', 'thickness', 'length', 'jointScore', 'current', 'voltage', 'speed', 'efficiency', 'heatInput', 'preheat']\n      .forEach(function (id) { setMsg(id, '', null); });\n  }\n  function fail(id, text) { errors.push({ id: id, text: text }); setMsg(id, text, 'error'); }\n\n  \/* Read and validate a numeric field.\n     Rejects empty (unless optional), non-numeric, zero\/negative (when positive=true) and values above max. *\/\n  function readNum(id, label, opts) {\n    opts = opts || {};\n    var raw = $(id).value.trim();\n    if (raw === '') {\n      if (opts.optional) return null;\n      fail(id, label + ' is required.'); return NaN;\n    }\n    if (!NUM_RE.test(raw)) { fail(id, label + ' must be a number (e.g. 12.5).'); return NaN; }\n    var v = parseFloat(raw);\n    if (all(opts.positive !== false, v <= 0)) { fail(id, label + ' must be greater than zero.'); return NaN; }\n    if (all(opts.max !== undefined, v > opts.max)) { fail(id, label + ' cannot be greater than ' + opts.max + '.'); return NaN; }\n    return v;\n  }\n\n  \/* Warn (never clamp) when a base-unit value is outside the configured practical range. *\/\n  function checkRange(id, label, baseVal, key, unit, fromBase) {\n    var r = config.ranges[key];\n    if (!r || !isFinite(baseVal)) return;\n    if (baseVal < r[0] || baseVal > r[1]) {\n      var lo = fromBase ? fromBase(r[0]) : r[0], hi = fromBase ? fromBase(r[1]) : r[1];\n      var t = label + ' is outside the typical range (' + sig(lo) + '\u2013' + sig(hi) + (unit ? ' ' + unit : '') + '). Check the value; it has been used as entered.';\n      warnings.push(t); setMsg(id, t, 'warn');\n    }\n  }\n\n  \/* ------------------------------ UI toggles ----------------------------- *\/\n  function syncProcess(resetEff) {\n    var p = $('process').value;\n    $('processNameWrap').hidden = p !== 'custom';\n    if (p === 'custom') {\n      if (resetEff) $('efficiency').value = '';\n      $('efficiency-hint').textContent = 'Enter the thermal efficiency for your custom process.';\n    } else {\n      if (resetEff) $('efficiency').value = config.efficiency[p];\n      $('efficiency-hint').textContent = 'Default for ' + PROCESS_NAMES[p] + ': ' + config.efficiency[p] + ' (editable).';\n    }\n  }\n  function syncMaterial() {\n    var m = $('material').value;\n    $('materialCustomWrap').hidden = m !== 'custom';\n    $('material-hint').textContent = MATERIAL_HINTS[m];\n  }\n  function syncJoint() { $('jointCustomWrap').hidden = $('joint').value !== 'custom'; }\n  function syncHi() {\n    var direct = radio('hiMethod') === 'direct';\n    $('hiCalcWrap').hidden = direct; $('hiDirectWrap').hidden = !direct;\n  }\n  function syncMode() {\n    $('modeDesc').textContent = radio('mode') === 'range'\n      ? 'Shows the risk rating plus a screening stress range (fraction of yield strength) based on documented, editable code-style assumptions. Not a measured value.'\n      : 'Rates residual stress risk as Low, Moderate or High using a configurable Residual Stress Index (0\u2013100).';\n  }\n\n  \/* ------------------------- configuration editor ------------------------ *\/\n  function buildConfigEditor() {\n    var html = '';\n    CONFIG_SCHEMA.forEach(function (g) {\n      html += '<fieldset class=\"cfg-group\"><legend>' + esc(g.group) + '<\/legend>' + (g.note ? '<p class=\"hint\">' + esc(g.note) + '<\/p>' : '') + '<div class=\"cfg-grid\">';\n      g.items.forEach(function (it) {\n        var id = 'wrsc-cfg-' + it[0].replace(\/\\.\/g, '-');\n        html += '<div class=\"cfg-item\"><label for=\"' + id + '\">' + esc(it[1]) + '<\/label>' +\n          '<input type=\"text\" inputmode=\"decimal\" id=\"' + id + '\" data-path=\"' + it[0] + '\" data-max=\"' + (g.max !== undefined ? g.max : '') + '\" value=\"' + getPath(config, it[0]) + '\" aria-describedby=\"' + id + '-msg\">' +\n          '<p class=\"msg\" id=\"' + id + '-msg\"><\/p><\/div>';\n      });\n      html += '<\/div><\/fieldset>';\n    });\n    $('cfgHost').innerHTML = html;\n    Array.prototype.forEach.call($('cfgHost').querySelectorAll('input[data-path]'), function (inp) {\n      inp.addEventListener('change', onCfgChange);\n    });\n    showCfgStatus();\n  }\n  function onCfgChange(e) {\n    var inp = e.target, raw = inp.value.trim(), msg = document.getElementById(inp.id + '-msg');\n    var max = inp.getAttribute('data-max');\n    var ok = all(NUM_RE.test(raw), parseFloat(raw) >= 0, (max === '' || parseFloat(raw) <= parseFloat(max)));\n    if (!ok) {\n      msg.textContent = 'Enter a number \u2265 0' + (max !== '' ? ' and \u2264 ' + max : '') + '. Previous value kept.';\n      msg.className = 'msg error'; inp.setAttribute('aria-invalid', 'true');\n      return;\n    }\n    msg.textContent = ''; msg.className = 'msg'; inp.removeAttribute('aria-invalid');\n    setPath(config, inp.getAttribute('data-path'), parseFloat(raw));\n    if (inp.getAttribute('data-path').indexOf('efficiency.') === 0) syncProcess(false);\n    showCfgStatus();\n    if (hasResult) calculate(false);\n  }\n  \/* Cross-checks so the model stays coherent after edits. *\/\n  function configProblems() {\n    var p = [], c = config, k;\n    var wsum = 0; for (k in c.weights) wsum += c.weights[k];\n    if (wsum <= 0) p.push('At least one index weight must be greater than zero.');\n    for (k in c.norm) if (c.norm[k][0] >= c.norm[k][1]) p.push('Normalisation min must be below max for ' + FACTOR_NAMES[k].toLowerCase() + '.');\n    for (k in c.ranges) if (c.ranges[k][0] >= c.ranges[k][1]) p.push('Practical range min must be below max for ' + k + '.');\n    if (c.thresholds.moderate >= c.thresholds.high) p.push('The Moderate threshold must be below the High threshold.');\n    if (c.preheat.refC >= c.preheat.fullEffectC) p.push('Preheat \"no-effect\" temperature must be below \"full effect\" temperature.');\n    if (c.preheat.maxReduction > 1 || c.pwhtFactor > 1) p.push('Preheat reduction and PWHT multiplier must be between 0 and 1.');\n    if (c.stress.awLowAtMin > c.stress.awUpper || c.stress.awLowAtMax > c.stress.awUpper) p.push('As-welded lower coefficients must not exceed the as-welded upper bound.');\n    if (c.stress.pwhtLower > c.stress.pwhtUpper) p.push('PWHT lower coefficient must not exceed the PWHT upper coefficient.');\n    return p;\n  }\n  function showCfgStatus() {\n    var p = configProblems(), wsum = 0, k;\n    for (k in config.weights) wsum += config.weights[k];\n    var el = $('cfgStatus');\n    if (p.length) { el.className = 'msg error'; el.textContent = 'Fix before calculating: ' + p.join(' '); }\n    else { el.className = 'hint'; el.textContent = 'Coefficients valid. Sum of raw weights = ' + fmt(wsum, 2) + ' (normalised to 1).'; }\n  }\n\n  \/* =====================================================================\n     RESIDUAL STRESS INDEX\n     index = 100 \u00d7 \u03a3(w\u1d62\/\u03a3w \u00d7 score\u1d62) \u00d7 preheatFactor \u00d7 pwhtFactor\n     Each score\u1d62 is in 0..1. The index is a relative ranking aid only.\n     ===================================================================== *\/\n  function computeIndex(d) {\n    var c = config, wsum = 0, k;\n    for (k in c.weights) wsum += c.weights[k];\n    var s = {\n      restraint: c.scores.restraint[d.restraint],\n      heatInput: norm(d.hi, c.norm.heatInput),\n      thickness: norm(d.t, c.norm.thickness),\n      yield: norm(d.y, c.norm.yield),\n      joint: d.joint === 'custom' ? d.jointScore : c.scores.joint[d.joint],\n      sequence: c.scores.sequence[d.sequence],\n      material: d.material === 'custom' ? d.materialScore : c.scores.material[d.material],\n      length: norm(d.L, c.norm.length)\n    };\n    var contrib = [], base = 0;\n    for (k in c.weights) {\n      var w = c.weights[k] \/ wsum, pts = 100 * w * s[k];\n      contrib.push({ key: k, score: s[k], weight: w, pts: pts });\n      base += pts;\n    }\n    contrib.sort(function (a, b) { return b.pts - a.pts; });\n    \/\/ Preheat: no effect at\/below refC, full maxReduction at\/above fullEffectC\n    var pre = (d.preheat === null) ? 1 : 1 - c.preheat.maxReduction * norm(d.preheat, [c.preheat.refC, c.preheat.fullEffectC]);\n    var pw = d.pwht === 'yes' ? c.pwhtFactor : 1;\n    var index = base * pre * pw;\n    var level = index >= c.thresholds.high ? 'high' : index >= c.thresholds.moderate ? 'moderate' : 'low';\n    return { base: base, index: index, level: level, contrib: contrib, preFactor: pre, pwhtFactor: pw, scores: s };\n  }\n\n  \/* =====================================================================\n     SCREENING STRESS RANGE (approximate mode)\n     As-welded: \u03c3_upper = awUpper \u00d7 \u03c3y (code upper-bound assumption);\n                \u03c3_lower = [awLowAtMin + (awLowAtMax \u2212 awLowAtMin) \u00d7 index\/100] \u00d7 \u03c3y (tool assumption).\n     After PWHT: pwhtLower \u2026 pwhtUpper \u00d7 \u03c3y.\n     Returns ok:false with a reason where no defensible figure can be given.\n     ===================================================================== *\/\n  function stressRange(d, r) {\n    var s = config.stress;\n    if (all(d.material === 'aluminum', d.pwht === 'yes')) {\n      return { ok: false, reason: 'For aluminum alloys, post-weld heat treatment changes temper and strength rather than acting as a conventional stress relief, and the code fractions used here are for steels. A numerical range is therefore not shown. Use the relative risk result and seek a specialist assessment.' };\n    }\n    if (d.pwht === 'yes') {\n      return { ok: true, basis: 'pwht', lo: s.pwhtLower * d.y, hi: s.pwhtUpper * d.y, fLo: s.pwhtLower, fHi: s.pwhtUpper };\n    }\n    var t = clamp01(r.index \/ 100);\n    var fLo = s.awLowAtMin + (s.awLowAtMax - s.awLowAtMin) * t;\n    return { ok: true, basis: 'aw', lo: fLo * d.y, hi: s.awUpper * d.y, fLo: fLo, fHi: s.awUpper };\n  }\n\n  \/* ------------------------------ main calc ------------------------------ *\/\n  var hasResult = false;\n\n  function calculate(focusOnError) {\n    clearMsgs();\n    var body = $('resultsBody');\n    var cp = configProblems();\n    if (cp.length) {\n      body.innerHTML = '<div class=\"alert error\" role=\"alert\"><strong>The model coefficients need attention.<\/strong><ul>' +\n        cp.map(function (x) { return '<li>' + esc(x) + '<\/li>'; }).join('') + '<\/ul><\/div>';\n      $('results').hidden = false;\n      $('cfgPanel').open = true; hasResult = false; return;\n    }\n\n    var d = {\n      mode: radio('mode'),\n      process: $('process').value, processName: $('processName').value.trim(),\n      material: $('material').value, materialName: $('materialName').value.trim(),\n      joint: $('joint').value, jointName: $('jointName').value.trim(),\n      sequence: $('sequence').value, restraint: $('restraint').value, pwht: $('pwht').value,\n      hiMethod: radio('hiMethod'), outUnit: $('outUnit').value\n    };\n    var yu = $('yieldUnit').value, tu = $('thicknessUnit').value, lu = $('lengthUnit').value,\n        su = $('speedUnit').value, hu = $('hiUnit').value, pu = $('preheatUnit').value;\n\n    var yRaw = readNum('yield', 'Yield strength');\n    var tRaw = readNum('thickness', 'Plate thickness');\n    var lRaw = readNum('length', 'Weld length');\n    if (d.material === 'custom') d.materialScore = readNum('materialScore', 'Material sensitivity factor', { positive: false, max: 1 });\n    if (all(d.material === 'custom', d.materialScore < 0)) fail('materialScore', 'Material sensitivity factor must be between 0 and 1.');\n    if (d.joint === 'custom') d.jointScore = readNum('jointScore', 'Joint severity factor', { positive: false, max: 1 });\n    if (all(d.joint === 'custom', d.jointScore < 0)) fail('jointScore', 'Joint severity factor must be between 0 and 1.');\n\n    \/\/ --- Heat input ---\n    var I, V, S, k, hiRaw;\n    if (d.hiMethod === 'calc') {\n      I = readNum('current', 'Welding current');\n      V = readNum('voltage', 'Arc voltage');\n      var sRaw = readNum('speed', 'Travel speed');\n      k = readNum('efficiency', 'Thermal efficiency', { max: 1 });\n      S = conv.speedToBase(sRaw, su);                \/\/ mm\/min\n      \/\/ HI [kJ\/mm] = V[V] \u00d7 I[A] \u00d7 k \u00d7 60 [s\/min] \u00f7 (1000 [J\/kJ] \u00d7 S[mm\/min])\n      d.hi = (V * I * k * 60) \/ (1000 * S);\n      d.I = I; d.V = V; d.S = S; d.k = k; d.sDisp = sRaw; d.su = su;\n    } else {\n      hiRaw = readNum('heatInput', 'Heat input');\n      d.hi = conv.hiToBase(hiRaw, hu);              \/\/ kJ\/mm\n    }\n\n    var pRaw = readNum('preheat', 'Preheat temperature', { optional: true, positive: false });\n    d.preheat = (pRaw === null || isNaN(pRaw)) ? null : conv.tempToBase(pRaw, pu);\n\n    if (errors.length) {\n      \/\/ Inputs are invalid: hide the results section and send the user to the first problem field\n      $('results').hidden = true; body.innerHTML = '';\n      if (focusOnError !== false) $(errors[0].id).focus();\n      hasResult = false; return;\n    }\n\n    d.y = conv.stressToBase(yRaw, yu);   \/\/ MPa\n    d.t = conv.lenToBase(tRaw, tu);      \/\/ mm\n    d.L = conv.lenToBase(lRaw, lu);      \/\/ mm\n\n    \/\/ --- Practical-range warnings (inputs are NOT modified) ---\n    checkRange('yield', 'Yield strength', d.y, 'yield', yu, function (v) { return conv.stressFromBase(v, yu); });\n    checkRange('thickness', 'Plate thickness', d.t, 'thickness', tu, function (v) { return conv.lenFromBase(v, tu); });\n    checkRange('length', 'Weld length', d.L, 'length', lu, function (v) { return conv.lenFromBase(v, lu); });\n    if (d.hiMethod === 'calc') {\n      checkRange('current', 'Welding current', I, 'current', 'A');\n      checkRange('voltage', 'Arc voltage', V, 'voltage', 'V');\n      checkRange('speed', 'Travel speed', S, 'speed', su, function (v) { return conv.speedFromBase(v, su); });\n      checkRange('efficiency', 'Thermal efficiency', k, 'efficiency', '');\n      var r = config.ranges.heatInput;\n      if (d.hi < r[0] || d.hi > r[1]) warnings.push('Calculated heat input (' + fmt(d.hi, 2) + ' kJ\/mm) is outside the typical range (' + sig(r[0]) + '\u2013' + sig(r[1]) + ' kJ\/mm). Check current, voltage and travel speed.');\n    } else {\n      checkRange('heatInput', 'Heat input', d.hi, 'heatInput', hu, function (v) { return conv.hiFromBase(v, hu); });\n    }\n    if (d.preheat !== null) checkRange('preheat', 'Preheat temperature', d.preheat, 'preheat', pu === 'F' ? '\u00b0F' : '\u00b0C', function (v) { return conv.tempFromBase(v, pu); });\n    if (all(d.material === 'aluminum', d.y > 350)) warnings.push('Yield strength above 350 MPa is unusual for welded aluminum; the heat-affected zone is typically much weaker than the parent temper.');\n    if (all(d.joint === 'fillet', d.sequence === 'alternating', d.t < 3)) warnings.push('Very thin section with alternating-side welding: check that the joint is practical.');\n\n    var res = computeIndex(d);\n    render(d, res, { yu: yu, tu: tu, lu: lu, hu: hu, pu: pu, yRaw: yRaw, tRaw: tRaw, lRaw: lRaw, pRaw: pRaw });\n    $('results').hidden = false;\n    if (focusOnError !== false) $('results').scrollIntoView({ behavior: 'smooth', block: 'start' });\n    hasResult = true;\n  }\n\n  \/* ------------------------------ rendering ------------------------------ *\/\n  function render(d, r, u) {\n    var LEVEL = { low: 'Low', moderate: 'Moderate', high: 'High' };\n    var ou = d.outUnit, h = '';\n\n    if (warnings.length) {\n      h += '<div class=\"alert warn\" role=\"status\"><strong>Check these values<\/strong> (used as entered):<ul>' +\n        warnings.map(function (w) { return '<li>' + esc(w) + '<\/li>'; }).join('') + '<\/ul><\/div>';\n    }\n\n    \/\/ Risk card\n    var pct = Math.max(0, Math.min(100, r.index));\n    var t1 = config.thresholds.moderate, t2 = config.thresholds.high;\n    h += '<div class=\"risk ' + r.level + '\"><div class=\"label\">Residual stress risk<\/div>' +\n      '<div class=\"level\">' + LEVEL[r.level] + '<\/div>' +\n      '<div class=\"idx\">Residual Stress Index: <strong>' + fmt(r.index, 0) + '<\/strong> \/ 100 <span class=\"tag\">relative score<\/span><\/div>' +\n      '<div class=\"gauge\" role=\"img\" aria-label=\"Index ' + fmt(r.index, 0) + ' of 100. Low below ' + t1 + ', moderate ' + t1 + ' to ' + t2 + ', high from ' + t2 + '.\">' +\n      '<div class=\"zones\"><div class=\"z1\" style=\"width:' + t1 + '%\"><\/div><div class=\"z2\" style=\"width:' + (t2 - t1) + '%\"><\/div><div class=\"z3\" style=\"width:' + (100 - t2) + '%\"><\/div><\/div>' +\n      '<div class=\"marker\" style=\"left:' + pct + '%\"><\/div><\/div>' +\n      '<div class=\"gauge-scale\" aria-hidden=\"true\"><span>0<\/span><span>Low ' + AMP + 'lt; ' + t1 + '<\/span><span>High \u2265 ' + t2 + '<\/span><span>100<\/span><\/div>' +\n      '<\/div>';\n\n    \/\/ Stress range\n    if (d.mode === 'range') {\n      var sr = stressRange(d, r);\n      if (sr.ok) {\n        var other = ou === 'MPa' ? 'ksi' : 'MPa';\n        h += '<div class=\"stress-box\"><h3>Approximate peak residual stress near the weld <span class=\"tag\">screening estimate<\/span><\/h3>' +\n          '<div class=\"stress-val\">' + fmt(conv.stressFromBase(sr.lo, ou), 0) + ' \u2013 ' + fmt(conv.stressFromBase(sr.hi, ou), 0) + ' ' + ou + '<\/div>' +\n          '<div class=\"stress-alt\">\u2248 ' + fmt(conv.stressFromBase(sr.lo, other), ou === 'MPa' ? 1 : 0) + ' \u2013 ' + fmt(conv.stressFromBase(sr.hi, other), ou === 'MPa' ? 1 : 0) + ' ' + other +\n          ' ' + AMP + 'nbsp;|' + AMP + 'nbsp; ' + fmt(sr.fLo, 2) + ' \u2013 ' + fmt(sr.fHi, 2) + ' \u00d7 yield strength<\/div>' +\n          '<p class=\"fine\" style=\"margin:.55rem 0 0\">' + (sr.basis === 'pwht'\n            ? 'Basis: after stress-relief PWHT, a residual stress of about ' + fmt(config.stress.pwhtLower, 2) + '\u2013' + fmt(config.stress.pwhtUpper, 2) + ' \u00d7 yield is assumed, in line with fractions commonly used in fitness-for-service codes (e.g. BS 7910, API 579-1). Actual relief depends on PWHT temperature, hold time and material.'\n            : 'Basis: in the as-welded condition, fitness-for-service codes (e.g. BS 7910, API 579-1) conservatively take peak residual stress up to the yield strength (upper bound ' + fmt(config.stress.awUpper, 2) + ' \u00d7 \u03c3y here). The lower bound is interpolated from the index between ' + fmt(config.stress.awLowAtMin, 2) + ' and ' + fmt(config.stress.awLowAtMax, 2) + ' \u00d7 \u03c3y; this interpolation is a tool assumption, not a code rule.') +\n          ' This is not a measured or validated value and does not describe the stress distribution or direction. All coefficients are editable under \u201cModel assumptions\u201d.<\/p><\/div>';\n      } else {\n        h += '<div class=\"alert info\"><strong>No numerical stress range shown.<\/strong> ' + esc(sr.reason) + '<\/div>';\n      }\n    }\n\n    \/\/ Key values\n    var procName = d.process === 'custom' ? (d.processName ? esc(d.processName) + ' (custom)' : 'Custom process') : PROCESS_NAMES[d.process];\n    var matName = d.material === 'custom' ? (d.materialName ? esc(d.materialName) + ' (custom)' : 'Custom material') : MATERIAL_NAMES[d.material];\n    var jName = d.joint === 'custom' ? (d.jointName ? esc(d.jointName) + ' (custom)' : 'Custom joint') : JOINT_NAMES[d.joint];\n    var rows = [\n      ['Welding process', procName],\n      ['Base material', matName + (d.material === 'custom' ? ' \u2014 factor ' + fmt(d.materialScore, 2) : '')],\n      ['Yield strength', fmt(d.y, 0) + ' MPa (' + fmt(conv.stressFromBase(d.y, 'ksi'), 1) + ' ksi)'],\n      ['Plate thickness', fmt(d.t, 1) + ' mm (' + fmt(d.t \/ MM_PER_IN, 3) + ' in)'],\n      ['Weld length', fmt(d.L, 0) + ' mm (' + fmt(d.L \/ MM_PER_IN, 1) + ' in)'],\n      ['Joint type', jName + (d.joint === 'custom' ? ' \u2014 factor ' + fmt(d.jointScore, 2) : '')],\n      ['Heat input', fmt(d.hi, 2) + ' kJ\/mm (' + fmt(d.hi * MM_PER_IN, 1) + ' kJ\/in) \u2014 ' + (d.hiMethod === 'calc'\n        ? 'calculated from ' + fmt(d.V, 1) + ' V \u00d7 ' + fmt(d.I, 0) + ' A, k = ' + fmt(d.k, 2) + ', ' + fmt(d.S, 0) + ' mm\/min'\n        : 'entered directly')],\n      ['Weld sequence', SEQ_NAMES[d.sequence]],\n      ['Restraint level', RESTRAINT_NAMES[d.restraint]],\n      ['Preheat', d.preheat === null ? 'None entered' : fmt(d.preheat, 0) + ' \u00b0C (' + fmt(conv.tempFromBase(d.preheat, 'F'), 0) + ' \u00b0F) \u2014 index \u00d7 ' + fmt(r.preFactor, 2)],\n      ['PWHT', PWHT_NAMES[d.pwht] + (d.pwht === 'yes' ? ' \u2014 index \u00d7 ' + fmt(r.pwhtFactor, 2) : '')],\n      ['Index before modifiers', fmt(r.base, 1) + ' \/ 100']\n    ];\n    h += '<div class=\"res-block\"><h3>Values used<\/h3><table class=\"kv\"><tbody>' +\n      rows.map(function (x) { return '<tr><th scope=\"row\">' + x[0] + '<\/th><td>' + x[1] + '<\/td><\/tr>'; }).join('') + '<\/tbody><\/table><\/div>';\n\n    \/\/ Influencing factors\n    var top = r.contrib.slice(0, 3).map(function (c) { return FACTOR_NAMES[c.key].toLowerCase(); });\n    var maxPts = r.contrib[0].pts || 1;\n    h += '<div class=\"res-block\"><h3>What influenced the result most<\/h3>' +\n      '<p style=\"margin:0 0 .4rem;font-size:.92rem\">The largest contributions came from <strong>' + top[0] + '<\/strong>, <strong>' + top[1] + '<\/strong> and <strong>' + top[2] + '<\/strong>' +\n      (d.pwht === 'yes' ? ', before the PWHT reduction was applied' : '') + '.<\/p>' +\n      '<ul class=\"bars\" aria-label=\"Index points contributed by each factor, before modifiers\">' +\n      r.contrib.map(function (c) {\n        return '<li><span>' + FACTOR_NAMES[c.key] + '<\/span><span class=\"track\" aria-hidden=\"true\"><span class=\"fill\" style=\"display:block;width:' + (100 * c.pts \/ maxPts).toFixed(1) + '%\"><\/span><\/span>' +\n          '<span class=\"pts\">' + fmt(c.pts, 1) + '<span class=\"sr-only\" style=\"position:absolute;left:-9999px\"> points, score ' + fmt(c.score, 2) + '<\/span><\/span><\/li>';\n      }).join('') + '<\/ul><p class=\"fine\" style=\"margin:.35rem 0 0\">Points = normalised weight \u00d7 factor score \u00d7 100, before preheat\/PWHT modifiers.<\/p><\/div>';\n\n    \/\/ Mitigation guidance\n    h += '<div class=\"res-block\"><h3>General mitigation guidance<\/h3><ul class=\"tips\">' + mitigation(d, r).map(function (t) { return '<li>' + t + '<\/li>'; }).join('') + '<\/ul><\/div>';\n\n    if (d.material === 'aluminum') {\n      h += '<div class=\"alert info\">Aluminum: the heat-affected zone of heat-treatable and work-hardened alloys is softened, so the local yield strength (and the residual stress it can sustain) may be much lower than the parent temper. Distortion is often the bigger practical issue.<\/div>';\n    }\n    if (d.pwht === 'planned') {\n      h += '<div class=\"alert info\">PWHT is marked as planned, so the as-welded condition is assessed. Re-run with \u201cStress-relief PWHT performed\u201d once the treatment is confirmed.<\/div>';\n    }\n\n    h += '<p class=\"fine\">Residual stress depends on material properties, restraint, weld geometry, thermal cycles, sequence and fabrication conditions. This tool is for preliminary planning only and does not replace finite element analysis, residual stress measurement, a qualified welding procedure, applicable codes, or professional engineering review.<\/p>';\n\n    $('resultsBody').innerHTML = h;\n  }\n\n  function mitigation(d, r) {\n    var s = r.scores, t = [];\n    if (s.restraint >= 0.7) t.push('<strong>High restraint:<\/strong> review fixturing and assembly order so joints can shrink in a controlled way; avoid closing out a fully locked-up structure last where possible.');\n    if (s.heatInput >= 0.45) t.push('<strong>Reduce unnecessary heat input:<\/strong> check whether bead size and heat input exceed what the WPS needs; stringer beads can shrink the heated zone. Stay within the qualified range and avoid lack-of-fusion.');\n    if (d.sequence === 'single') t.push('<strong>Balance the sequence:<\/strong> where access allows, weld from both sides alternately, or use back-step \/ skip welding to spread shrinkage.');\n    if (d.sequence === 'multipass') t.push('<strong>Multi-pass control:<\/strong> control interpass temperature and balance passes about the neutral axis; avoid oversized weld deposits.');\n    if (s.length >= 0.6) t.push('<strong>Long welds:<\/strong> use segmented or back-step welding and adequate tack spacing to limit cumulative longitudinal shrinkage.');\n    if (s.thickness >= 0.5) t.push('<strong>Thick section:<\/strong> apply suitable preheat and interpass temperature control per the WPS and code; check whether PWHT is mandatory for this thickness.');\n    if (s.yield >= 0.55) t.push('<strong>Higher-strength material:<\/strong> residual stress can reach higher absolute values; follow hydrogen-control and preheat requirements to reduce cracking risk.');\n    if (d.material === 'stainless') t.push('<strong>Stainless steel:<\/strong> higher thermal expansion and lower conductivity increase distortion and stress; limit interpass temperature and consider chill bars or balanced sequencing.');\n    if (d.material === 'aluminum') t.push('<strong>Aluminum:<\/strong> use fast, low-heat techniques where qualified and rigid but well-planned fixturing; stress-relief PWHT is not a conventional option.');\n    t.push('<strong>Improve fit-up:<\/strong> consistent root gaps and accurate edge preparation avoid excess weld metal and uneven shrinkage.');\n    if (all(d.preheat === null, d.material !== 'aluminum', (s.thickness >= 0.4 || s.yield >= 0.5))) t.push('<strong>Consider preheat:<\/strong> a suitable preheat lowers thermal gradients and cooling rate; set it from the material, thickness and hydrogen level per the applicable code.');\n    if (all(d.pwht !== 'yes', r.level !== 'low', d.material !== 'aluminum')) t.push('<strong>Evaluate PWHT with an engineer:<\/strong> decide whether stress-relief heat treatment is required or beneficial for the service conditions and code.');\n    t.push('<strong>Confirm with qualified procedures:<\/strong> use a qualified WPS and have critical joints reviewed by a welding engineer; use FEA or measurement when the residual stress level matters to design or fitness-for-service.');\n    return t;\n  }\n\n  \/* ------------------------------- actions ------------------------------- *\/\n  \/* -------------------------------- init --------------------------------- *\/\n  $('process').addEventListener('change', function () { syncProcess(true); });\n  $('material').addEventListener('change', syncMaterial);\n  $('joint').addEventListener('change', syncJoint);\n  Array.prototype.forEach.call(document.querySelectorAll('#wrsc input[name=\"wrsc-hiMethod\"]'), function (el) { el.addEventListener('change', syncHi); });\n  Array.prototype.forEach.call(document.querySelectorAll('#wrsc input[name=\"wrsc-mode\"]'), function (el) {\n    el.addEventListener('change', function () { syncMode(); if (hasResult) calculate(false); });\n  });\n  $('outUnit').addEventListener('change', function () { if (hasResult) calculate(false); });\n  $('calc').addEventListener('submit', function (e) { e.preventDefault(); calculate(true); });\n  $('btnCfgDefaults').addEventListener('click', function () {\n    config = clone(DEFAULT_CONFIG); buildConfigEditor(); syncProcess(false);\n    if (hasResult) calculate(false);\n  });\n\n  buildConfigEditor();\n  syncProcess(true); syncMaterial(); syncJoint(); syncHi(); syncMode();\n\n  \/\/ Expose for testing \/ integration\n  window.WRSC_CALC = { computeIndex: computeIndex, stressRange: stressRange, conv: conv, getConfig: function () { return config; } };\n})();\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>The Welding Residual Stress Calculator helps estimate relative residual stress risk based on welding heat input, material properties, plate thickness, joint type, weld sequence, and&#8230;<\/p>","protected":false},"author":2,"featured_media":25115,"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_hide_featured_image":false,"_jetpack_featured_image_duplicate_dismissed":0,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[8808],"tags":[8851,8860,8793,8856],"class_list":["post-25114","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-manufacturing-and-processes","tag-heat-input","tag-residual-stress","tag-welding-calculator","tag-welding-process","pmpro-has-access"],"acf":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/www.mechstream.com\/wp-content\/uploads\/2026\/11\/Welding-Residual-Stress-Calculator.png","_links":{"self":[{"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/posts\/25114","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/comments?post=25114"}],"version-history":[{"count":2,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/posts\/25114\/revisions"}],"predecessor-version":[{"id":25117,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/posts\/25114\/revisions\/25117"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/media\/25115"}],"wp:attachment":[{"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/media?parent=25114"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/categories?post=25114"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/tags?post=25114"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}