{"id":47534,"date":"2025-08-19T15:13:20","date_gmt":"2025-08-19T15:13:20","guid":{"rendered":"https:\/\/staging.nxtprocess.com\/calculateur-poc\/"},"modified":"2025-08-20T12:37:42","modified_gmt":"2025-08-20T12:37:42","slug":"calculateur-poc","status":"publish","type":"page","link":"https:\/\/staging.nxtprocess.com\/fr\/calculateur-poc\/","title":{"rendered":"Calculateur POC"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"47534\" class=\"elementor elementor-47534 elementor-47528\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9f4cbbc e-flex e-con-boxed e-con e-parent\" data-id=\"9f4cbbc\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-10c35e1 elementor-widget elementor-widget-html\" data-id=\"10c35e1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<style>\r\n  :root {\r\n    --primary-color: #0f5ba7;\r\n    --secondary-color: #f5f9ff;\r\n    --accent-color: #00a4e4;\r\n    --text-color: #333;\r\n  }\r\n\r\n  \/* body {\r\n    font-family: \"Segoe UI\", Tahoma, sans-serif;\r\n    margin: 0;\r\n    padding: 0;\r\n    background: linear-gradient(to bottom right, var(--secondary-color), #ffffff);\r\n    color: var(--text-color);\r\n  } *\/\r\n\r\n  .inner-container header {\r\n    background-color: #000;\r\n    color: #fff;\r\n    \/* padding: 1rem 2rem; *\/\r\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\r\n  }\r\n\r\n  .inner-container h1 {\r\n    margin: 0;\r\n    font-size: 1.8rem;\r\n  }\r\n\r\n  .container {\r\n    max-width: 960px;\r\n    margin: 2rem auto;\r\n    background: #fff;\r\n    padding: 2rem;\r\n    border-radius: 8px;\r\n    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);\r\n  }\r\n\r\n  .inner-container label {\r\n    display: block;\r\n    margin-top: 0.5rem;\r\n    font-weight: 600;\r\n  }\r\n\r\n  .inner-container input[type=\"number\"],\r\n  .inner-containe select {\r\n    width: 100%;\r\n    padding: 13px 20px;\r\n    margin-top: 0.3rem;\r\n    border: 1px solid #E5E7EA;\r\n    border-radius: 11px;\r\n    font-size: 15px;\r\n    color: #5c6672;\r\n  }\r\n\r\n  .inner-container .row {\r\n    display: flex;\r\n    flex-wrap: wrap;\r\n    gap: 1rem;\r\n    margin-top: 1rem;\r\n    align-items: end;\r\n  }\r\n\r\n  .inner-container .col {\r\n    flex: 1;\r\n    min-width: 160px;\r\n  }\r\n\r\n  .container.cal-form {\r\n    padding-top: 12px !important;\r\n  }\r\n\r\n  .inner-container .wizard-step.active {\r\n    padding: 0 15px !important;\r\n  }\r\n\r\n  .inner-container .wizard-nav {\r\n    margin-left: -12px;\r\n  }\r\n\r\n  .inner-container button {\r\n    margin-top: 0.5rem;\r\n  }\r\n\r\n  .inner-container .row select {\r\n    margin-top: 0.3rem;\r\n  }\r\n\r\n  .inner-container label {\r\n    display: block;\r\n    margin-top: 0.5rem;\r\n    font-size: 15px;\r\n    font-weight: 500;\r\n    color: #000;\r\n    font-family: \"Avenir\", Sans-serif !important;\r\n  }\r\n\r\n  \/* Responsive adjustments *\/\r\n  @media (max-width: 600px) {\r\n    .inner-container .row {\r\n      flex-direction: column;\r\n    }\r\n\r\n    .inner-container .col {\r\n      min-width: 100%;\r\n    }\r\n\r\n    .inner-container button {\r\n      width: 100%;\r\n    }\r\n  }\r\n\r\n  .inner-container button {\r\n    margin-top: 1.5rem;\r\n    padding: 5px 30px;\r\n    background-color: var(--primary-color);\r\n    color: #fff;\r\n    border: none;\r\n    border-radius: 4px;\r\n    font-size: 15px;\r\n    cursor: pointer;\r\n    font-weight: 500;\r\n  }\r\n\r\n  .inner-container #result {\r\n    margin-top: 2rem;\r\n    padding: 1.5rem;\r\n    background-color: var(--secondary-color);\r\n    border-radius: 6px;\r\n    box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.05);\r\n  }\r\n\r\n  .inner-container #gauge {\r\n    width: 100%;\r\n    max-width: 400px;\r\n    height: 200px;\r\n    margin-top: 1rem;\r\n  }\r\n\r\n  .inner-container h2 {\r\n    margin-top: 1.5rem;\r\n    color: var(--primary-color);\r\n  }\r\n\r\n  \/* Wizard progress bar *\/\r\n  .inner-container .progress-bar {\r\n    display: flex;\r\n    justify-content: space-between;\r\n    margin-bottom: 1rem;\r\n  }\r\n\r\n  .inner-container .progress-step {\r\n    flex: 1;\r\n    padding: 0.5rem;\r\n    background-color: #e0e6f0;\r\n    border-radius: 4px;\r\n    text-align: center;\r\n    font-weight: bold;\r\n    color: #666;\r\n    margin-right: 0.5rem;\r\n  }\r\n\r\n  .inner-container .progress-step:last-child {\r\n    margin-right: 0;\r\n  }\r\n\r\n  .inner-container .progress-step.active {\r\n    background-color: var(--primary-color);\r\n    color: #fff;\r\n  }\r\n\r\n  \/* Wizard step containers *\/\r\n  .inner-container .wizard-step {\r\n    display: none;\r\n  }\r\n\r\n  .inner-container .wizard-step.active {\r\n    display: block;\r\n  }\r\n\r\n  .inner-container .wizard-nav {\r\n    margin-top: 1rem;\r\n    display: flex;\r\n    justify-content: space-between;\r\n    gap: 1rem;\r\n  }\r\n\r\n  \/* Dark mode styling *\/\r\n  body.dark {\r\n    background: #121212;\r\n    color: #eee;\r\n  }\r\n\r\n  body.dark .container {\r\n    background: #1e1e1e;\r\n    color: #eee;\r\n  }\r\n\r\n  body.dark header {\r\n    background-color: #000;\r\n  }\r\n\r\n  body.dark .progress-step {\r\n    background-color: #333;\r\n    color: #bbb;\r\n  }\r\n\r\n  body.dark .progress-step.active {\r\n    background-color: var(--primary-color);\r\n    color: #fff;\r\n  }\r\n\r\n  body.dark input[type=\"number\"],\r\n  body.dark select {\r\n    background-color: #2a2a2a;\r\n    color: #eee;\r\n    border-color: #555;\r\n  }\r\n\r\n  body.dark label {\r\n    color: #ccc;\r\n  }\r\n\r\n  .progress-bar {\r\n    margin-top: 40px;\r\n  }\r\n\r\n.inner-container h2 {\r\n    margin: 30px 0px 10px;\r\n    font-size: 32px;\r\n    color: #000;\r\n}\r\n  h2.hero-heading {\r\n    margin-top: 10px;\r\n    margin-bottom: 15px;\r\n    font-size: 32px;\r\n    color: #000;\r\n\r\n  } \r\n  h2.hero-heading,\r\n  .cal-main-para\r\n  {\r\n  text-align: center;\r\n  }\r\n\r\n  .cal-main-para {\r\n    color: #000;\r\n  }\r\n  .inner-container #result {\r\n    padding: 2px 30px 40px;\r\n}\r\n<\/style>\r\n<div class=\"container cal-form\">\r\n  <div class=\"inner-container\">\r\n    <!-- Error message container -->\r\n    <div id=\"errorContainer\" style=\"color:#b00020; margin-bottom: 1rem;\"><\/div>\r\n    <h2 class=\"hero-heading\">Calculateur de pertes de charge<\/h2>\r\n    <p class=\"cal-main-para\">\r\n Estimez les pertes de charge et la chute de pression dans les tubes sanitaires ou les tuyaux schedule\r\n en utilisant l'\u00e9quation de Darcy-Weisbach avec le facteur de friction\r\n de Swamee-Jain. Ce calculateur est inspir\u00e9 d'un outil similaire fourni par\r\n NXT Process. Saisissez les conditions d'\u00e9coulement, les caract\u00e9ristiques du tuyau et\r\n les raccords ci-dessous.  \r\n    <\/p>\r\n    <!-- Progress bar for wizard steps -->\r\n    <div class=\"progress-bar\">\r\n      <div class=\"progress-step active\" data-step=\"1\">1<\/div>\r\n      <div class=\"progress-step\" data-step=\"2\">2<\/div>\r\n      <div class=\"progress-step\" data-step=\"3\">3<\/div>\r\n    <\/div>\r\n    <!-- Step 1: flow and fluid properties -->\r\n    <div id=\"step1\" class=\"wizard-step active\">\r\n      <div class=\"row\">\r\n        <div class=\"col\">\r\n          <label>D\u00e9bit (valeur)<\/label>\r\n          <input type=\"number\" id=\"flow\" value=\"100\" step=\"0.1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Unit\u00e9s<\/label>\r\n          <select id=\"flowUnits\">\r\n            <option value=\"gpm\">gpm US<\/option>\r\n            <option value=\"lpm\">L\/min<\/option>\r\n          <\/select> \r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"row\">\r\n        <div class=\"col\">\r\n          <!-- Label for selecting pipe size. The text 'nominal OD' was removed because\r\n           schedule pipe sizes do not correspond to outside diameter. -->\r\n          <label>Taille du tuyau (po)<\/label>\r\n          <select id=\"tubeSize\">\r\n            <option value=\"0.5\">0,50 po<\/option>\r\n            <option value=\"0.75\">0,75 po<\/option>\r\n            <option value=\"1.0\">1,00 po<\/option>\r\n            <option value=\"1.25\">1,25 po<\/option>\r\n            <option value=\"1.5\">1,50 po<\/option>\r\n            <option value=\"2.0\" selected>2,00 po<\/option>\r\n            <option value=\"2.5\">2,50 po<\/option>\r\n            <option value=\"3.0\">3,00 po<\/option>\r\n            <option value=\"4.0\">4,00 po<\/option>\r\n            <option value=\"5.0\">5,00 po<\/option>\r\n            <option value=\"6.0\">6,00 po<\/option>\r\n            <option value=\"8.0\">8,00 po<\/option>\r\n          <\/select>\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Longueur droite (valeur)<\/label>\r\n          <input type=\"number\" id=\"length\" value=\"10\" step=\"0.1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Unit\u00e9s de longueur<\/label>\r\n          <select id=\"lengthUnits\">\r\n            <option value=\"ft\">pi<\/option>\r\n            <option value=\"m\">m<\/option>\r\n          <\/select> \r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"row\">\r\n        <div class=\"col\">\r\n          <label>Viscosit\u00e9 (cP)<\/label>\r\n          <input type=\"number\" id=\"viscosity\" value=\"1.0\" step=\"0.1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Densit\u00e9 relative<\/label>\r\n          <input type=\"number\" id=\"specificGravity\" value=\"1.0\" step=\"0.01\">\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <!-- Temperature input and viscosity hint -->\r\n      <div class=\"row\">\r\n        <div class=\"col\">\r\n          <label>Temp\u00e9rature (\u00b0C)<\/label>\r\n          <input type=\"number\" id=\"temperature\" value=\"20\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label><\/label>\r\n          <div id=\"viscosityHint\" style=\"margin-top: 0.6rem; font-size: 0.9rem; color:#555;\"><\/div>\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"row\">\r\n        <div class=\"col\">\r\n          <label>Pr\u00e9r\u00e9glage du fluide<\/label>\r\n          <select id=\"fluidPreset\">\r\n            <option value=\"custom\" selected>Personnalis\u00e9<\/option>\r\n            <option value=\"water\">Eau<\/option>\r\n            <option value=\"beer\">Bi\u00e8re\/mo\u00fbt<\/option>\r\n            <option value=\"syrup\">Sirop<\/option>\r\n            <option value=\"milk\">Lait\/produits laitiers<\/option>\r\n            <option value=\"caustic\">Soude caustique (NEP)<\/option>\r\n            <option value=\"oil\">Huile v\u00e9g\u00e9tale<\/option>\r\n            <option value=\"honey\">Miel\/m\u00e9lasse<\/option>\r\n            <option value=\"glycol\">Glycol (33 %)<\/option>\r\n          <\/select>\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>\u00c9paisseur de paroi (po)<\/label>\r\n          <select id=\"wallThickness\">\r\n            <option value=\"0.065\" selected>0,065 po (cal. 16)<\/option>\r\n            <option value=\"0.083\">0,083 po (cal. 14)<\/option>\r\n            <option value=\"0.109\">0,109 po (cal. 12)<\/option>\r\n          <\/select> \r\n        <\/div>\r\n      <\/div>\r\n\r\n      <!-- End of Step 1 -->\r\n      <div class=\"wizard-nav\">\r\n        <button id=\"next1\" type=\"button\">Suivant<\/button>\r\n      <\/div>\r\n    <\/div><!-- end step1 -->\r\n    <!-- Step 2: pipe characteristics and fittings -->\r\n    <div id=\"step2\" class=\"wizard-step\" style=\"display:none;\">\r\n      <!-- Pipe type selector: sanitary vs schedule piping -->\r\n      <div class=\"row cal-row-2\">\r\n        <div class=\"col\">\r\n          <label>Type de tuyau<\/label>\r\n          <select id=\"pipeType\">\r\n            <option value=\"sanitary\" selected>Tube sanitaire<\/option>\r\n            <option value=\"schedule\">Tuyau Schedule<\/option>\r\n          <\/select> \r\n        <\/div>\r\n        <div class=\"col\" id=\"scheduleCol\" style=\"display:none;\">\r\n          <label>Schedule<\/label>\r\n          <select id=\"scheduleNumber\">\r\n            <option value=\"40\" selected>schedule 40<\/option>\r\n            <option value=\"80\">schedule 80<\/option>\r\n          <\/select> \r\n        <\/div>\r\n      <\/div>\r\n\r\n      <h2>Raccords<\/h2>\r\n      <div class=\"row\">\r\n        <div class=\"col\">\r\n          <label>Coudes 90\u00b0<\/label>\r\n          <input type=\"number\" id=\"elbow90\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Coudes 45\u00b0<\/label>\r\n          <input type=\"number\" id=\"elbow45\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Vannes \u00e0 boisseau<\/label>\r\n          <input type=\"number\" id=\"ballValve\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Vannes papillon<\/label>\r\n          <input type=\"number\" id=\"butterflyValve\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n      <\/div>\r\n      <div class=\"row\">\r\n        <div class=\"col\">\r\n          <label>T\u00e9s directs (travers\u00e9e)<\/label>\r\n          <input type=\"number\" id=\"teeRun\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>T\u00e9s lat\u00e9raux (d\u00e9rivation)<\/label>\r\n          <input type=\"number\" id=\"teeBranch\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>\u00c9l\u00e9vation verticale (pi)<\/label>\r\n          <input type=\"number\" id=\"verticalRise\" value=\"0\" step=\"0.1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Perte additionnelle (psi)<\/label>\r\n          <input type=\"number\" id=\"additionalLoss\" value=\"0\" step=\"0.1\">\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <div class=\"row\">\r\n        <div class=\"col\">\r\n          <label>Coudes \u00e0 grand rayon<\/label>\r\n          <input type=\"number\" id=\"elbowLong\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>Clapets anti-retour<\/label>\r\n          <input type=\"number\" id=\"checkValve\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>R\u00e9ducteurs<\/label>\r\n          <input type=\"number\" id=\"reducer\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n        <div class=\"col\">\r\n          <label>\u00c9largissements<\/label>\r\n          <input type=\"number\" id=\"expansion\" value=\"0\" step=\"1\">\r\n        <\/div>\r\n      <\/div>\r\n\r\n      <!-- Navigation for Step 2 -->\r\n      <div class=\"wizard-nav\">\r\n        <button id=\"back2\" type=\"button\">Retour<\/button>\r\n        <button id=\"next2\" type=\"button\">Suivant<\/button>\r\n      <\/div>\r\n    <\/div><!-- end step2 -->\r\n    <!-- Step 3: results -->\r\n    <div id=\"step3\" class=\"wizard-step\" style=\"display:none;\">\r\n      <div id=\"result\">\r\n        <!-- results will be inserted here -->\r\n      <\/div>\r\n      <div class=\"wizard-nav\">\r\n        <button id=\"back3\" type=\"button\">Retour<\/button>\r\n      <\/div>\r\n    <\/div>\r\n\r\n    <!-- Hidden calculate button left for non\u2011JavaScript fallback; not required since real\u2011time updates occur -->\r\n    <button id=\"calcBtn\" onclick=\"calculateLoss()\" style=\"display:none;\">Calculer les pertes<\/button>\r\n\r\n\r\n  <\/div><!-- end container -->\r\n\r\n\r\n\r\n  <script>\r\n    \/\/ Prepare a global Image object for the logo used in the PDF report. We load\r\n    \/\/ the local PNG asynchronously and convert it to a data URI at runtime. This\r\n    \/\/ avoids cross\u2011origin issues when exporting the canvas to a data URL for PDF.\r\n    const logoImage = new Image();\r\n    fetch('NXT_rgb.png')\r\n      .then(response => response.blob())\r\n      .then(blob => {\r\n        const reader = new FileReader();\r\n        reader.onloadend = () => {\r\n          logoImage.src = reader.result;\r\n        };\r\n        reader.readAsDataURL(blob);\r\n      })\r\n      .catch(err => {\r\n        console.error('Failed to load logo for report:', err);\r\n      });\r\n    \/\/ Constants matching the Python implementation\r\n    const absoluteRoughness = 1.5e-6; \/\/ metres\r\n    const tubeSpecs = {\r\n      \"0.5\": 0.065,\r\n      \"0.75\": 0.065,\r\n      \"1.0\": 0.065,\r\n      \"1.25\": 0.065,\r\n      \"1.5\": 0.065,\r\n      \"2.0\": 0.065,\r\n      \"2.5\": 0.065,\r\n      \"3.0\": 0.065,\r\n      \"4.0\": 0.083,\r\n      \"5.0\": 0.083,\r\n      \"6.0\": 0.109,\r\n      \"8.0\": 0.109\r\n    };\r\n    const equivFactors = {\r\n      elbow_90: 30.0,\r\n      elbow_45: 20.0,\r\n      ball_valve: 6.0,\r\n      butterfly_valve: 50.0,\r\n      tee_run: 20.0,\r\n      tee_branch: 60.0\r\n      , elbow_long: 15.0\r\n      , check_valve: 100.0\r\n      , reducer: 40.0\r\n      , expansion: 40.0\r\n    };\r\n\r\n    \/\/ Fluid presets (specific gravity and viscosity in cP)\r\n    const fluidPresets = {\r\n      water: { sg: 1.0, viscosity: 1.0 },\r\n      beer: { sg: 1.02, viscosity: 2.0 },\r\n      syrup: { sg: 1.3, viscosity: 10.0 }\r\n      , milk: { sg: 1.03, viscosity: 2.0 }\r\n      , caustic: { sg: 1.16, viscosity: 0.9 }\r\n      , oil: { sg: 0.92, viscosity: 50.0 }\r\n      , honey: { sg: 1.36, viscosity: 200.0 }\r\n      , glycol: { sg: 1.05, viscosity: 13.0 }\r\n    };\r\n\r\n    \/\/ Viscosity vs temperature data (cP) for each fluid preset.  Values are\r\n    \/\/ approximate and provide a simple basis for interpolation.  When the\r\n    \/\/ user selects a temperature, the calculator will estimate viscosity\r\n    \/\/ based on this table.  Temperatures are in \u00b0C.\r\n    const fluidViscosities = {\r\n      water: [\r\n        { temp: 0, visc: 1.79 },\r\n        { temp: 10, visc: 1.31 },\r\n        { temp: 20, visc: 1.00 },\r\n        { temp: 40, visc: 0.65 },\r\n        { temp: 60, visc: 0.47 },\r\n        { temp: 80, visc: 0.36 }\r\n      ],\r\n      beer: [\r\n        { temp: 10, visc: 3.0 },\r\n        { temp: 20, visc: 2.0 },\r\n        { temp: 40, visc: 1.5 },\r\n        { temp: 60, visc: 1.2 }\r\n      ],\r\n      syrup: [\r\n        { temp: 10, visc: 15.0 },\r\n        { temp: 20, visc: 10.0 },\r\n        { temp: 40, visc: 6.0 },\r\n        { temp: 60, visc: 4.0 }\r\n      ],\r\n      milk: [\r\n        { temp: 4, visc: 2.0 },\r\n        { temp: 10, visc: 1.8 },\r\n        { temp: 20, visc: 1.3 },\r\n        { temp: 40, visc: 0.9 }\r\n      ],\r\n      caustic: [\r\n        { temp: 20, visc: 1.2 },\r\n        { temp: 50, visc: 0.9 },\r\n        { temp: 70, visc: 0.7 }\r\n      ],\r\n      oil: [\r\n        { temp: 20, visc: 50.0 },\r\n        { temp: 40, visc: 30.0 },\r\n        { temp: 60, visc: 15.0 },\r\n        { temp: 80, visc: 8.0 }\r\n      ],\r\n      honey: [\r\n        { temp: 10, visc: 400.0 },\r\n        { temp: 25, visc: 200.0 },\r\n        { temp: 40, visc: 80.0 },\r\n        { temp: 60, visc: 40.0 }\r\n      ],\r\n      glycol: [\r\n        { temp: 0, visc: 25.0 },\r\n        { temp: 20, visc: 13.0 },\r\n        { temp: 40, visc: 7.0 },\r\n        { temp: 60, visc: 4.0 }\r\n      ]\r\n    };\r\n\r\n    \/**\r\n     * Linearly interpolate the viscosity for a fluid at a given temperature.\r\n     * If the temperature is outside the range, the nearest endpoint is used.\r\n     * @param {string} fluid - Key of fluid in fluidViscosities.\r\n     * @param {number} temp - Temperature in \u00b0C.\r\n     * @returns {number|null} Estimated viscosity in cP, or null if no data.\r\n     *\/\r\n    function interpolateViscosity(fluid, temp) {\r\n      const table = fluidViscosities[fluid];\r\n      if (!table) return null;\r\n      \/\/ If exact temperature exists, return directly\r\n      for (const entry of table) {\r\n        if (entry.temp === temp) return entry.visc;\r\n      }\r\n      \/\/ Find bounding entries\r\n      let lower = null;\r\n      let upper = null;\r\n      for (const entry of table) {\r\n        if (entry.temp <= temp && (!lower || entry.temp > lower.temp)) lower = entry;\r\n        if (entry.temp >= temp && (!upper || entry.temp < upper.temp)) upper = entry;\r\n      }\r\n      if (!lower) return upper ? upper.visc : null;\r\n      if (!upper) return lower.visc;\r\n      if (upper.temp === lower.temp) return lower.visc;\r\n      \/\/ Linear interpolation\r\n      const ratio = (temp - lower.temp) \/ (upper.temp - lower.temp);\r\n      return lower.visc + ratio * (upper.visc - lower.visc);\r\n    }\r\n\r\n    \/**\r\n     * Update the viscosity hint message based on selected fluid preset and\r\n     * temperature.  Optionally update the viscosity input field when a\r\n     * preset (non-custom) is chosen.\r\n     *\/\r\n    function updateViscosityHint() {\r\n      const preset = document.getElementById('fluidPreset').value;\r\n      const tempField = document.getElementById('temperature');\r\n      const temp = parseFloat(tempField.value);\r\n      const hintEl = document.getElementById('viscosityHint');\r\n      \/\/ If custom preset, clear hint and allow editing\r\n      if (preset === 'custom') {\r\n        hintEl.textContent = '';\r\n        document.getElementById('viscosity').removeAttribute('readonly');\r\n        return;\r\n      }\r\n      const est = interpolateViscosity(preset, temp);\r\n      if (est !== null) {\r\n        hintEl.textContent = `Estimated viscosity at ${temp.toFixed(0)}\u00b0C: ${est.toFixed(2)} cP`;\r\n        \/\/ Update viscosity field and lock it\r\n        const visField = document.getElementById('viscosity');\r\n        visField.value = est.toFixed(2);\r\n        visField.setAttribute('readonly', '');\r\n      } else {\r\n        hintEl.textContent = '';\r\n        document.getElementById('viscosity').removeAttribute('readonly');\r\n      }\r\n    }\r\n\r\n    \/\/ Internal diameters (inches) for schedule pipes by schedule number.  Only\r\n    \/\/ selected nominal sizes are provided.  Values are approximate for\r\n    \/\/ common carbon steel pipe (ANSI B36.10).  Schedule 40 and 80 are\r\n    \/\/ included; sanitary tubing sizes default to the wall thickness choice.\r\n    const scheduleDiameters = {\r\n      \"40\": {\r\n        \"0.5\": 0.62,\r\n        \"0.75\": 0.82,\r\n        \"1.0\": 1.05,\r\n        \"1.25\": 1.38,\r\n        \"1.5\": 1.61,\r\n        \"2.0\": 2.07,\r\n        \"2.5\": 2.47,\r\n        \"3.0\": 3.07,\r\n        \"4.0\": 4.03,\r\n        \"5.0\": 5.05,\r\n        \"6.0\": 6.07,\r\n        \"8.0\": 7.98\r\n      },\r\n      \"80\": {\r\n        \"0.5\": 0.55,\r\n        \"0.75\": 0.74,\r\n        \"1.0\": 0.96,\r\n        \"1.25\": 1.28,\r\n        \"1.5\": 1.50,\r\n        \"2.0\": 1.94,\r\n        \"2.5\": 2.32,\r\n        \"3.0\": 2.90,\r\n        \"4.0\": 3.83,\r\n        \"5.0\": 4.81,\r\n        \"6.0\": 5.76,\r\n        \"8.0\": 7.63\r\n      }\r\n    };\r\n\r\n    \/\/ Update viscosity and specific gravity based on fluid preset\r\n    function updateFluidPreset() {\r\n      const preset = document.getElementById('fluidPreset').value;\r\n      const visField = document.getElementById('viscosity');\r\n      const sgField = document.getElementById('specificGravity');\r\n      if (preset === 'custom') {\r\n        visField.removeAttribute('readonly');\r\n        sgField.removeAttribute('readonly');\r\n      } else {\r\n        const props = fluidPresets[preset];\r\n        if (props) {\r\n          visField.value = props.viscosity;\r\n          sgField.value = props.sg;\r\n          visField.setAttribute('readonly', '');\r\n          sgField.setAttribute('readonly', '');\r\n        }\r\n      }\r\n      updateViscosityHint();\r\n      calculateLoss();\r\n    }\r\n\r\n    \/\/ Handle unit conversion when flow or length units change\r\n    function handleUnitChange(inputId, selectId, convToNew) {\r\n      const input = document.getElementById(inputId);\r\n      const select = document.getElementById(selectId);\r\n      \/\/ Store previous unit on first run\r\n      if (!select.dataset.prev) {\r\n        select.dataset.prev = select.value;\r\n      }\r\n      select.addEventListener('change', function () {\r\n        const prev = select.dataset.prev;\r\n        const current = select.value;\r\n        let val = parseFloat(input.value);\r\n        if (!isFinite(val)) {\r\n          select.dataset.prev = current;\r\n          return;\r\n        }\r\n        \/\/ Flow units conversion\r\n        if (selectId === 'flowUnits') {\r\n          if (prev === 'gpm' && current === 'lpm') {\r\n            val = val * 3.78541;\r\n          } else if (prev === 'lpm' && current === 'gpm') {\r\n            val = val \/ 3.78541;\r\n          }\r\n        }\r\n        \/\/ Length units conversion\r\n        if (selectId === 'lengthUnits') {\r\n          if (prev === 'ft' && current === 'm') {\r\n            val = val * 0.3048;\r\n          } else if (prev === 'm' && current === 'ft') {\r\n            val = val \/ 0.3048;\r\n          }\r\n        }\r\n        input.value = val.toFixed(3);\r\n        select.dataset.prev = current;\r\n        calculateLoss();\r\n      });\r\n    }\r\n\r\n    \/\/ Helper to compute head (ft) for a given flow (gpm) using current form values\r\n    function computeHeadForFlow(flowVal) {\r\n      const tubeSize = parseFloat(document.getElementById('tubeSize').value);\r\n      const pipeType = document.getElementById('pipeType').value;\r\n      let dIn;\r\n      if (pipeType === 'schedule') {\r\n        const sched = document.getElementById('scheduleNumber').value;\r\n        const schedDiamTable = scheduleDiameters[sched] || {};\r\n        \/\/ Use a fallback to sanitary calculation if the size is not defined\r\n        dIn = schedDiamTable[String(tubeSize)] !== undefined ? schedDiamTable[String(tubeSize)] : (tubeSize - 2 * parseFloat(document.getElementById('wallThickness').value));\r\n      } else {\r\n        const wall = parseFloat(document.getElementById('wallThickness').value);\r\n        dIn = tubeSize - 2 * wall;\r\n      }\r\n      const dM = dIn * 0.0254;\r\n      const length = parseFloat(document.getElementById('length').value);\r\n      const lengthUnits = document.getElementById('lengthUnits').value;\r\n      const straightFt = lengthUnits === 'ft' ? length : length * 3.28084;\r\n      const viscosity = parseFloat(document.getElementById('viscosity').value);\r\n      const sg = parseFloat(document.getElementById('specificGravity').value);\r\n      \/\/ Fittings counts\r\n      const counts = {\r\n        elbow_90: parseInt(document.getElementById('elbow90').value, 10),\r\n        elbow_45: parseInt(document.getElementById('elbow45').value, 10),\r\n        ball_valve: parseInt(document.getElementById('ballValve').value, 10),\r\n        butterfly_valve: parseInt(document.getElementById('butterflyValve').value, 10),\r\n        tee_run: parseInt(document.getElementById('teeRun').value, 10),\r\n        tee_branch: parseInt(document.getElementById('teeBranch').value, 10),\r\n        elbow_long: parseInt(document.getElementById('elbowLong').value, 10),\r\n        check_valve: parseInt(document.getElementById('checkValve').value, 10),\r\n        reducer: parseInt(document.getElementById('reducer').value, 10),\r\n        expansion: parseInt(document.getElementById('expansion').value, 10)\r\n      };\r\n      \/\/ Convert flow to m3\/s from gpm\r\n      const flowM3s = flowVal * 0.00378541 \/ 60.0;\r\n      const area = Math.PI * (dM ** 2) \/ 4.0;\r\n      const velocity = flowM3s \/ area;\r\n      const density = 1000.0 * sg;\r\n      const mu = viscosity * 1e-3;\r\n      const reynolds = density * velocity * dM \/ mu;\r\n      const relRough = absoluteRoughness \/ dM;\r\n      const f = swameeJain(reynolds, relRough);\r\n      \/\/ Equivalent length\r\n      const dFt = dIn \/ 12.0;\r\n      let equivFt = 0;\r\n      for (const key in counts) {\r\n        equivFt += equivFactors[key] * counts[key] * dFt;\r\n      }\r\n      const totalLengthM = (straightFt + equivFt) * 0.3048;\r\n      const headM = f * (totalLengthM \/ dM) * (velocity * velocity) \/ (2.0 * 9.80665);\r\n      return headM * 3.28084;\r\n    }\r\n\r\n    \/\/ Copy summary to clipboard\r\n    let latestSummary = '';\r\n    let latestResults = null;\r\n    function copySummary() {\r\n      if (!navigator.clipboard) return;\r\n      navigator.clipboard.writeText(latestSummary).then(() => {\r\n        alert('Summary copied to clipboard');\r\n      }).catch(() => {\r\n        alert('Unable to copy summary');\r\n      });\r\n    }\r\n\r\n    \/\/ Download summary as a text file\r\n    function downloadSummary() {\r\n      const blob = new Blob([latestSummary], { type: 'text\/plain' });\r\n      const url = URL.createObjectURL(blob);\r\n      const a = document.createElement('a');\r\n      a.href = url;\r\n      a.download = 'friction_summary.txt';\r\n      document.body.appendChild(a);\r\n      a.click();\r\n      document.body.removeChild(a);\r\n      URL.revokeObjectURL(url);\r\n    }\r\n\r\n    \/\/ Draw a report on an off\u2011screen canvas and return the canvas\r\n    function createReportCanvas() {\r\n      \/\/ Use A4 at 72 dpi (595 \u00d7 842 points)\r\n      const canvas = document.createElement('canvas');\r\n      canvas.width = 595;\r\n      canvas.height = 842;\r\n      const ctx = canvas.getContext('2d');\r\n      \/\/ White background\r\n      ctx.fillStyle = '#ffffff';\r\n      ctx.fillRect(0, 0, canvas.width, canvas.height);\r\n      \/\/ Header\r\n      ctx.fillStyle = '#0f5ba7';\r\n      ctx.font = 'bold 24px Segoe UI';\r\n      ctx.fillText('Friction Loss Report', 40, 50);\r\n      if (!latestResults) return canvas;\r\n      \/\/ Summary lines\r\n      ctx.fillStyle = '#333';\r\n      ctx.font = '14px Segoe UI';\r\n      const lines = [];\r\n      \/\/ Round the flow to the nearest whole unit when reporting in the PDF\r\n      const flowRounded = Math.round(latestResults.flow);\r\n      lines.push(`Flow rate: ${flowRounded} ${latestResults.flowUnits.toUpperCase()}`);\r\n      lines.push(`Pipe size: ${latestResults.tubeSize} in`);\r\n      if (latestResults.pipeType === 'schedule') {\r\n        lines.push(`Schedule: ${latestResults.schedule}`);\r\n      } else {\r\n        lines.push(`Wall thickness: ${latestResults.wall.toFixed(3)} in`);\r\n      }\r\n      lines.push(`Internal diameter: ${latestResults.internalDiameter.toFixed(3)} in`);\r\n      lines.push(`Straight length: ${latestResults.straightLengthFt.toFixed(3)} ft`);\r\n      lines.push(`Viscosity: ${latestResults.viscosity} cP`);\r\n      lines.push(`Specific gravity: ${latestResults.specificGravity}`);\r\n      lines.push(`Total head loss: ${latestResults.headFt.toFixed(3)} ft`);\r\n      lines.push(`Pressure drop: ${latestResults.pressurePsi.toFixed(3)} psi`);\r\n      lines.push(`Reynolds number: ${Math.round(latestResults.reynolds)}`);\r\n      lines.push(`Friction factor: ${latestResults.frictionFactor.toFixed(5)}`);\r\n      lines.push(`Velocity: ${latestResults.velocityFt.toFixed(2)} ft\/s`);\r\n      \/\/ Include velocity advisory message when appropriate\r\n      const vFt = latestResults.velocityFt;\r\n      let vMessage = '';\r\n      if (vFt < 1.0) {\r\n        vMessage = 'Velocity is below 1 ft\/s. Consider decreasing the pipe size.';\r\n      } else if (vFt >= 10.0) {\r\n        vMessage = 'Velocity is 10 ft\/s or higher. Consider increasing the pipe size to prevent vibration in the piping network.';\r\n      }\r\n      if (vMessage) lines.push(vMessage);\r\n      let y = 90;\r\n      lines.forEach((line) => {\r\n        ctx.fillText(line, 40, y);\r\n        y += 20;\r\n      });\r\n      \/\/ Draw the company logo on the report. The logoImage is loaded asynchronously\r\n      \/\/ at script startup using fetch and FileReader; if it is ready, draw it. Use\r\n      \/\/ a fixed width and maintain aspect ratio based on natural dimensions.\r\n      if (logoImage && logoImage.complete && logoImage.naturalWidth) {\r\n        const logoWidth = 120;\r\n        const aspect = logoImage.naturalHeight \/ logoImage.naturalWidth;\r\n        const logoHeight = logoWidth * aspect;\r\n        ctx.drawImage(logoImage, canvas.width - logoWidth - 40, 30, logoWidth, logoHeight);\r\n      }\r\n      \/\/ Draw graph of head vs flow in the report\r\n      const chartX = 40;\r\n      const chartY = y + 20;\r\n      const chartW = canvas.width - 80;\r\n      const chartH = 250;\r\n      drawReportChart(ctx, chartX, chartY, chartW, chartH);\r\n      \/\/ Return canvas\r\n      return canvas;\r\n    }\r\n\r\n    \/\/ When pipe type selection changes, toggle the visibility of the schedule\r\n    \/\/ and wall thickness controls.  Sanitary tubing allows the user to select\r\n    \/\/ wall thickness; schedule pipe uses the internal diameter table and hides\r\n    \/\/ wall thickness, showing schedule instead.  After toggling, recalculate.\r\n    function updatePipeType() {\r\n      const type = document.getElementById('pipeType').value;\r\n      \/\/ The wall thickness select is inside a .col div; hide or show that div\r\n      const wallDiv = document.getElementById('wallThickness').parentNode;\r\n      const schedCol = document.getElementById('scheduleCol');\r\n      if (type === 'sanitary') {\r\n        \/\/ Show wall thickness, hide schedule\r\n        wallDiv.style.display = '';\r\n        schedCol.style.display = 'none';\r\n      } else {\r\n        \/\/ Hide wall thickness, show schedule\r\n        wallDiv.style.display = 'none';\r\n        schedCol.style.display = '';\r\n      }\r\n      calculateLoss();\r\n    }\r\n\r\n    \/\/ Draw a gauge on a given context; similar to drawGauge but accepts custom center and radius\r\n    function drawGaugeOnContext(ctx, cx, cy, radius, value, maxValue) {\r\n      \/\/ Background arc\r\n      const startAngle = Math.PI;\r\n      const endAngle = 0;\r\n      ctx.lineWidth = 20;\r\n      ctx.strokeStyle = '#e0e6f0';\r\n      ctx.beginPath();\r\n      ctx.arc(cx, cy, radius, startAngle, endAngle, false);\r\n      ctx.stroke();\r\n      \/\/ Value arc\r\n      const pct = Math.min(value \/ maxValue, 1.0);\r\n      const angle = startAngle + (endAngle - startAngle) * pct;\r\n      ctx.strokeStyle = '#00a4e4';\r\n      ctx.beginPath();\r\n      ctx.arc(cx, cy, radius, startAngle, angle, false);\r\n      ctx.stroke();\r\n      \/\/ Pointer\r\n      ctx.strokeStyle = '#0f5ba7';\r\n      ctx.lineWidth = 4;\r\n      ctx.beginPath();\r\n      ctx.moveTo(cx, cy);\r\n      const px = cx + (radius - 20) * Math.cos(angle);\r\n      const py = cy + (radius - 20) * Math.sin(angle);\r\n      ctx.lineTo(px, py);\r\n      ctx.stroke();\r\n      \/\/ Label\r\n      ctx.fillStyle = '#0f5ba7';\r\n      ctx.font = 'bold 16px Segoe UI';\r\n      ctx.textAlign = 'center';\r\n      ctx.textBaseline = 'middle';\r\n      ctx.fillText(value.toFixed(2) + ' ft', cx, cy - radius - 15);\r\n    }\r\n\r\n    \/\/ Draw a head vs flow chart onto an arbitrary context for the PDF report\r\n    function drawReportChart(ctx, x, y, width, height) {\r\n      \/\/ Compute data points\r\n      const minFlow = 10;\r\n      const maxFlow = 200;\r\n      const step = 10;\r\n      const points = [];\r\n      let maxHead = 0;\r\n      for (let f = minFlow; f <= maxFlow; f += step) {\r\n        const h = computeHeadForFlow(f);\r\n        points.push({ flow: f, head: h });\r\n        if (h > maxHead) maxHead = h;\r\n      }\r\n      const padding = 40;\r\n      const w = width;\r\n      const hgt = height;\r\n      \/\/ Draw axes\r\n      ctx.strokeStyle = '#888';\r\n      ctx.lineWidth = 1;\r\n      ctx.beginPath();\r\n      ctx.moveTo(x + padding, y + padding);\r\n      ctx.lineTo(x + padding, y + hgt - padding);\r\n      ctx.lineTo(x + w - padding, y + hgt - padding);\r\n      ctx.stroke();\r\n      \/\/ Labels\r\n      ctx.fillStyle = '#555';\r\n      ctx.font = '12px Segoe UI';\r\n      ctx.fillText('Head (ft)', x + 5, y + padding - 10);\r\n      ctx.fillText('Flow (gpm)', x + w - padding - 40, y + hgt - 10);\r\n      \/\/ Plot line\r\n      ctx.strokeStyle = '#0f5ba7';\r\n      ctx.lineWidth = 2;\r\n      ctx.beginPath();\r\n      points.forEach((pt, index) => {\r\n        const px = x + padding + (pt.flow - minFlow) \/ (maxFlow - minFlow) * (w - 2 * padding);\r\n        const py = y + hgt - padding - (pt.head \/ maxHead) * (hgt - 2 * padding);\r\n        if (index === 0) ctx.moveTo(px, py);\r\n        else ctx.lineTo(px, py);\r\n      });\r\n      ctx.stroke();\r\n      \/\/ Plot points\r\n      ctx.fillStyle = '#00a4e4';\r\n      points.forEach((pt) => {\r\n        const px = x + padding + (pt.flow - minFlow) \/ (maxFlow - minFlow) * (w - 2 * padding);\r\n        const py = y + hgt - padding - (pt.head \/ maxHead) * (hgt - 2 * padding);\r\n        ctx.beginPath();\r\n        ctx.arc(px, py, 3, 0, 2 * Math.PI);\r\n        ctx.fill();\r\n      });\r\n      \/\/ Highlight current flow\/head\r\n      const currentFlow = parseFloat(document.getElementById('flow').value);\r\n      if (isFinite(currentFlow) && currentFlow >= minFlow && currentFlow <= maxFlow) {\r\n        const currentHead = computeHeadForFlow(currentFlow);\r\n        const px = x + padding + (currentFlow - minFlow) \/ (maxFlow - minFlow) * (w - 2 * padding);\r\n        const py = y + hgt - padding - (currentHead \/ maxHead) * (hgt - 2 * padding);\r\n        \/\/ vertical line\r\n        ctx.strokeStyle = '#b00020';\r\n        ctx.lineWidth = 1;\r\n        ctx.beginPath();\r\n        ctx.moveTo(px, y + hgt - padding);\r\n        ctx.lineTo(px, y + padding);\r\n        ctx.stroke();\r\n        \/\/ dot\r\n        ctx.fillStyle = '#b00020';\r\n        ctx.beginPath();\r\n        ctx.arc(px, py, 4, 0, 2 * Math.PI);\r\n        ctx.fill();\r\n      }\r\n    }\r\n\r\n    \/\/ Generate a PDF file from an image data URI (JPEG) and trigger download\r\n    function createPdfFromImage(dataUri) {\r\n      \/\/ Extract binary data from base64\r\n      const imgBase64 = dataUri.split(',')[1];\r\n      const imgBinary = atob(imgBase64);\r\n      const imgLen = imgBinary.length;\r\n      const width = 595;\r\n      const height = 842;\r\n      \/\/ Build PDF objects\r\n      const objects = [];\r\n      let pdf = '';\r\n      function addObject(content) {\r\n        const offset = pdf.length;\r\n        const id = objects.length + 1;\r\n        objects.push({ id, offset });\r\n        pdf += `${id} 0 obj\\n${content}\\nendobj\\n`;\r\n      }\r\n      \/\/ 1: Catalog\r\n      addObject('<< \/Type \/Catalog \/Pages 2 0 R >>');\r\n      \/\/ 2: Pages\r\n      addObject('<< \/Type \/Pages \/Kids [3 0 R] \/Count 1 >>');\r\n      \/\/ 3: Page\r\n      addObject(`<< \/Type \/Page \/Parent 2 0 R \/MediaBox [0 0 ${width} ${height}] \/Contents 4 0 R \/Resources << \/XObject << \/Im0 5 0 R >> \/ProcSet [\/PDF \/ImageC] >> >>`);\r\n      \/\/ 4: Contents stream referencing image (q ... Q)\r\n      const contentStream = `q\\n${width} 0 0 ${height} 0 0 cm\\n\/Im0 Do\\nQ`;\r\n      addObject(`<< \/Length ${contentStream.length} >>\\nstream\\n${contentStream}\\nendstream`);\r\n      \/\/ 5: Image XObject\r\n      \/\/ Convert binary string to octal characters (binary) inside PDF\r\n      let imgStream = '';\r\n      for (let i = 0; i < imgLen; i++) {\r\n        imgStream += String.fromCharCode(imgBinary.charCodeAt(i));\r\n      }\r\n      addObject(` \/Type \/XObject \/Subtype \/Image \/Filter \/DCTDecode \/Width ${width} \/Height ${height} \/ColorSpace \/DeviceRGB \/BitsPerComponent 8 \/Length ${imgLen} >>\\nstream\\n${imgStream}\\nendstream`);\r\n      \/\/ Build xref table; offsets measured from start of final PDF (header + objects)\r\n      const header = '%PDF-1.3\\n';\r\n      let xref = 'xref\\n0 ' + (objects.length + 1) + '\\n0000000000 65535 f \\n';\r\n      objects.forEach(obj => {\r\n        const absoluteOffset = header.length + obj.offset;\r\n        const offsetStr = String(absoluteOffset).padStart(10, '0');\r\n        xref += offsetStr + ' 00000 n \\n';\r\n      });\r\n      const startxref = header.length + pdf.length;\r\n      const trailer = `trailer\\n<< \/Size ${objects.length + 1} \/Root 1 0 R >>\\nstartxref\\n${startxref}\\n%%EOF`;\r\n      const pdfData = header + pdf + xref + trailer;\r\n      \/\/ Convert to binary and create blob\r\n      const byteArray = new Uint8Array(pdfData.length);\r\n      for (let i = 0; i < pdfData.length; i++) {\r\n        byteArray[i] = pdfData.charCodeAt(i);\r\n      }\r\n      const blob = new Blob([byteArray], { type: 'application\/pdf' });\r\n      const url = URL.createObjectURL(blob);\r\n      const a = document.createElement('a');\r\n      a.href = url;\r\n      a.download = 'friction_report.pdf';\r\n      document.body.appendChild(a);\r\n      a.click();\r\n      document.body.removeChild(a);\r\n      URL.revokeObjectURL(url);\r\n    }\r\n\r\n    \/\/ Download PDF: draw report canvas, convert to image, then to PDF\r\n    function downloadPdf() {\r\n      if (!latestResults) return;\r\n      const reportCanvas = createReportCanvas();\r\n      const dataUri = reportCanvas.toDataURL('image\/jpeg', 0.95);\r\n      createPdfFromImage(dataUri);\r\n    }\r\n\r\n    \/\/ Draw a simple chart of head vs flow (10\u2013200 gpm) and highlight current flow\r\n    function drawChart() {\r\n      const container = document.getElementById('chartContainer');\r\n      const chart = document.getElementById('chart');\r\n      container.style.display = 'block';\r\n      chart.width = container.clientWidth;\r\n      chart.height = 250;\r\n      const ctx = chart.getContext('2d');\r\n      ctx.clearRect(0, 0, chart.width, chart.height);\r\n      \/\/ Compute data points\r\n      const minFlow = 10;\r\n      const maxFlow = 200;\r\n      const step = 10;\r\n      const points = [];\r\n      let maxHead = 0;\r\n      for (let f = minFlow; f <= maxFlow; f += step) {\r\n        const h = computeHeadForFlow(f);\r\n        points.push({ flow: f, head: h });\r\n        if (h > maxHead) maxHead = h;\r\n      }\r\n      \/\/ Draw axes\r\n      const padding = 40;\r\n      const w = chart.width;\r\n      const hgt = chart.height;\r\n      ctx.strokeStyle = '#888';\r\n      ctx.lineWidth = 1;\r\n      ctx.beginPath();\r\n      ctx.moveTo(padding, padding);\r\n      ctx.lineTo(padding, hgt - padding);\r\n      ctx.lineTo(w - padding, hgt - padding);\r\n      ctx.stroke();\r\n      \/\/ Labels\r\n      ctx.fillStyle = '#555';\r\n      ctx.font = '12px Segoe UI';\r\n      ctx.fillText('Head (ft)', 10, padding - 10);\r\n      ctx.fillText('Flow (gpm)', w - padding - 40, hgt - 10);\r\n      \/\/ Plot line\r\n      ctx.strokeStyle = '#0f5ba7';\r\n      ctx.lineWidth = 2;\r\n      ctx.beginPath();\r\n      points.forEach((pt, index) => {\r\n        const x = padding + (pt.flow - minFlow) \/ (maxFlow - minFlow) * (w - 2 * padding);\r\n        const y = hgt - padding - (pt.head \/ maxHead) * (hgt - 2 * padding);\r\n        if (index === 0) ctx.moveTo(x, y);\r\n        else ctx.lineTo(x, y);\r\n      });\r\n      ctx.stroke();\r\n      \/\/ Plot points\r\n      ctx.fillStyle = '#00a4e4';\r\n      points.forEach((pt) => {\r\n        const x = padding + (pt.flow - minFlow) \/ (maxFlow - minFlow) * (w - 2 * padding);\r\n        const y = hgt - padding - (pt.head \/ maxHead) * (hgt - 2 * padding);\r\n        ctx.beginPath();\r\n        ctx.arc(x, y, 3, 0, 2 * Math.PI);\r\n        ctx.fill();\r\n      });\r\n      \/\/ Highlight current flow\/head\r\n      const currentFlow = parseFloat(document.getElementById('flow').value);\r\n      if (isFinite(currentFlow) && currentFlow >= minFlow && currentFlow <= maxFlow) {\r\n        const currentHead = computeHeadForFlow(currentFlow);\r\n        const x = padding + (currentFlow - minFlow) \/ (maxFlow - minFlow) * (w - 2 * padding);\r\n        const y = hgt - padding - (currentHead \/ maxHead) * (hgt - 2 * padding);\r\n        ctx.strokeStyle = '#b00020';\r\n        ctx.lineWidth = 1;\r\n        ctx.beginPath();\r\n        ctx.moveTo(x, hgt - padding);\r\n        ctx.lineTo(x, padding);\r\n        ctx.stroke();\r\n        ctx.fillStyle = '#b00020';\r\n        ctx.beginPath();\r\n        ctx.arc(x, y, 4, 0, 2 * Math.PI);\r\n        ctx.fill();\r\n      }\r\n    }\r\n\r\n    \/\/ Toggle the graph display\r\n    function toggleGraph() {\r\n      const container = document.getElementById('chartContainer');\r\n      const btn = document.getElementById('graphBtn');\r\n      if (container.style.display === 'none' || container.style.display === '') {\r\n        drawChart();\r\n        if (btn) btn.innerText = 'Hide graph';\r\n      } else {\r\n        container.style.display = 'none';\r\n        if (btn) btn.innerText = 'Show graph';\r\n      }\r\n    }\r\n\r\n    \/\/ Chart and graph functions removed; graph output disabled\r\n\r\n    \/\/ Set up listeners and perform initial calculation on page load\r\n    window.addEventListener('load', () => {\r\n      \/\/ Unit conversion handlers\r\n      handleUnitChange('flow', 'flowUnits');\r\n      handleUnitChange('length', 'lengthUnits');\r\n      \/\/ Fluid preset change handler\r\n      document.getElementById('fluidPreset').addEventListener('change', updateFluidPreset);\r\n      \/\/ Real\u2011time calculation: bind to inputs and selects\r\n      const ids = [\r\n        'flow', 'length', 'viscosity', 'specificGravity', 'elbow90', 'elbow45', 'ballValve', 'butterflyValve',\r\n        'teeRun', 'teeBranch', 'verticalRise', 'additionalLoss', 'elbowLong', 'checkValve', 'reducer', 'expansion',\r\n        'tubeSize', 'wallThickness', 'flowUnits', 'lengthUnits', 'fluidPreset', 'pipeType', 'scheduleNumber'\r\n      ];\r\n      ids.forEach(id => {\r\n        const elem = document.getElementById(id);\r\n        if (elem) {\r\n          elem.addEventListener('input', () => { calculateLoss(); });\r\n          elem.addEventListener('change', () => { calculateLoss(); });\r\n        }\r\n      });\r\n      \/\/ Pipe type change toggles controls\r\n      document.getElementById('pipeType').addEventListener('change', updatePipeType);\r\n      \/\/ Ensure schedule selection triggers recalculation\r\n      document.getElementById('scheduleNumber').addEventListener('change', calculateLoss);\r\n      \/\/ Perform an initial calculation\r\n      updatePipeType();\r\n      calculateLoss();\r\n    });\r\n\r\n    function swameeJain(reynolds, relativeRoughness) {\r\n      if (reynolds <= 0) return 0;\r\n      const rr = Math.max(relativeRoughness, 1e-12);\r\n      return 0.25 \/ (Math.log10(rr \/ 3.7 + 5.74 \/ Math.pow(reynolds, 0.9)) ** 2);\r\n    }\r\n\r\n    function drawGauge(value, maxValue) {\r\n      const canvas = document.getElementById('gauge');\r\n      if (!canvas.getContext) return;\r\n      const ctx = canvas.getContext('2d');\r\n      const w = canvas.width;\r\n      const h = canvas.height;\r\n      ctx.clearRect(0, 0, w, h);\r\n      \/\/ Define gauge geometry\r\n      const cx = w \/ 2;\r\n      const cy = h * 0.9;\r\n      const radius = Math.min(w, h) * 0.45;\r\n      const startAngle = Math.PI;\r\n      const endAngle = 0;\r\n      \/\/ Background arc\r\n      ctx.beginPath();\r\n      ctx.arc(cx, cy, radius, startAngle, endAngle, false);\r\n      ctx.lineWidth = 20;\r\n      ctx.strokeStyle = '#e0e6f0';\r\n      ctx.stroke();\r\n      \/\/ Filled arc proportionate to value\r\n      const pct = Math.min(value \/ maxValue, 1.0);\r\n      const angle = startAngle + (endAngle - startAngle) * pct;\r\n      ctx.beginPath();\r\n      ctx.arc(cx, cy, radius, startAngle, angle, false);\r\n      ctx.lineWidth = 20;\r\n      ctx.strokeStyle = '#00a4e4';\r\n      ctx.stroke();\r\n      \/\/ Pointer\r\n      const pointerAngle = startAngle + (endAngle - startAngle) * pct;\r\n      const px = cx + (radius - 20) * Math.cos(pointerAngle);\r\n      const py = cy + (radius - 20) * Math.sin(pointerAngle);\r\n      ctx.beginPath();\r\n      ctx.moveTo(cx, cy);\r\n      ctx.lineTo(px, py);\r\n      ctx.lineWidth = 4;\r\n      ctx.strokeStyle = '#0f5ba7';\r\n      ctx.stroke();\r\n      \/\/ Text value\r\n      ctx.fillStyle = '#0f5ba7';\r\n      ctx.font = 'bold 20px Segoe UI';\r\n      ctx.textAlign = 'center';\r\n      ctx.textBaseline = 'middle';\r\n      ctx.fillText(value.toFixed(2) + ' ft', cx, h * 0.5);\r\n    }\r\n\r\n    function calculateLoss() {\r\n      \/\/ Validate inputs; highlight invalid fields and display error\r\n      const allFields = [\r\n        'flow', 'length', 'viscosity', 'specificGravity',\r\n        'elbow90', 'elbow45', 'ballValve', 'butterflyValve', 'teeRun', 'teeBranch',\r\n        'verticalRise', 'additionalLoss', 'elbowLong', 'checkValve', 'reducer', 'expansion'\r\n      ];\r\n      let valid = true;\r\n      allFields.forEach((id) => {\r\n        const el = document.getElementById(id);\r\n        if (!el) return;\r\n        const val = el.value;\r\n        \/\/ Remove prior error styling\r\n        el.style.borderColor = '';\r\n        if (val === '' || isNaN(parseFloat(val))) {\r\n          valid = false;\r\n          el.style.borderColor = '#b00020';\r\n        } else {\r\n          const num = parseFloat(val);\r\n          \/\/ Negative not allowed except verticalRise and additionalLoss can be negative? We'll treat all negative as invalid\r\n          if (num < 0) {\r\n            valid = false;\r\n            el.style.borderColor = '#b00020';\r\n          }\r\n        }\r\n      });\r\n      const errDiv = document.getElementById('errorContainer');\r\n      if (!valid) {\r\n        errDiv.textContent = 'Please correct highlighted inputs.';\r\n        \/\/ Clear result\r\n        document.getElementById('result').innerHTML = '';\r\n        \/\/ Clear gauge\/graph container if exists\r\n        return;\r\n      } else {\r\n        errDiv.textContent = '';\r\n      }\r\n      const flow = parseFloat(document.getElementById('flow').value);\r\n      const flowUnits = document.getElementById('flowUnits').value;\r\n      const tubeSize = parseFloat(document.getElementById('tubeSize').value);\r\n      const length = parseFloat(document.getElementById('length').value);\r\n      const lengthUnits = document.getElementById('lengthUnits').value;\r\n      const viscosity = parseFloat(document.getElementById('viscosity').value);\r\n      const sg = parseFloat(document.getElementById('specificGravity').value);\r\n      const num90 = parseInt(document.getElementById('elbow90').value, 10);\r\n      const num45 = parseInt(document.getElementById('elbow45').value, 10);\r\n      const numBall = parseInt(document.getElementById('ballValve').value, 10);\r\n      const numButterfly = parseInt(document.getElementById('butterflyValve').value, 10);\r\n      const numTeeRun = parseInt(document.getElementById('teeRun').value, 10);\r\n      const numTeeBranch = parseInt(document.getElementById('teeBranch').value, 10);\r\n      const verticalRise = parseFloat(document.getElementById('verticalRise').value);\r\n      const additionalLoss = parseFloat(document.getElementById('additionalLoss').value);\r\n\r\n      \/\/ New fittings\r\n      const numLong = parseInt(document.getElementById('elbowLong').value, 10);\r\n      const numCheck = parseInt(document.getElementById('checkValve').value, 10);\r\n      const numReducer = parseInt(document.getElementById('reducer').value, 10);\r\n      const numExpansion = parseInt(document.getElementById('expansion').value, 10);\r\n\r\n      \/\/ Determine internal diameter based on pipe type\r\n      const pipeType = document.getElementById('pipeType').value;\r\n      let selectedWall = parseFloat(document.getElementById('wallThickness').value);\r\n      let scheduleNum = document.getElementById('scheduleNumber').value;\r\n      let dIn;\r\n      if (pipeType === 'schedule') {\r\n        \/\/ Use schedule table for ID; fallback to sanitary if not available\r\n        const schedTable = scheduleDiameters[scheduleNum] || {};\r\n        if (schedTable[String(tubeSize)] !== undefined) {\r\n          dIn = schedTable[String(tubeSize)];\r\n        } else {\r\n          \/\/ approximate with wall thickness if unknown\r\n          dIn = tubeSize - 2 * selectedWall;\r\n        }\r\n      } else {\r\n        dIn = tubeSize - 2 * selectedWall;\r\n        scheduleNum = null;\r\n      }\r\n      const dM = dIn * 0.0254;\r\n\r\n      \/\/ Convert flow to m\u00b3\/s\r\n      let flowM3s;\r\n      if (flowUnits === 'gpm') {\r\n        flowM3s = flow * 0.00378541 \/ 60.0;\r\n      } else {\r\n        flowM3s = flow \/ 1000.0 \/ 60.0; \/\/ L\/min -> m\u00b3\/s\r\n      }\r\n      const area = Math.PI * Math.pow(dM, 2) \/ 4.0;\r\n      const velocity = flowM3s \/ area;\r\n      const density = 1000.0 * sg;\r\n      const mu = viscosity * 1e-3;\r\n      const reynolds = density * velocity * dM \/ mu;\r\n      const relRoughness = absoluteRoughness \/ dM;\r\n      const f = swameeJain(reynolds, relRoughness);\r\n\r\n      \/\/ Compute equivalent length (ft)\r\n      const dFt = dIn \/ 12.0;\r\n      const equivFt = (equivFactors.elbow_90 * num90\r\n        + equivFactors.elbow_45 * num45\r\n        + equivFactors.ball_valve * numBall\r\n        + equivFactors.butterfly_valve * numButterfly\r\n        + equivFactors.tee_run * numTeeRun\r\n        + equivFactors.tee_branch * numTeeBranch\r\n        + equivFactors.elbow_long * numLong\r\n        + equivFactors.check_valve * numCheck\r\n        + equivFactors.reducer * numReducer\r\n        + equivFactors.expansion * numExpansion\r\n      ) * dFt;\r\n      \/\/ Straight length in ft\r\n      const straightFt = lengthUnits === 'ft' ? length : length * 3.28084;\r\n      const totalLengthM = (straightFt + equivFt) * 0.3048;\r\n      const headM = f * (totalLengthM \/ dM) * (velocity * velocity) \/ (2.0 * 9.80665);\r\n      let headFt = headM * 3.28084;\r\n      \/\/ Add vertical rise and extra losses\r\n      headFt += verticalRise;\r\n      headFt += additionalLoss * 2.31 \/ sg;\r\n      const pressureDrop = headFt * 0.433 * sg;\r\n      \/\/ Compute velocity in ft\/s for advisory message\r\n      const velocityFt = velocity * 3.28084;\r\n      let velocityAdvice = '';\r\n      if (velocityFt < 1.0) {\r\n        velocityAdvice = '<p style=\"color:#b00020;\">Velocity is below 1&nbsp;ft\/s. Consider decreasing the pipe size to maintain adequate flow.<\/p>';\r\n      } else if (velocityFt >= 10.0) {\r\n        velocityAdvice = '<p style=\"color:#b00020;\">Velocity is 10&nbsp;ft\/s or higher. Consider increasing the pipe size to prevent vibration in the piping network.<\/p>';\r\n      }\r\n      \/\/ Display textual results\r\n      const resultDiv = document.getElementById('result');\r\n      resultDiv.innerHTML = `\r\n        <h2>Results<\/h2>\r\n        <p>Total head loss: <strong>${headFt.toFixed(3)}<\/strong> ft<\/p>\r\n        <p>Pressure drop: <strong>${pressureDrop.toFixed(3)}<\/strong> psi<\/p>\r\n        <p>Velocity: ${velocityFt.toFixed(2)} ft\/s<\/p>\r\n        <p>Reynolds number: ${reynolds.toFixed(0)}<\/p>\r\n        <p>Friction factor: ${f.toFixed(5)}<\/p>\r\n        ${velocityAdvice}\r\n        <div class=\"actions\" style=\"margin-top:1rem; display:flex; gap:0.5rem; flex-wrap:wrap;\">\r\n          <button id=\"copyBtn\" type=\"button\">Copy summary<\/button>\r\n          <button id=\"downloadBtn\" type=\"button\">Download summary<\/button>\r\n          <button id=\"pdfBtn\" type=\"button\">Download PDF<\/button>\r\n          <button id=\"graphBtn\" type=\"button\">Show graph<\/button>\r\n        <\/div>\r\n        <div id=\"chartContainer\" style=\"display:none; margin-top:1rem;\">\r\n          <canvas id=\"chart\"><\/canvas>\r\n        <\/div>\r\n      `;\r\n      \/\/ Rebind action buttons because resultDiv was replaced\r\n      const copyBtn = document.getElementById('copyBtn');\r\n      if (copyBtn) copyBtn.addEventListener('click', copySummary);\r\n      const downloadBtn = document.getElementById('downloadBtn');\r\n      if (downloadBtn) downloadBtn.addEventListener('click', downloadSummary);\r\n      const pdfBtn = document.getElementById('pdfBtn');\r\n      if (pdfBtn) pdfBtn.addEventListener('click', downloadPdf);\r\n      const graphBtn = document.getElementById('graphBtn');\r\n      if (graphBtn) graphBtn.addEventListener('click', toggleGraph);\r\n\r\n      \/\/ Build summary text for copy functionality\r\n      \/\/ Build summary depending on pipe type\r\n      let summaryLines = [];\r\n      summaryLines.push(`Flow rate: ${flow.toFixed(3)} ${flowUnits.toUpperCase()}`);\r\n      summaryLines.push(`Pipe size: ${tubeSize} in`);\r\n      if (pipeType === 'schedule') {\r\n        summaryLines.push(`Schedule: ${scheduleNum}`);\r\n      } else {\r\n        summaryLines.push(`Wall thickness: ${selectedWall.toFixed(3)} in`);\r\n      }\r\n      summaryLines.push(`Internal diameter: ${dIn.toFixed(3)} in`);\r\n      summaryLines.push(`Straight length: ${straightFt.toFixed(3)} ft`);\r\n      summaryLines.push(`Viscosity: ${viscosity} cP`);\r\n      summaryLines.push(`Specific gravity: ${sg}`);\r\n      summaryLines.push(`Total head loss: ${headFt.toFixed(3)} ft`);\r\n      summaryLines.push(`Pressure drop: ${pressureDrop.toFixed(3)} psi`);\r\n      summaryLines.push(`Velocity: ${velocityFt.toFixed(2)} ft\/s`);\r\n      latestSummary = summaryLines.join('\\n');\r\n\r\n      \/\/ Store latest results for PDF generation\r\n      latestResults = {\r\n        flow: flow,\r\n        flowUnits: flowUnits,\r\n        tubeSize: tubeSize,\r\n        wall: pipeType === 'schedule' ? null : selectedWall,\r\n        schedule: pipeType === 'schedule' ? scheduleNum : null,\r\n        internalDiameter: dIn,\r\n        straightLengthFt: straightFt,\r\n        viscosity: viscosity,\r\n        specificGravity: sg,\r\n        headFt: headFt,\r\n        pressurePsi: pressureDrop,\r\n        reynolds: reynolds,\r\n        frictionFactor: f,\r\n        pipeType: pipeType,\r\n        velocityFt: velocityFt\r\n      };\r\n    }\r\n\r\n    \/\/ ----------------------------------------------------------------------\r\n    \/\/ Wizard navigation\r\n    \/\/ Show the specified step (1, 2 or 3) and update the progress bar.\r\n    function showStep(step) {\r\n      ['step1', 'step2', 'step3'].forEach((id) => {\r\n        const el = document.getElementById(id);\r\n        if (el) el.style.display = 'none';\r\n      });\r\n      const current = document.getElementById('step' + step);\r\n      if (current) current.style.display = 'block';\r\n      const progressSteps = document.querySelectorAll('.progress-step');\r\n      progressSteps.forEach((ps, index) => {\r\n        ps.classList.toggle('active', (index + 1) === step);\r\n      });\r\n    }\r\n\r\n    \/\/ Attach event listeners for navigation buttons on DOM ready\r\n    document.addEventListener('DOMContentLoaded', () => {\r\n      const next1 = document.getElementById('next1');\r\n      if (next1) next1.addEventListener('click', () => {\r\n        showStep(2);\r\n      });\r\n      const back2 = document.getElementById('back2');\r\n      if (back2) back2.addEventListener('click', () => {\r\n        showStep(1);\r\n      });\r\n      const next2 = document.getElementById('next2');\r\n      if (next2) next2.addEventListener('click', () => {\r\n        \/\/ Compute results before moving to results step\r\n        calculateLoss();\r\n        showStep(3);\r\n      });\r\n      const back3 = document.getElementById('back3');\r\n      if (back3) back3.addEventListener('click', () => {\r\n        showStep(2);\r\n      });\r\n      \/\/ Start on step1\r\n      showStep(1);\r\n      \/\/ Theme toggle button\r\n      const themeBtn = document.getElementById('themeToggle');\r\n      if (themeBtn) themeBtn.addEventListener('click', function () {\r\n        const body = document.body;\r\n        body.classList.toggle('dark');\r\n        this.textContent = body.classList.contains('dark') ? 'Light Mode' : 'Dark Mode';\r\n      });\r\n      \/\/ Update viscosity hint when temperature changes\r\n      const tempField = document.getElementById('temperature');\r\n      if (tempField) tempField.addEventListener('input', updateViscosityHint);\r\n      \/\/ Ensure the hint is initialised on load\r\n      updateViscosityHint();\r\n    });\r\n  <\/script><\/div>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Calculateur de pertes de charge Estimez les pertes de charge et la chute de pression dans les tubes sanitaires ou les tuyaux schedule en utilisant l&#8217;\u00e9quation de Darcy-Weisbach avec le facteur de friction de Swamee-Jain. Ce calculateur est inspir\u00e9 d&#8217;un outil similaire fourni par NXT Process. Saisissez les conditions d&#8217;\u00e9coulement, les caract\u00e9ristiques du tuyau et [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-47534","page","type-page","status-publish","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.6 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Calculateur POC | NXT Process<\/title>\n<meta name=\"robots\" content=\"noindex, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Calculateur POC | NXT Process\" \/>\n<meta property=\"og:description\" content=\"Calculateur de pertes de charge Estimez les pertes de charge et la chute de pression dans les tubes sanitaires ou les tuyaux schedule en utilisant l&#8217;\u00e9quation de Darcy-Weisbach avec le facteur de friction de Swamee-Jain. Ce calculateur est inspir\u00e9 d&#8217;un outil similaire fourni par NXT Process. Saisissez les conditions d&#8217;\u00e9coulement, les caract\u00e9ristiques du tuyau et [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/staging.nxtprocess.com\/fr\/calculateur-poc\/\" \/>\n<meta property=\"og:site_name\" content=\"NXT Process\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/people\/NXT-Process\/61560023431243\/\" \/>\n<meta property=\"article:modified_time\" content=\"2025-08-20T12:37:42+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/fr\\\/calculateur-poc\\\/\",\"url\":\"https:\\\/\\\/staging.nxtprocess.com\\\/fr\\\/calculateur-poc\\\/\",\"name\":\"Calculateur POC | NXT Process\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/#website\"},\"datePublished\":\"2025-08-19T15:13:20+00:00\",\"dateModified\":\"2025-08-20T12:37:42+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/fr\\\/calculateur-poc\\\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/staging.nxtprocess.com\\\/fr\\\/calculateur-poc\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/fr\\\/calculateur-poc\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/staging.nxtprocess.com\\\/fr\\\/accueil-1\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Calculateur POC\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/#website\",\"url\":\"https:\\\/\\\/staging.nxtprocess.com\\\/\",\"name\":\"NXT Process\",\"description\":\"Optimizing your future, one liquid process at a time\",\"publisher\":{\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/staging.nxtprocess.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"fr-FR\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/#organization\",\"name\":\"Processus NXT\",\"url\":\"https:\\\/\\\/staging.nxtprocess.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"http:\\\/\\\/staging.nxtprocess.com\\\/wp-content\\\/uploads\\\/2025\\\/06\\\/Screenshot_1.png\",\"contentUrl\":\"http:\\\/\\\/staging.nxtprocess.com\\\/wp-content\\\/uploads\\\/2025\\\/06\\\/Screenshot_1.png\",\"width\":255,\"height\":186,\"caption\":\"Processus NXT\"},\"image\":{\"@id\":\"https:\\\/\\\/staging.nxtprocess.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/people\\\/NXT-Process\\\/61560023431243\\\/\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/11569305\\\/\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Calculateur POC | NXT Process","robots":{"index":"noindex","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"og_locale":"fr_FR","og_type":"article","og_title":"Calculateur POC | NXT Process","og_description":"Calculateur de pertes de charge Estimez les pertes de charge et la chute de pression dans les tubes sanitaires ou les tuyaux schedule en utilisant l&#8217;\u00e9quation de Darcy-Weisbach avec le facteur de friction de Swamee-Jain. Ce calculateur est inspir\u00e9 d&#8217;un outil similaire fourni par NXT Process. Saisissez les conditions d&#8217;\u00e9coulement, les caract\u00e9ristiques du tuyau et [&hellip;]","og_url":"https:\/\/staging.nxtprocess.com\/fr\/calculateur-poc\/","og_site_name":"NXT Process","article_publisher":"https:\/\/www.facebook.com\/people\/NXT-Process\/61560023431243\/","article_modified_time":"2025-08-20T12:37:42+00:00","twitter_card":"summary_large_image","twitter_misc":{"Dur\u00e9e de lecture estim\u00e9e":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/staging.nxtprocess.com\/fr\/calculateur-poc\/","url":"https:\/\/staging.nxtprocess.com\/fr\/calculateur-poc\/","name":"Calculateur POC | NXT Process","isPartOf":{"@id":"https:\/\/staging.nxtprocess.com\/#website"},"datePublished":"2025-08-19T15:13:20+00:00","dateModified":"2025-08-20T12:37:42+00:00","breadcrumb":{"@id":"https:\/\/staging.nxtprocess.com\/fr\/calculateur-poc\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/staging.nxtprocess.com\/fr\/calculateur-poc\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/staging.nxtprocess.com\/fr\/calculateur-poc\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/staging.nxtprocess.com\/fr\/accueil-1\/"},{"@type":"ListItem","position":2,"name":"Calculateur POC"}]},{"@type":"WebSite","@id":"https:\/\/staging.nxtprocess.com\/#website","url":"https:\/\/staging.nxtprocess.com\/","name":"NXT Process","description":"Optimizing your future, one liquid process at a time","publisher":{"@id":"https:\/\/staging.nxtprocess.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/staging.nxtprocess.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"fr-FR"},{"@type":"Organization","@id":"https:\/\/staging.nxtprocess.com\/#organization","name":"Processus NXT","url":"https:\/\/staging.nxtprocess.com\/","logo":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/staging.nxtprocess.com\/#\/schema\/logo\/image\/","url":"http:\/\/staging.nxtprocess.com\/wp-content\/uploads\/2025\/06\/Screenshot_1.png","contentUrl":"http:\/\/staging.nxtprocess.com\/wp-content\/uploads\/2025\/06\/Screenshot_1.png","width":255,"height":186,"caption":"Processus NXT"},"image":{"@id":"https:\/\/staging.nxtprocess.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/people\/NXT-Process\/61560023431243\/","https:\/\/www.linkedin.com\/company\/11569305\/"]}]}},"_links":{"self":[{"href":"https:\/\/staging.nxtprocess.com\/fr\/wp-json\/wp\/v2\/pages\/47534","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/staging.nxtprocess.com\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/staging.nxtprocess.com\/fr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/staging.nxtprocess.com\/fr\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/staging.nxtprocess.com\/fr\/wp-json\/wp\/v2\/comments?post=47534"}],"version-history":[{"count":0,"href":"https:\/\/staging.nxtprocess.com\/fr\/wp-json\/wp\/v2\/pages\/47534\/revisions"}],"wp:attachment":[{"href":"https:\/\/staging.nxtprocess.com\/fr\/wp-json\/wp\/v2\/media?parent=47534"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}