{"id":24874,"date":"2026-09-09T05:53:13","date_gmt":"2026-09-09T05:53:13","guid":{"rendered":"https:\/\/www.mechstream.com\/?p=24874"},"modified":"2026-09-09T06:36:06","modified_gmt":"2026-09-09T06:36:06","slug":"thermal-stress-calculator","status":"publish","type":"post","link":"https:\/\/www.mechstream.com\/ar\/thermal-stress-calculator\/","title":{"rendered":"Thermal Stress Calculator"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The <strong>Thermal Stress Calculator<\/strong> is a practical engineering tool designed to estimate stress caused by restricted thermal expansion or contraction. By entering Young\u2019s modulus, thermal expansion coefficient, temperature change, and restraint conditions, users can calculate thermal strain, thermal stress, thermal force, safety factor, and allowable temperature change. The Thermal Stress Calculator is useful for mechanical components, piping systems, machine frames, shafts, bearings, structural members, and other engineering applications affected by temperature variation.<\/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.0\">\n  <meta name=\"description\" content=\"Thermal Stress Calculator for restrained thermal expansion, thermal strain, thermal force, safety factor, allowable temperature change, and material comparison.\">\n  <title>Thermal Stress Calculator<\/title>\n  <style>\n    \/* Scoped for WordPress embed: styles only apply inside #tsc-root *\/\n    #tsc-root {\n      --bg: #ffffff;\n      --panel: #ffffff;\n      --panel-soft: #eef4ff;\n      --ink: #172326;\n      --muted: #5e6f73;\n      --line: #cfdcde;\n      --brand: #2563eb;\n      --brand-dark: #1d4ed8;\n      --steel: #53616a;\n      --amber: #b7791f;\n      --danger: #b42318;\n      --ok: #15803d;\n      --blue: #2563eb;\n      --shadow: 0 14px 34px rgba(21, 38, 43, 0.11);\n      --radius: 8px;\n      display: block;\n      max-width: 100%;\n      margin: 0;\n      background: #ffffff;\n      color: var(--ink);\n      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n      line-height: 1.5;\n      text-align: left;\n      -webkit-text-size-adjust: 100%;\n    }\n\n    #tsc-root *,\n    #tsc-root *::before,\n    #tsc-root *::after {\n      box-sizing: border-box;\n    }\n\n    #tsc-root button,\n    #tsc-root input,\n    #tsc-root select,\n    #tsc-root textarea {\n      font: inherit;\n    }\n\n    #tsc-root .page {\n      width: min(1440px, calc(100% - 32px));\n      margin: 0 auto;\n      padding: 28px 0 44px;\n    }\n\n    #tsc-root .topbar {\n      display: flex;\n      align-items: center;\n      justify-content: space-between;\n      gap: 18px;\n      margin-bottom: 18px;\n    }\n\n    #tsc-root .brand {\n      display: flex;\n      align-items: center;\n      gap: 12px;\n    }\n\n    #tsc-root .brand-mark {\n      width: 42px;\n      height: 42px;\n      display: grid;\n      place-items: center;\n      border: 1px solid rgba(37, 99, 235, 0.22);\n      border-radius: var(--radius);\n      background: #e5edff;\n      color: var(--brand-dark);\n      box-shadow: 0 8px 22px rgba(37, 99, 235, 0.15);\n    }\n\n    #tsc-root h1,\n    #tsc-root h2,\n    #tsc-root h3,\n    #tsc-root p {\n      margin-top: 0;\n    }\n\n    #tsc-root h1 {\n      margin-bottom: 2px;\n      font-size: 2.15rem;\n      line-height: 1.08;\n      letter-spacing: 0;\n    }\n\n    #tsc-root .subtitle {\n      margin: 0;\n      color: var(--muted);\n      max-width: 880px;\n    }\n\n    #tsc-root .app-shell {\n      display: grid;\n      grid-template-columns: minmax(0, 1fr);\n      gap: 18px;\n      align-items: start;\n    }\n\n    #tsc-root .panel {\n      background: var(--panel);\n      border: 1px solid var(--line);\n      border-radius: var(--radius);\n      box-shadow: var(--shadow);\n    }\n\n    #tsc-root .controls {\n      overflow: hidden;\n    }\n\n    #tsc-root .panel-header {\n      padding: 16px 18px;\n      border-bottom: 1px solid var(--line);\n      background: #f8fbfb;\n    }\n\n    #tsc-root .panel-header h2,\n    #tsc-root .section h2 {\n      margin: 0;\n      font-size: 1rem;\n      letter-spacing: 0;\n    }\n\n    #tsc-root .panel-body {\n      padding: 18px;\n    }\n\n    #tsc-root .section {\n      padding: 18px;\n      margin-bottom: 18px;\n    }\n\n    #tsc-root .field-grid {\n      display: grid;\n      grid-template-columns: repeat(2, minmax(0, 1fr));\n      gap: 12px;\n    }\n\n    #tsc-root .field,\n    #tsc-root .wide {\n      min-width: 0;\n    }\n\n    #tsc-root .wide {\n      grid-column: 1 \/ -1;\n    }\n\n    #tsc-root label {\n      display: flex;\n      align-items: center;\n      gap: 6px;\n      margin-bottom: 6px;\n      color: #334347;\n      font-size: 0.86rem;\n      font-weight: 700;\n    }\n\n    #tsc-root input,\n    #tsc-root select,\n    #tsc-root textarea {\n      width: 100%;\n      min-height: 42px;\n      border: 1px solid #b9c9cc;\n      border-radius: 6px;\n      padding: 9px 10px;\n      background: #fff;\n      color: var(--ink);\n      outline: none;\n    }\n\n    #tsc-root input:focus,\n    #tsc-root select:focus,\n    #tsc-root textarea:focus {\n      border-color: var(--brand);\n      box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.16);\n    }\n\n    #tsc-root textarea {\n      min-height: 96px;\n      resize: vertical;\n      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n      font-size: 0.86rem;\n    }\n\n    #tsc-root .segmented {\n      display: grid;\n      grid-template-columns: repeat(2, 1fr);\n      padding: 4px;\n      border: 1px solid var(--line);\n      border-radius: 999px;\n      background: #eef4ff;\n      gap: 4px;\n    }\n\n    #tsc-root .segmented button,\n    #tsc-root .mode-tabs button {\n      border: 0;\n      border-radius: 999px;\n      min-height: 34px;\n      background: transparent;\n      color: var(--muted);\n      cursor: pointer;\n      font-weight: 800;\n      font-size: 0.86rem;\n    }\n\n    #tsc-root .segmented button.active,\n    #tsc-root .mode-tabs button.active {\n      background: var(--brand);\n      color: #fff;\n    }\n\n    #tsc-root .mode-tabs {\n      display: grid;\n      grid-template-columns: repeat(2, minmax(0, 1fr));\n      gap: 6px;\n      margin-top: 12px;\n    }\n\n    #tsc-root .mode-tabs button {\n      border: 1px solid var(--line);\n      border-radius: 6px;\n      background: #fff;\n      min-height: 42px;\n      padding: 6px 8px;\n    }\n\n    #tsc-root .button-row {\n      display: flex;\n      justify-content: center;\n      gap: 8px;\n      margin-top: 14px;\n    }\n\n    #tsc-root .btn {\n      width: 140px;\n      min-height: 44px;\n      border: 0;\n      border-radius: 6px;\n      padding: 0 13px;\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      gap: 8px;\n      cursor: pointer;\n      font-weight: 900;\n      color: #fff;\n      background: var(--brand);\n    }\n\n    #tsc-root .btn:hover {\n      background: var(--brand-dark);\n    }\n\n    #tsc-root .btn.secondary {\n      color: var(--ink);\n      background: #e7eeee;\n      border: 1px solid var(--line);\n    }\n\n    #tsc-root .btn.secondary:hover {\n      background: #dce8e8;\n    }\n\n    #tsc-root .icon {\n      width: 18px;\n      height: 18px;\n      flex: 0 0 auto;\n      stroke: currentColor;\n      stroke-width: 2;\n      fill: none;\n      stroke-linecap: round;\n      stroke-linejoin: round;\n    }\n\n    #tsc-root .info {\n      width: 16px;\n      height: 16px;\n      display: inline-grid;\n      place-items: center;\n      border-radius: 999px;\n      border: 1px solid #9fb2b5;\n      color: var(--muted);\n      font-size: 0.72rem;\n      cursor: help;\n    }\n\n    #tsc-root .results-grid {\n      display: grid;\n      grid-template-columns: repeat(4, minmax(0, 1fr));\n      gap: 12px;\n    }\n\n    #tsc-root .metric {\n      border: 1px solid var(--line);\n      border-radius: var(--radius);\n      padding: 14px;\n      background: #fbfdfd;\n      min-height: 112px;\n    }\n\n    #tsc-root .metric .label {\n      display: flex;\n      align-items: center;\n      gap: 7px;\n      color: var(--muted);\n      font-weight: 800;\n      font-size: 0.78rem;\n      text-transform: uppercase;\n    }\n\n    #tsc-root .metric .value {\n      margin-top: 8px;\n      font-size: 1.42rem;\n      font-weight: 950;\n      line-height: 1.1;\n      overflow-wrap: anywhere;\n    }\n\n    #tsc-root .metric .sub {\n      margin-top: 5px;\n      color: var(--muted);\n      font-size: 0.84rem;\n    }\n\n    #tsc-root .status-band {\n      display: grid;\n      grid-template-columns: auto 1fr;\n      gap: 14px;\n      align-items: center;\n      border-radius: var(--radius);\n      padding: 14px 16px;\n      margin-bottom: 12px;\n      border: 1px solid var(--line);\n      background: #f8fbfb;\n    }\n\n    #tsc-root .status-dot {\n      width: 44px;\n      height: 44px;\n      border-radius: 50%;\n      display: grid;\n      place-items: center;\n      color: #fff;\n      background: var(--ok);\n      font-weight: 950;\n    }\n\n    #tsc-root .status-band.caution .status-dot {\n      background: var(--amber);\n    }\n\n    #tsc-root .status-band.danger .status-dot {\n      background: var(--danger);\n    }\n\n    #tsc-root .status-band h2 {\n      margin-bottom: 2px;\n      font-size: 1.05rem;\n    }\n\n    #tsc-root .status-band p {\n      margin: 0;\n      color: var(--muted);\n    }\n\n    #tsc-root .visual-grid {\n      display: grid;\n      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);\n      gap: 14px;\n    }\n\n    #tsc-root .viz,\n    #tsc-root .chart-box {\n      border: 1px solid var(--line);\n      border-radius: var(--radius);\n      background: #fbfdfd;\n      padding: 12px;\n      min-width: 0;\n    }\n\n    #tsc-root .viz h3,\n    #tsc-root .chart-box h3 {\n      margin: 0 0 8px;\n      font-size: 0.95rem;\n    }\n\n    #tsc-root svg,\n    #tsc-root canvas {\n      display: block;\n      width: 100%;\n    }\n\n    #tsc-root .diagram {\n      height: 260px;\n    }\n\n    #tsc-root .chart {\n      height: 260px;\n    }\n\n    #tsc-root .bars {\n      display: grid;\n      gap: 8px;\n      margin-top: 10px;\n    }\n\n    #tsc-root .comparison-row {\n      display: grid;\n      grid-template-columns: minmax(110px, 1.1fr) minmax(0, 2.3fr) auto;\n      gap: 10px;\n      align-items: center;\n      color: #334347;\n      font-size: 0.88rem;\n    }\n\n    #tsc-root .bar-track {\n      height: 12px;\n      border-radius: 999px;\n      background: #dce8e8;\n      overflow: hidden;\n    }\n\n    #tsc-root .bar-fill {\n      height: 100%;\n      border-radius: inherit;\n      background: linear-gradient(90deg, #1d4ed8, #60a5fa);\n    }\n\n    #tsc-root details {\n      border: 1px solid var(--line);\n      border-radius: var(--radius);\n      padding: 12px 14px;\n      background: #fbfdfd;\n      margin-top: 10px;\n    }\n\n    #tsc-root summary {\n      cursor: pointer;\n      font-weight: 900;\n    }\n\n    #tsc-root .formula {\n      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n      background: #eef4ff;\n      border-radius: 6px;\n      padding: 9px 10px;\n      overflow-x: auto;\n      white-space: nowrap;\n    }\n\n    #tsc-root .note-list {\n      columns: 2;\n      padding-left: 18px;\n      color: var(--muted);\n    }\n\n    #tsc-root .notice {\n      border-left: 4px solid var(--brand);\n      background: #eff6ff;\n      padding: 12px 14px;\n      border-radius: 0 6px 6px 0;\n      color: #26383b;\n    }\n\n    #tsc-root .warning-list {\n      display: grid;\n      gap: 8px;\n      margin: 12px 0 0;\n    }\n\n    #tsc-root .warning {\n      border: 1px solid #f3c7c2;\n      background: #fff5f4;\n      color: #7a271a;\n      border-radius: 6px;\n      padding: 9px 10px;\n      font-size: 0.9rem;\n    }\n\n    #tsc-root .hidden {\n      display: none !important;\n    }\n\n    #tsc-root .copy-state {\n      min-height: 20px;\n      margin-top: 8px;\n      color: var(--muted);\n      font-size: 0.85rem;\n    }\n\n    #tsc-root .seo {\n      margin-top: 18px;\n      color: #344649;\n    }\n\n    #tsc-root .seo h2 {\n      font-size: 1.12rem;\n    }\n\n    @media (max-width: 1100px) {\n      #tsc-root .results-grid {\n        grid-template-columns: repeat(2, minmax(0, 1fr));\n      }\n    }\n\n    @media (max-width: 680px) {\n      #tsc-root .page {\n        width: min(100% - 20px, 1440px);\n        padding-top: 16px;\n      }\n\n      #tsc-root .topbar,\n      #tsc-root .brand {\n        align-items: flex-start;\n      }\n\n      #tsc-root .topbar {\n        flex-direction: column;\n      }\n\n      #tsc-root .field-grid,\n      #tsc-root .results-grid,\n      #tsc-root .mode-tabs {\n        grid-template-columns: 1fr;\n      }\n\n      #tsc-root .note-list {\n        columns: 1;\n      }\n\n      #tsc-root .comparison-row {\n        grid-template-columns: 1fr;\n      }\n\n      #tsc-root .diagram,\n      #tsc-root .chart {\n        height: 230px;\n      }\n\n      #tsc-root h1 {\n        font-size: 1.78rem;\n      }\n\n      #tsc-root .metric .value {\n        font-size: 1.24rem;\n      }\n    }\n\n    \/* Isolated embed layout for WordPress content areas. *\/\n    #tsc-root {\n      width: 1000px !important;\n      max-width: 1000px !important;\n      margin: 0 auto !important;\n      padding: 8px 0 !important;\n      overflow: hidden;\n      font-family: Arial, Helvetica, sans-serif !important;\n      font-size: 14px !important;\n      line-height: 1.35 !important;\n    }\n\n    #tsc-root .page {\n      width: 100% !important;\n      max-width: 1000px !important;\n      margin: 0 auto !important;\n      padding: 0 !important;\n    }\n\n    #tsc-root .app-shell {\n      display: block !important;\n      width: 100% !important;\n    }\n\n    #tsc-root .panel {\n      width: 100% !important;\n      border: 1px solid #cbd8e0 !important;\n      border-radius: 4px !important;\n      box-shadow: 0 7px 18px rgba(31, 55, 69, 0.1) !important;\n    }\n\n    #tsc-root .panel-header {\n      padding: 10px 16px 12px !important;\n      background: #f7fbff !important;\n      border-bottom: 1px solid #d4e0e6 !important;\n    }\n\n    #tsc-root .panel-header h2 {\n      margin: 0 0 12px !important;\n      color: #18364b !important;\n      font-size: 14px !important;\n      line-height: 1 !important;\n    }\n\n    #tsc-root .panel-body {\n      padding: 14px 16px !important;\n    }\n\n    #tsc-root .field-grid {\n      grid-template-columns: repeat(2, minmax(0, 1fr)) !important;\n      column-gap: 16px !important;\n      row-gap: 10px !important;\n    }\n\n    #tsc-root [data-field],\n    #tsc-root .wide {\n      display: block !important;\n      position: static !important;\n      float: none !important;\n      width: auto !important;\n      max-width: none !important;\n      min-width: 0 !important;\n      margin: 0 !important;\n      padding: 0 !important;\n      border: 0 !important;\n      background: transparent !important;\n      transform: none !important;\n    }\n\n    #tsc-root .wide {\n      grid-column: 1 \/ -1 !important;\n    }\n\n    #tsc-root label {\n      min-height: 18px !important;\n      margin: 0 0 6px !important;\n      gap: 5px !important;\n      color: #18364b !important;\n      font-size: 13px !important;\n      line-height: 1.2 !important;\n      font-weight: 700 !important;\n    }\n\n    #tsc-root input,\n    #tsc-root select,\n    #tsc-root textarea {\n      min-height: 42px !important;\n      height: 42px !important;\n      border: 1px solid #b7c9d4 !important;\n      border-radius: 4px !important;\n      padding: 8px 12px !important;\n      background: #ffffff !important;\n      color: #18364b !important;\n      font-size: 16px !important;\n      line-height: 1.35 !important;\n      box-shadow: none !important;\n      text-shadow: none !important;\n    }\n\n    #tsc-root textarea {\n      height: 72px !important;\n      min-height: 72px !important;\n      padding-top: 10px !important;\n      font-family: Consolas, \"Courier New\", monospace !important;\n      font-size: 13px !important;\n    }\n\n    #tsc-root select,\n    #tsc-root select[id],\n    #tsc-root .field-grid select {\n      display: block !important;\n      position: static !important;\n      float: none !important;\n      clear: none !important;\n      width: 100% !important;\n      min-width: 0 !important;\n      max-width: none !important;\n      height: 42px !important;\n      min-height: 42px !important;\n      margin: 0 !important;\n      padding: 8px 12px !important;\n      border: 1px solid #b7c9d4 !important;\n      border-radius: 4px !important;\n      background-color: #ffffff !important;\n      background-image: none !important;\n      color: #18364b !important;\n      font-size: 16px !important;\n      line-height: 1.35 !important;\n      text-indent: 0 !important;\n      text-transform: none !important;\n      opacity: 1 !important;\n      transform: none !important;\n      box-shadow: none !important;\n      -webkit-appearance: menulist !important;\n      appearance: auto !important;\n    }\n\n    #tsc-root select::before,\n    #tsc-root select::after,\n    #tsc-root [data-field]::before,\n    #tsc-root [data-field]::after {\n      display: none !important;\n      content: none !important;\n    }\n\n    #tsc-root input:focus,\n    #tsc-root select:focus,\n    #tsc-root textarea:focus {\n      border-color: #2563eb !important;\n      box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.14) !important;\n    }\n\n    #tsc-root .mode-tabs {\n      grid-template-columns: repeat(2, minmax(0, 1fr)) !important;\n      gap: 6px !important;\n      margin: 0 !important;\n    }\n\n    #tsc-root .mode-tabs button {\n      min-width: 0 !important;\n      min-height: 28px !important;\n      height: 28px !important;\n      padding: 4px 8px !important;\n      border: 1px solid #b7c9d4 !important;\n      border-radius: 3px !important;\n      background: #ffffff !important;\n      color: #18364b !important;\n      font-size: 11px !important;\n      line-height: 1 !important;\n      font-weight: 700 !important;\n      white-space: nowrap;\n    }\n\n    #tsc-root .mode-tabs button.active {\n      border-color: #2563eb !important;\n      background: #2563eb !important;\n      color: #ffffff !important;\n    }\n\n    #tsc-root .segmented {\n      min-height: 42px !important;\n      height: 42px !important;\n      padding: 4px !important;\n      gap: 4px !important;\n      border: 1px solid #b7c9d4 !important;\n      background: #edf4ff !important;\n    }\n\n    #tsc-root .segmented button {\n      min-height: 32px !important;\n      height: 32px !important;\n      padding: 4px 8px !important;\n      color: #18364b !important;\n      font-size: 13px !important;\n      line-height: 1 !important;\n    }\n\n    #tsc-root .segmented button.active {\n      background: #2563eb !important;\n      color: #ffffff !important;\n    }\n\n    #tsc-root .info {\n      width: 14px !important;\n      height: 14px !important;\n      font-size: 10px !important;\n      line-height: 1 !important;\n    }\n\n    #tsc-root .button-row {\n      display: flex !important;\n      justify-content: center !important;\n      margin-top: 14px !important;\n    }\n\n    #tsc-root .btn {\n      width: 96px !important;\n      min-width: 96px !important;\n      min-height: 32px !important;\n      height: 32px !important;\n      padding: 5px 14px !important;\n      border-radius: 4px !important;\n      font-size: 13px !important;\n      line-height: 1 !important;\n      font-weight: 700 !important;\n    }\n\n    #tsc-root .results-panel,\n    #tsc-root .section {\n      padding: 14px 16px !important;\n      margin-bottom: 12px !important;\n    }\n\n    #tsc-root .results-grid {\n      grid-template-columns: repeat(4, minmax(0, 1fr)) !important;\n      gap: 6px !important;\n    }\n\n    #tsc-root .metric {\n      min-height: 78px !important;\n      padding: 8px !important;\n    }\n\n    #tsc-root .metric .label {\n      font-size: 7px !important;\n    }\n\n    #tsc-root .metric .value {\n      margin-top: 5px !important;\n      font-size: 14px !important;\n    }\n\n    #tsc-root .metric .sub,\n    #tsc-root .status-band p,\n    #tsc-root .warning {\n      font-size: 8px !important;\n    }\n\n    #tsc-root .status-band {\n      gap: 8px !important;\n      padding: 8px !important;\n      margin-bottom: 7px !important;\n    }\n\n    #tsc-root .status-dot {\n      width: 30px !important;\n      height: 30px !important;\n      font-size: 8px !important;\n    }\n\n    #tsc-root .status-band h2 {\n      font-size: 11px !important;\n    }\n\n    #tsc-root .warning-list {\n      gap: 5px !important;\n      margin-top: 7px !important;\n    }\n\n    @media (max-width: 680px) {\n      #tsc-root {\n        width: calc(100% - 20px) !important;\n        max-width: calc(100% - 20px) !important;\n        padding: 5px 0 !important;\n      }\n\n      #tsc-root .field-grid,\n      #tsc-root .results-grid {\n        grid-template-columns: repeat(2, minmax(0, 1fr)) !important;\n      }\n\n      #tsc-root .wide {\n        grid-column: 1 \/ -1 !important;\n      }\n    }\n\n    #tsc-root .hidden,\n    #tsc-root [data-field].hidden,\n    #tsc-root #resultsPanel.hidden {\n      display: none !important;\n    }\n  <\/style>\n<\/head>\n<body>\n  <div id=\"tsc-root\" class=\"tsc-root\">\n  <main class=\"page\">\n    <div class=\"app-shell\">\n      <aside class=\"panel controls\" aria-label=\"Thermal stress inputs\">\n        <div class=\"panel-header\">\n          <h2>Inputs<\/h2>\n          <div class=\"mode-tabs\" id=\"modeTabs\" aria-label=\"Calculation mode\"><\/div>\n        <\/div>\n        <div class=\"panel-body\">\n          <div class=\"field-grid\">\n            <div class=\"wide\">\n              <label for=\"unitSystem\">Unit system<\/label>\n              <div class=\"segmented\" id=\"unitTabs\">\n                <button type=\"button\" class=\"active\" data-unit=\"metric\">Metric<\/button>\n                <button type=\"button\" data-unit=\"imperial\">Imperial<\/button>\n              <\/div>\n            <\/div>\n\n            <div class=\"wide\">\n              <label for=\"materialSearch\">Search material presets<\/label>\n              <input id=\"materialSearch\" type=\"search\" placeholder=\"Filter by material or grade\">\n            <\/div>\n\n            <div class=\"wide\">\n              <label for=\"material\">Material preset<\/label>\n              <select id=\"material\"><\/select>\n            <\/div>\n\n            <div data-field=\"material-base\">\n              <label for=\"youngsModulus\">Young&#8217;s modulus <span class=\"info\" title=\"Elastic modulus E. Stress is proportional to E while the material remains linear elastic.\">?<\/span><\/label>\n              <input id=\"youngsModulus\" type=\"number\" min=\"0\" step=\"0.1\">\n            <\/div>\n            <div data-field=\"material-base\">\n              <label for=\"modulusUnit\">E unit<\/label>\n              <select id=\"modulusUnit\">\n                <option value=\"GPa\">GPa<\/option>\n                <option value=\"MPa\">MPa<\/option>\n                <option value=\"Pa\">Pa<\/option>\n                <option value=\"ksi\">ksi<\/option>\n                <option value=\"psi\">psi<\/option>\n              <\/select>\n            <\/div>\n\n            <div data-field=\"material-base\">\n              <label for=\"alpha\">Thermal expansion coefficient <span class=\"info\" title=\"Linear coefficient of thermal expansion alpha. Presets are approximate average values.\">?<\/span><\/label>\n              <input id=\"alpha\" type=\"number\" step=\"0.01\">\n            <\/div>\n            <div data-field=\"material-base\">\n              <label for=\"alphaUnit\">Alpha unit<\/label>\n              <select id=\"alphaUnit\">\n                <option value=\"umC\">um\/m-C<\/option>\n                <option value=\"perC\">1\/C<\/option>\n                <option value=\"perK\">1\/K<\/option>\n                <option value=\"umF\">um\/m-F<\/option>\n                <option value=\"perF\">1\/F<\/option>\n              <\/select>\n            <\/div>\n\n            <div data-field=\"length\">\n              <label for=\"length\">Original length<\/label>\n              <input id=\"length\" type=\"number\" min=\"0\" step=\"0.001\">\n            <\/div>\n            <div data-field=\"length\">\n              <label for=\"lengthUnit\">Length unit<\/label>\n              <select id=\"lengthUnit\">\n                <option value=\"m\">m<\/option>\n                <option value=\"mm\">mm<\/option>\n                <option value=\"cm\">cm<\/option>\n                <option value=\"in\">in<\/option>\n                <option value=\"ft\">ft<\/option>\n              <\/select>\n            <\/div>\n\n            <div data-field=\"temperature\">\n              <label for=\"initialTemp\">Initial temperature<\/label>\n              <input id=\"initialTemp\" type=\"number\" step=\"0.1\">\n            <\/div>\n            <div data-field=\"temperature\">\n              <label for=\"finalTemp\">Final temperature<\/label>\n              <input id=\"finalTemp\" type=\"number\" step=\"0.1\">\n            <\/div>\n\n            <div data-field=\"temperature\">\n              <label for=\"tempUnit\">Temperature unit<\/label>\n              <select id=\"tempUnit\">\n                <option value=\"C\">C<\/option>\n                <option value=\"F\">F<\/option>\n                <option value=\"K\">K<\/option>\n              <\/select>\n            <\/div>\n            <div data-field=\"restraint\">\n              <label for=\"restraint\">Restraint factor <span class=\"info\" title=\"R = 0 is free expansion. R = 1 is fully restrained. Values between 0 and 1 model partial restraint.\">?<\/span><\/label>\n              <input id=\"restraint\" type=\"number\" min=\"0\" max=\"1\" step=\"0.01\">\n            <\/div>\n\n            <div data-field=\"area\">\n              <label for=\"area\">Cross-sectional area<\/label>\n              <input id=\"area\" type=\"number\" min=\"0\" step=\"0.001\">\n            <\/div>\n            <div data-field=\"area\">\n              <label for=\"areaUnit\">Area unit<\/label>\n              <select id=\"areaUnit\">\n                <option value=\"mm2\">mm2<\/option>\n                <option value=\"m2\">m2<\/option>\n                <option value=\"in2\">in2<\/option>\n                <option value=\"ft2\">ft2<\/option>\n              <\/select>\n            <\/div>\n\n            <div data-field=\"strength\">\n              <label for=\"yieldStrength\">Yield strength<\/label>\n              <input id=\"yieldStrength\" type=\"number\" min=\"0\" step=\"0.1\">\n            <\/div>\n            <div data-field=\"strength\">\n              <label for=\"allowableStress\">Allowable stress<\/label>\n              <input id=\"allowableStress\" type=\"number\" min=\"0\" step=\"0.1\">\n            <\/div>\n\n            <div data-field=\"stress\">\n              <label for=\"stressUnit\">Stress input unit<\/label>\n              <select id=\"stressUnit\">\n                <option value=\"MPa\">MPa<\/option>\n                <option value=\"GPa\">GPa<\/option>\n                <option value=\"Pa\">Pa<\/option>\n                <option value=\"ksi\">ksi<\/option>\n                <option value=\"psi\">psi<\/option>\n              <\/select>\n            <\/div>\n            <div data-field=\"known-stress\">\n              <label for=\"knownStress\">Known thermal stress<\/label>\n              <input id=\"knownStress\" type=\"number\" step=\"0.1\">\n            <\/div>\n\n            <div data-field=\"displacement\">\n              <label for=\"allowedDisp\">Allowed displacement<\/label>\n              <input id=\"allowedDisp\" type=\"number\" min=\"0\" step=\"0.001\">\n            <\/div>\n            <div data-field=\"displacement\">\n              <label for=\"dispUnit\">Displacement unit<\/label>\n              <select id=\"dispUnit\">\n                <option value=\"mm\">mm<\/option>\n                <option value=\"m\">m<\/option>\n                <option value=\"um\">um<\/option>\n                <option value=\"in\">in<\/option>\n              <\/select>\n            <\/div>\n\n            <div data-field=\"spring\">\n              <label for=\"springStiffness\">Support stiffness<\/label>\n              <input id=\"springStiffness\" type=\"number\" min=\"0\" step=\"1\">\n            <\/div>\n            <div data-field=\"spring\">\n              <label for=\"springUnit\">Stiffness unit<\/label>\n              <select id=\"springUnit\">\n                <option value=\"N\/m\">N\/m<\/option>\n                <option value=\"N\/mm\">N\/mm<\/option>\n                <option value=\"lbf\/in\">lbf\/in<\/option>\n              <\/select>\n            <\/div>\n\n            <div class=\"wide\" data-field=\"advanced\">\n              <label for=\"advancedData\">Advanced E(T), alpha(T), Sy(T) data<\/label>\n              <textarea id=\"advancedData\" placeholder=\"Optional rows: temperature, E_GPa, alpha_um_per_C, Sy_MPa\"><\/textarea>\n            <\/div>\n\n            <div class=\"wide\" data-field=\"comparison\">\n              <label>Comparison materials<\/label>\n              <div class=\"field-grid\" id=\"compareChecks\"><\/div>\n            <\/div>\n          <\/div>\n\n          <div class=\"button-row\">\n            <button class=\"btn\" id=\"calculateBtn\" type=\"button\" title=\"Calculate\">\n              Calculate\n            <\/button>\n          <\/div>\n        <\/div>\n      <\/aside>\n\n      <section aria-label=\"Thermal stress results\">\n        <div class=\"panel section hidden\" id=\"resultsPanel\">\n          <div class=\"status-band\" id=\"statusBand\">\n            <div class=\"status-dot\" id=\"statusDot\">OK<\/div>\n            <div>\n              <h2 id=\"statusTitle\">Safe<\/h2>\n              <p id=\"statusText\">Carbon steel example: heating from 20 C to 80 C under full restraint produces compressive thermal stress.<\/p>\n            <\/div>\n          <\/div>\n          <div class=\"results-grid\" id=\"resultsGrid\"><\/div>\n          <div class=\"warning-list\" id=\"warnings\"><\/div>\n        <\/div>\n\n      <\/section>\n    <\/div>\n  <\/main>\n  <\/div>\n\n  <script>\n    const materials = [\n      { name: \"Carbon Steel\", grade: \"Typical structural\", E: 200, alpha: 12, yield: 250, allow: 150 },\n      { name: \"Stainless Steel\", grade: \"304\/316 approximate\", E: 193, alpha: 17, yield: 215, allow: 130 },\n      { name: \"Aluminum Alloy\", grade: \"6061-T6 approximate\", E: 69, alpha: 23, yield: 276, allow: 165 },\n      { name: \"Copper\", grade: \"Annealed approximate\", E: 110, alpha: 16.5, yield: 70, allow: 45 },\n      { name: \"Brass\", grade: \"Cartridge brass approximate\", E: 100, alpha: 19, yield: 200, allow: 120 },\n      { name: \"Titanium Alloy\", grade: \"Ti-6Al-4V approximate\", E: 110, alpha: 8.6, yield: 830, allow: 500 },\n      { name: \"Cast Iron\", grade: \"Gray cast iron approximate\", E: 120, alpha: 11, yield: 170, allow: 85 },\n      { name: \"Invar\", grade: \"Fe-Ni low expansion\", E: 140, alpha: 1.2, yield: 240, allow: 140 }\n    ];\n\n    const modes = [\n      [\"fully\", \"Fully restrained\"],\n      [\"partial\", \"Partial restraint\"],\n      [\"free\", \"Free expansion\"],\n      [\"strain\", \"Thermal strain\"],\n      [\"allowTemp\", \"Allowable Delta T\"],\n      [\"stressTemp\", \"Delta T from stress\"],\n      [\"safety\", \"Safety factor\"],\n      [\"compare\", \"Material comparison\"],\n      [\"displacement\", \"Allowed displacement\"],\n      [\"spring\", \"Spring restraint\"],\n      [\"differential\", \"Two materials\"]\n    ];\n\n    const state = {\n      unitSystem: \"metric\",\n      mode: \"fully\",\n      result: null,\n      hasCalculated: false\n    };\n\n    const $ = (id) => document.getElementById(id);\n    const inputIds = [\n      \"material\", \"youngsModulus\", \"modulusUnit\", \"alpha\", \"alphaUnit\", \"length\", \"lengthUnit\",\n      \"initialTemp\", \"finalTemp\", \"tempUnit\", \"restraint\", \"area\", \"areaUnit\", \"yieldStrength\",\n      \"allowableStress\", \"stressUnit\", \"knownStress\", \"allowedDisp\", \"dispUnit\",\n      \"springStiffness\", \"springUnit\", \"advancedData\"\n    ];\n\n    const factors = {\n      stress: { Pa: 1, MPa: 1e6, GPa: 1e9, psi: 6894.757293, ksi: 6894757.293 },\n      length: { m: 1, mm: 0.001, cm: 0.01, in: 0.0254, ft: 0.3048, um: 1e-6 },\n      area: { m2: 1, mm2: 1e-6, in2: 0.00064516, ft2: 0.09290304 },\n      stiffness: { \"N\/m\": 1, \"N\/mm\": 1000, \"lbf\/in\": 175.126835 }\n    };\n\n    function toPa(value, unit) {\n      return value * factors.stress[unit];\n    }\n\n    function fromPa(value, unit) {\n      return value \/ factors.stress[unit];\n    }\n\n    function toMeters(value, unit) {\n      return value * factors.length[unit];\n    }\n\n    function fromMeters(value, unit) {\n      return value \/ factors.length[unit];\n    }\n\n    function toArea(value, unit) {\n      return value * factors.area[unit];\n    }\n\n    function alphaPerC(value, unit) {\n      if (unit === \"umC\") return value * 1e-6;\n      if (unit === \"perC\" || unit === \"perK\") return value;\n      if (unit === \"umF\") return value * 1e-6 * 1.8;\n      if (unit === \"perF\") return value * 1.8;\n      return value;\n    }\n\n    function deltaTC(initial, final, unit) {\n      const delta = final - initial;\n      if (unit === \"F\") return delta \/ 1.8;\n      return delta;\n    }\n\n    function format(value, digits = 3) {\n      if (!Number.isFinite(value)) return \"N\/A\";\n      if (Math.abs(value) >= 1000 || (Math.abs(value) > 0 && Math.abs(value) < 0.01)) {\n        return value.toExponential(3);\n      }\n      return value.toLocaleString(undefined, { maximumFractionDigits: digits });\n    }\n\n    function stressDisplay(pa) {\n      const unit = state.unitSystem === \"metric\" ? \"MPa\" : \"ksi\";\n      return `${format(fromPa(pa, unit), 3)} ${unit}`;\n    }\n\n    function lengthDisplay(m) {\n      if (state.unitSystem === \"metric\") {\n        return `${format(fromMeters(m, \"mm\"), 4)} mm`;\n      }\n      return `${format(fromMeters(m, \"in\"), 5)} in`;\n    }\n\n    function forceDisplay(n) {\n      if (!Number.isFinite(n)) return \"N\/A\";\n      if (state.unitSystem === \"metric\") return `${format(n \/ 1000, 3)} kN`;\n      return `${format(n \/ 4448.221615, 3)} kip`;\n    }\n\n    function getNumber(id) {\n      const value = parseFloat($(id).value);\n      return Number.isFinite(value) ? value : NaN;\n    }\n\n    function activeMaterial() {\n      return materials.find((m) => m.name === $(\"material\").value) || materials[0];\n    }\n\n    function populateMaterials() {\n      const term = $(\"materialSearch\").value.trim().toLowerCase();\n      const current = $(\"material\").value;\n      const options = materials.filter((m) => `${m.name} ${m.grade}`.toLowerCase().includes(term));\n      $(\"material\").innerHTML = options.map((m) => `<option value=\"${m.name}\">${m.name} - ${m.grade}<\/option>`).join(\"\");\n      if (options.some((m) => m.name === current)) $(\"material\").value = current;\n      if (!$(\"material\").value && options[0]) $(\"material\").value = options[0].name;\n    }\n\n    function populateComparison() {\n      $(\"compareChecks\").innerHTML = materials.map((m, index) => `\n        <label style=\"font-weight:600\">\n          <input type=\"checkbox\" class=\"compare-check\" value=\"${m.name}\" ${index < 4 ? \"checked\" : \"\"} style=\"width:auto;min-height:auto\">\n          ${m.name}\n        <\/label>\n      `).join(\"\");\n    }\n\n    function populateModes() {\n      $(\"modeTabs\").innerHTML = modes.map(([id, label]) => `<button type=\"button\" data-mode=\"${id}\" class=\"${id === state.mode ? \"active\" : \"\"}\">${label}<\/button>`).join(\"\");\n    }\n\n    function applyPreset(material = activeMaterial()) {\n      $(\"youngsModulus\").value = material.E;\n      $(\"modulusUnit\").value = \"GPa\";\n      $(\"alpha\").value = material.alpha;\n      $(\"alphaUnit\").value = \"umC\";\n      $(\"yieldStrength\").value = material.yield;\n      $(\"allowableStress\").value = material.allow;\n      $(\"stressUnit\").value = \"MPa\";\n    }\n\n    function defaults() {\n      state.unitSystem = \"metric\";\n      state.mode = \"fully\";\n      state.hasCalculated = false;\n      $(\"materialSearch\").value = \"\";\n      populateMaterials();\n      $(\"material\").value = \"Carbon Steel\";\n      applyPreset(materials[0]);\n      $(\"length\").value = 1;\n      $(\"lengthUnit\").value = \"m\";\n      $(\"initialTemp\").value = 20;\n      $(\"finalTemp\").value = 80;\n      $(\"tempUnit\").value = \"C\";\n      $(\"restraint\").value = 1;\n      $(\"area\").value = 1000;\n      $(\"areaUnit\").value = \"mm2\";\n      $(\"knownStress\").value = 144;\n      $(\"allowedDisp\").value = 0.2;\n      $(\"dispUnit\").value = \"mm\";\n      $(\"springStiffness\").value = 100000;\n      $(\"springUnit\").value = \"N\/mm\";\n      $(\"advancedData\").value = \"\";\n      document.querySelectorAll(\"#unitTabs button\").forEach((btn) => btn.classList.toggle(\"active\", btn.dataset.unit === state.unitSystem));\n      populateModes();\n      updateVisibleFields();\n      $(\"warnings\").innerHTML = \"\";\n      $(\"resultsGrid\").innerHTML = \"\";\n      $(\"resultsPanel\").classList.add(\"hidden\");\n    }\n\n    function collectInputs() {\n      return {\n        E: toPa(getNumber(\"youngsModulus\"), $(\"modulusUnit\").value),\n        alpha: alphaPerC(getNumber(\"alpha\"), $(\"alphaUnit\").value),\n        length: toMeters(getNumber(\"length\"), $(\"lengthUnit\").value),\n        dT: deltaTC(getNumber(\"initialTemp\"), getNumber(\"finalTemp\"), $(\"tempUnit\").value),\n        restraint: getNumber(\"restraint\"),\n        area: toArea(getNumber(\"area\"), $(\"areaUnit\").value),\n        yieldPa: toPa(getNumber(\"yieldStrength\"), $(\"stressUnit\").value),\n        allowPa: toPa(getNumber(\"allowableStress\"), $(\"stressUnit\").value),\n        knownStressPa: toPa(getNumber(\"knownStress\"), $(\"stressUnit\").value),\n        allowedDisp: toMeters(getNumber(\"allowedDisp\"), $(\"dispUnit\").value),\n        ks: getNumber(\"springStiffness\") * factors.stiffness[$(\"springUnit\").value]\n      };\n    }\n\n    function validate(values) {\n      const warnings = [];\n      if (!(values.E > 0)) warnings.push(\"Young's modulus must be greater than zero.\");\n      if (!Number.isFinite(values.alpha)) warnings.push(\"Thermal expansion coefficient must be valid.\");\n      if (!(values.restraint >= 0 && values.restraint <= 1)) warnings.push(\"Restraint factor must be between 0 and 1.\");\n      if (!(values.length > 0) && [\"free\", \"displacement\", \"spring\", \"differential\", \"compare\"].includes(state.mode)) warnings.push(\"Original length must be greater than zero for this mode.\");\n      if (!(values.area > 0) && [\"spring\"].includes(state.mode)) warnings.push(\"Cross-sectional area must be greater than zero for spring restraint modeling.\");\n      if (Math.abs(values.dT) > 250) warnings.push(\"Temperature range is large; constant-property assumptions may be weak.\");\n      return warnings;\n    }\n\n    function parseAdvanced(values) {\n      const rows = $(\"advancedData\").value.trim().split(\/\\n+\/).map((line) => line.split(\/[,\\s]+\/).map(Number)).filter((row) => row.length >= 3 && row.every(Number.isFinite));\n      if (rows.length < 2) return values;\n      const avgE = rows.reduce((sum, row) => sum + row[1], 0) \/ rows.length;\n      const avgAlpha = rows.reduce((sum, row) => sum + row[2], 0) \/ rows.length;\n      const avgYield = rows.filter((row) => Number.isFinite(row[3])).reduce((sum, row) => sum + row[3], 0) \/ Math.max(1, rows.filter((row) => Number.isFinite(row[3])).length);\n      return {\n        ...values,\n        E: avgE * 1e9,\n        alpha: avgAlpha * 1e-6,\n        yieldPa: Number.isFinite(avgYield) ? avgYield * 1e6 : values.yieldPa,\n        usedAdvanced: true\n      };\n    }\n\n    function calculate() {\n      let values = parseAdvanced(collectInputs());\n      const warnings = validate(values);\n      const R = Math.min(1, Math.max(0, Number.isFinite(values.restraint) ? values.restraint : 0));\n      const freeStrain = values.alpha * values.dT;\n      const freeExpansion = values.length * freeStrain;\n      let signedStress = -R * values.E * freeStrain;\n      let actualExpansion = (1 - R) * freeExpansion;\n      let restrainedExpansion = R * freeExpansion;\n      let modeNote = \"Partial restraint is a simplified engineering approximation.\";\n      let differentialExpansion = NaN;\n\n      if (state.mode === \"free\" || state.mode === \"strain\") {\n        signedStress = 0;\n        actualExpansion = freeExpansion;\n        restrainedExpansion = 0;\n        modeNote = \"Free expansion creates thermal strain but no axial stress when motion is unrestrained.\";\n      }\n\n      if (state.mode === \"displacement\") {\n        const restricted = freeExpansion - values.allowedDisp;\n        restrainedExpansion = Math.max(0, restricted);\n        signedStress = -Math.sign(values.dT || 1) * values.E * Math.abs(restrainedExpansion \/ values.length);\n        actualExpansion = values.allowedDisp;\n        modeNote = \"Stress is based on free expansion minus the allowed displacement.\";\n      }\n\n      if (state.mode === \"spring\") {\n        const km = values.E * values.area \/ values.length;\n        const forceMag = Math.abs(freeExpansion) \/ ((1 \/ km) + (1 \/ values.ks));\n        signedStress = -Math.sign(values.dT || 1) * forceMag \/ values.area;\n        restrainedExpansion = Math.abs(signedStress) \/ values.E * values.length * Math.sign(freeExpansion);\n        actualExpansion = freeExpansion - restrainedExpansion;\n        modeNote = \"Spring restraint uses a simplified member-support series stiffness model.\";\n      }\n\n      if (state.mode === \"allowTemp\") {\n        modeNote = \"Allowable Delta T is solved from sigma_allow \/ (R * E * alpha).\";\n      }\n\n      if (state.mode === \"stressTemp\") {\n        modeNote = \"Temperature change is solved from known stress \/ (R * E * alpha).\";\n      }\n\n      if (state.mode === \"differential\") {\n        const choices = selectedComparison();\n        const first = choices[0] || materials[0];\n        const second = choices[1] || materials[1];\n        differentialExpansion = values.length * ((first.alpha - second.alpha) * 1e-6) * values.dT;\n        modeNote = `${first.name} vs ${second.name}: differential expansion is based on equal original lengths.`;\n      }\n\n      const stressAbs = Math.abs(signedStress);\n      const force = Number.isFinite(values.area) ? signedStress * values.area : NaN;\n      const utilization = values.allowPa > 0 ? stressAbs \/ values.allowPa * 100 : NaN;\n      const safetyFactor = stressAbs > 0 && values.allowPa > 0 ? values.allowPa \/ stressAbs : Infinity;\n      const yieldFactor = stressAbs > 0 && values.yieldPa > 0 ? values.yieldPa \/ stressAbs : Infinity;\n      const allowableDeltaT = values.allowPa > 0 && R > 0 && values.E > 0 && values.alpha !== 0 ? values.allowPa \/ (R * values.E * Math.abs(values.alpha)) : NaN;\n      const tempFromStress = R > 0 && values.E > 0 && values.alpha !== 0 ? -values.knownStressPa \/ (R * values.E * values.alpha) : NaN;\n      const requiredR = values.E > 0 && values.alpha !== 0 && values.dT !== 0 ? Math.abs(values.knownStressPa \/ (values.E * values.alpha * values.dT)) : NaN;\n\n      if (values.allowPa > 0 && utilization > 100) warnings.push(\"Calculated stress exceeds allowable stress.\");\n      if (values.yieldPa > 0 && stressAbs >= values.yieldPa) warnings.push(\"Calculated stress exceeds yield strength; linear elastic thermal stress is no longer valid after significant yielding.\");\n      if (values.usedAdvanced) warnings.push(\"Advanced data was averaged; this is still an approximation for temperature-dependent properties.\");\n      if (Number.isFinite(requiredR) && requiredR > 1) warnings.push(\"Required restraint factor from known stress exceeds 1.0 and is not physically compatible with the simple model.\");\n\n      state.result = {\n        ...values,\n        R,\n        freeStrain,\n        freeExpansion,\n        actualExpansion,\n        restrainedExpansion,\n        signedStress,\n        stressAbs,\n        force,\n        utilization,\n        safetyFactor,\n        yieldFactor,\n        allowableDeltaT,\n        tempFromStress,\n        requiredR,\n        differentialExpansion,\n        modeNote,\n        warnings\n      };\n\n      $(\"resultsPanel\").classList.remove(\"hidden\");\n      renderResults();\n      renderWarnings(warnings);\n      updateVisibleFields();\n    }\n\n    function statusFor(result) {\n      if (result.yieldPa > 0 && result.stressAbs >= result.yieldPa) return [\"Potential Yielding\", \"danger\", \"YLD\", \"Thermal stress reaches or exceeds yield strength.\"];\n      if (result.utilization > 100) return [\"Exceeds Allowable Stress\", \"danger\", \"MAX\", \"Stress is above the entered allowable stress.\"];\n      if (result.utilization >= 90) return [\"Near Allowable Limit\", \"caution\", \"90\", \"Stress is close to the entered allowable limit.\"];\n      if (result.utilization >= 70) return [\"Caution\", \"caution\", \"70\", \"Stress utilization is moderate.\"];\n      if (result.stressAbs > 0) return [\"Acceptable\", \"\", \"OK\", result.modeNote];\n      return [\"Safe\", \"\", \"OK\", result.modeNote];\n    }\n\n    function renderResults() {\n      const r = state.result;\n      const [title, klass, dot, text] = statusFor(r);\n      $(\"statusBand\").className = `status-band ${klass}`;\n      $(\"statusDot\").textContent = dot;\n      $(\"statusTitle\").textContent = title;\n      $(\"statusText\").textContent = text;\n      const tempUnitLabel = $(\"tempUnit\").value;\n      const rows = [\n        [\"Temperature Change\", `${format(r.dT, 3)} C`, `${format(r.dT * 1.8, 3)} F difference`],\n        [\"Thermal Strain\", `${format(r.freeStrain, 6)}`, `${format(r.freeStrain * 100, 5)}% | ${format(r.freeStrain * 1e6, 2)} microstrain`],\n        [\"Free Expansion\", lengthDisplay(r.freeExpansion), `Original length: ${format(getNumber(\"length\"), 3)} ${$(\"lengthUnit\").value}`],\n        [\"Actual Expansion\", lengthDisplay(r.actualExpansion), `Restrained: ${lengthDisplay(r.restrainedExpansion)}`],\n        [\"Thermal Stress\", stressDisplay(r.signedStress), r.signedStress < 0 ? \"Compression\" : r.signedStress > 0 ? \"Tension\" : \"No restrained stress\"],\n        [\"Thermal Force\", forceDisplay(r.force), Number.isFinite(r.force) ? `${format(r.force, 2)} N` : \"Enter area to calculate force\"],\n        [\"Stress Utilization\", Number.isFinite(r.utilization) ? `${format(r.utilization, 1)}%` : \"N\/A\", \"Based on allowable stress\"],\n        [\"Safety Factor\", Number.isFinite(r.safetyFactor) ? format(r.safetyFactor, 2) : \"Infinity\", `Yield SF: ${Number.isFinite(r.yieldFactor) ? format(r.yieldFactor, 2) : \"Infinity\"}`],\n        [\"Allowable Delta T\", Number.isFinite(r.allowableDeltaT) ? `${format(r.allowableDeltaT, 2)} C` : \"N\/A\", \"From allowable stress\"],\n        [\"Delta T From Stress\", Number.isFinite(r.tempFromStress) ? `${format(r.tempFromStress, 2)} C` : \"N\/A\", `Known stress uses ${$(\"stressUnit\").value}`],\n        [\"Required Restraint\", Number.isFinite(r.requiredR) ? format(r.requiredR, 3) : \"N\/A\", \"Limited to 0 through 1\"],\n        [\"Differential Expansion\", Number.isFinite(r.differentialExpansion) ? lengthDisplay(r.differentialExpansion) : \"N\/A\", \"First two selected comparison materials\"],\n        [\"Input Delta\", `${format(getNumber(\"finalTemp\") - getNumber(\"initialTemp\"), 3)} ${tempUnitLabel}`, \"Temperature differences avoid absolute offsets\"]\n      ];\n      $(\"resultsGrid\").innerHTML = rows.map(([label, value, sub]) => `\n        <div class=\"metric\">\n          <div class=\"label\">${label}<\/div>\n          <div class=\"value\">${value}<\/div>\n          <div class=\"sub\">${sub}<\/div>\n        <\/div>\n      `).join(\"\");\n    }\n\n    function renderWarnings(warnings) {\n      $(\"warnings\").innerHTML = warnings.map((warning) => `<div class=\"warning\">${warning}<\/div>`).join(\"\");\n    }\n\n    function drawDiagram() {\n      const r = state.result;\n      const heat = r.dT >= 0;\n      const expansionScale = Math.min(90, Math.abs(r.freeStrain) * 90000);\n      const baseX = 120;\n      const freeW = 360;\n      const actualW = Math.max(120, freeW + Math.sign(r.freeExpansion || 1) * expansionScale * (state.mode === \"free\" ? 1 : 1 - r.R));\n      const stressColor = r.signedStress < 0 ? \"#b42318\" : \"#2563eb\";\n      $(\"diagram\").innerHTML = `\n        <defs>\n          <marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n            <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"${stressColor}\"><\/path>\n          <\/marker>\n          <linearGradient id=\"barGrad\" x1=\"0\" x2=\"1\">\n            <stop offset=\"0\" stop-color=\"#73808a\"><\/stop>\n            <stop offset=\"1\" stop-color=\"#2563eb\"><\/stop>\n          <\/linearGradient>\n        <\/defs>\n            <rect x=\"30\" y=\"30\" width=\"660\" height=\"260\" rx=\"8\" fill=\"#eef4ff\" stroke=\"#cfdcde\"><\/rect>\n        <text x=\"48\" y=\"62\" fill=\"#172326\" font-size=\"18\" font-weight=\"800\">${heat ? \"Heating\" : \"Cooling\"} under ${r.R === 1 ? \"full\" : r.R === 0 ? \"free\" : \"partial\"} restraint<\/text>\n        <text x=\"48\" y=\"88\" fill=\"#5e6f73\" font-size=\"13\">Positive stress = tension, negative stress = compression<\/text>\n        <line x1=\"${baseX}\" y1=\"132\" x2=\"${baseX + freeW}\" y2=\"132\" stroke=\"#87949a\" stroke-width=\"10\" stroke-linecap=\"round\"><\/line>\n        <text x=\"${baseX + 140}\" y=\"116\" fill=\"#5e6f73\" font-size=\"13\">Original length<\/text>\n        <rect x=\"${baseX}\" y=\"168\" width=\"${actualW}\" height=\"38\" rx=\"5\" fill=\"url(#barGrad)\"><\/rect>\n        <text x=\"${baseX + 12}\" y=\"193\" fill=\"#fff\" font-size=\"14\" font-weight=\"800\">Actual thermal movement<\/text>\n        <rect x=\"82\" y=\"154\" width=\"22\" height=\"68\" fill=\"#53616a\"><\/rect>\n        <path d=\"M82 154 L58 170 M82 176 L58 192 M82 198 L58 214\" stroke=\"#53616a\" stroke-width=\"4\"><\/path>\n        <rect x=\"${baseX + freeW + 36}\" y=\"154\" width=\"22\" height=\"68\" fill=\"${r.R > 0.05 ? \"#53616a\" : \"#cfdcde\"}\"><\/rect>\n        <path d=\"M${baseX + freeW + 58} 154 L${baseX + freeW + 82} 170 M${baseX + freeW + 58} 176 L${baseX + freeW + 82} 192 M${baseX + freeW + 58} 198 L${baseX + freeW + 82} 214\" stroke=\"${r.R > 0.05 ? \"#53616a\" : \"#cfdcde\"}\" stroke-width=\"4\"><\/path>\n        <line x1=\"${baseX + actualW + 12}\" y1=\"187\" x2=\"${baseX + freeW + expansionScale + 24}\" y2=\"187\" stroke=\"#b7791f\" stroke-width=\"3\" marker-end=\"url(#arrow)\"><\/line>\n        <text x=\"${baseX + 120}\" y=\"242\" fill=\"#334347\" font-size=\"14\">Free expansion: ${lengthDisplay(r.freeExpansion)}<\/text>\n        <text x=\"${baseX + 120}\" y=\"264\" fill=\"#334347\" font-size=\"14\">Thermal stress: ${stressDisplay(r.signedStress)} (${r.signedStress < 0 ? \"compression\" : r.signedStress > 0 ? \"tension\" : \"none\"})<\/text>\n        <line x1=\"${baseX + 52}\" y1=\"224\" x2=\"${baseX + 12}\" y2=\"224\" stroke=\"${stressColor}\" stroke-width=\"4\" marker-end=\"url(#arrow)\"><\/line>\n        <line x1=\"${baseX + actualW - 52}\" y1=\"224\" x2=\"${baseX + actualW - 12}\" y2=\"224\" stroke=\"${stressColor}\" stroke-width=\"4\" marker-end=\"url(#arrow)\"><\/line>\n      `;\n    }\n\n    function drawAxes(ctx, w, h, titleY) {\n      ctx.clearRect(0, 0, w, h);\n      ctx.fillStyle = \"#fbfdfd\";\n      ctx.fillRect(0, 0, w, h);\n      ctx.strokeStyle = \"#cfdcde\";\n      ctx.lineWidth = 1;\n      ctx.beginPath();\n      ctx.moveTo(54, 20);\n      ctx.lineTo(54, h - 42);\n      ctx.lineTo(w - 18, h - 42);\n      ctx.stroke();\n      ctx.fillStyle = \"#5e6f73\";\n      ctx.font = \"12px system-ui\";\n      ctx.fillText(titleY, 10, 18);\n    }\n\n    function drawLineChart(canvas, points, color, yLabel, xLabels) {\n      const ctx = canvas.getContext(\"2d\");\n      const w = canvas.width;\n      const h = canvas.height;\n      drawAxes(ctx, w, h, yLabel);\n      const minX = Math.min(...points.map((p) => p.x));\n      const maxX = Math.max(...points.map((p) => p.x));\n      const maxY = Math.max(1, ...points.map((p) => Math.abs(p.y)));\n      const xMap = (x) => 54 + (x - minX) \/ (maxX - minX || 1) * (w - 86);\n      const yMap = (y) => (h - 42) - (Math.abs(y) \/ maxY) * (h - 72);\n      ctx.strokeStyle = color;\n      ctx.lineWidth = 3;\n      ctx.beginPath();\n      points.forEach((p, i) => {\n        const x = xMap(p.x);\n        const y = yMap(p.y);\n        if (i === 0) ctx.moveTo(x, y);\n        else ctx.lineTo(x, y);\n      });\n      ctx.stroke();\n      ctx.fillStyle = color;\n      points.forEach((p) => {\n        ctx.beginPath();\n        ctx.arc(xMap(p.x), yMap(p.y), 3, 0, Math.PI * 2);\n        ctx.fill();\n      });\n      ctx.fillStyle = \"#5e6f73\";\n      ctx.fillText(xLabels[0], 54, h - 18);\n      ctx.fillText(xLabels[1], w - 76, h - 18);\n      ctx.fillText(`${format(maxY, 1)}`, 8, 34);\n      ctx.fillText(\"0\", 36, h - 44);\n    }\n\n    function drawStressChart() {\n      const r = state.result;\n      const canvas = $(\"stressChart\");\n      const span = Math.max(10, Math.abs(r.dT) * 1.4);\n      const points = [];\n      for (let i = -6; i <= 6; i += 1) {\n        const dt = i \/ 6 * span;\n        points.push({ x: dt, y: fromPa(-r.R * r.E * r.alpha * dt, state.unitSystem === \"metric\" ? \"MPa\" : \"ksi\") });\n      }\n      drawLineChart(canvas, points, \"#2563eb\", `|stress| ${state.unitSystem === \"metric\" ? \"MPa\" : \"ksi\"}`, [`-${format(span, 0)} C`, `${format(span, 0)} C`]);\n    }\n\n    function drawRestraintChart() {\n      const r = state.result;\n      const canvas = $(\"restraintChart\");\n      const points = [];\n      for (let i = 0; i <= 10; i += 1) {\n        const factor = i \/ 10;\n        points.push({ x: factor, y: fromPa(-factor * r.E * r.alpha * r.dT, state.unitSystem === \"metric\" ? \"MPa\" : \"ksi\") });\n      }\n      drawLineChart(canvas, points, \"#2563eb\", `|stress| ${state.unitSystem === \"metric\" ? \"MPa\" : \"ksi\"}`, [\"R 0\", \"R 1\"]);\n    }\n\n    function selectedComparison() {\n      return [...document.querySelectorAll(\".compare-check:checked\")].slice(0, 6).map((box) => materials.find((m) => m.name === box.value)).filter(Boolean);\n    }\n\n    function updateVisibleFields() {\n      const visible = new Set([\"material-base\", \"temperature\"]);\n      if (![\"strain\", \"allowTemp\", \"stressTemp\", \"safety\"].includes(state.mode)) visible.add(\"length\");\n      if (![\"free\", \"strain\"].includes(state.mode)) visible.add(\"restraint\");\n      if ([\"fully\", \"partial\", \"safety\", \"spring\"].includes(state.mode)) visible.add(\"area\");\n      if ([\"fully\", \"partial\", \"allowTemp\", \"safety\", \"compare\", \"displacement\", \"spring\", \"differential\"].includes(state.mode)) visible.add(\"strength\");\n      if ([\"stressTemp\", \"safety\", \"allowTemp\"].includes(state.mode)) visible.add(\"stress\");\n      if ([\"stressTemp\", \"safety\"].includes(state.mode)) visible.add(\"known-stress\");\n      if (state.mode === \"displacement\") visible.add(\"displacement\");\n      if (state.mode === \"spring\") visible.add(\"spring\");\n      if ([\"compare\", \"differential\"].includes(state.mode)) visible.add(\"comparison\");\n      if ([\"fully\", \"partial\", \"allowTemp\"].includes(state.mode)) visible.add(\"advanced\");\n      document.querySelectorAll(\"[data-field]\").forEach((field) => {\n        field.classList.toggle(\"hidden\", !visible.has(field.dataset.field));\n      });\n    }\n\n    function renderComparison() {\n      const r = state.result;\n      const unit = state.unitSystem === \"metric\" ? \"MPa\" : \"ksi\";\n      const rows = selectedComparison().map((m) => {\n        const stress = Math.abs(-r.R * m.E * 1e9 * m.alpha * 1e-6 * r.dT);\n        const expansion = r.length * m.alpha * 1e-6 * r.dT;\n        const sf = stress > 0 ? (m.yield * 1e6) \/ stress : Infinity;\n        return { ...m, stress, expansion, sf };\n      }).sort((a, b) => a.stress - b.stress);\n      const maxStress = Math.max(1, ...rows.map((row) => row.stress));\n      $(\"comparisonBars\").innerHTML = rows.map((row) => `\n        <div class=\"comparison-row\">\n          <strong>${row.name}<\/strong>\n          <div class=\"bar-track\"><div class=\"bar-fill\" style=\"width:${Math.max(2, row.stress \/ maxStress * 100)}%\"><\/div><\/div>\n          <span>${format(fromPa(row.stress, unit), 2)} ${unit} | SF ${format(row.sf, 2)}<\/span>\n        <\/div>\n      `).join(\"\") + `<div class=\"notice\">Ranked by lowest restrained thermal stress. Fully restrained stress depends on both E and alpha; high expansion does not always mean highest stress when modulus is low.<\/div>`;\n    }\n\n    function bindEvents() {\n      $(\"modeTabs\").addEventListener(\"click\", (event) => {\n        const btn = event.target.closest(\"button[data-mode]\");\n        if (!btn) return;\n        state.mode = btn.dataset.mode;\n        populateModes();\n        updateVisibleFields();\n        if (state.hasCalculated) calculate();\n      });\n      $(\"unitTabs\").addEventListener(\"click\", (event) => {\n        const btn = event.target.closest(\"button[data-unit]\");\n        if (!btn) return;\n        state.unitSystem = btn.dataset.unit;\n        document.querySelectorAll(\"#unitTabs button\").forEach((item) => item.classList.toggle(\"active\", item === btn));\n        if (state.hasCalculated) calculate();\n      });\n      $(\"materialSearch\").addEventListener(\"input\", populateMaterials);\n      $(\"material\").addEventListener(\"change\", () => {\n        applyPreset();\n        if (state.hasCalculated) calculate();\n      });\n      inputIds.forEach((id) => $(id).addEventListener(\"input\", () => {\n        if (state.hasCalculated) calculate();\n      }));\n      $(\"calculateBtn\").addEventListener(\"click\", () => {\n        state.hasCalculated = true;\n        calculate();\n      });\n      document.addEventListener(\"change\", (event) => {\n        if (event.target.classList.contains(\"compare-check\") && state.hasCalculated) calculate();\n      });\n    }\n\n    populateMaterials();\n    populateComparison();\n    populateModes();\n    bindEvents();\n    defaults();\n  <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>The Thermal Stress Calculator is a practical engineering tool designed to estimate stress caused by restricted thermal expansion or contraction. By entering Young\u2019s modulus, thermal&#8230;<\/p>","protected":false},"author":2,"featured_media":24875,"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":[8663,8583,8716,8717],"class_list":["post-24874","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-materials-science-and-properties","tag-stress-analysis","tag-thermal-expansion","tag-thermal-strain","tag-thermal-stress","pmpro-has-access"],"acf":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/www.mechstream.com\/wp-content\/uploads\/2026\/09\/Thermal-Stress-Calculator.png","_links":{"self":[{"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/posts\/24874","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/comments?post=24874"}],"version-history":[{"count":8,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/posts\/24874\/revisions"}],"predecessor-version":[{"id":24885,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/posts\/24874\/revisions\/24885"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/media\/24875"}],"wp:attachment":[{"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/media?parent=24874"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/categories?post=24874"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/tags?post=24874"}],"curies":[{"name":"\u0648\u0648\u0631\u062f\u0628\u0631\u064a\u0633","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}