Spaces:
Running
Running
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Hydroponic Profile Generator (HPG)</title> | |
| <style> | |
| * { | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Arial', sans-serif; | |
| background: #c3c3c3; | |
| margin: 0; | |
| padding: 0; | |
| color: #202020; | |
| } | |
| .container { | |
| max-width: 720px; | |
| margin: 14px auto; | |
| background: #d6d6d6; | |
| border: 1px solid #9c9c9c; | |
| box-shadow: inset 0 1px 0 rgba(255,255,255,0.6); | |
| } | |
| .header { | |
| background: linear-gradient(to bottom, #e6e6e6, #cccccc); | |
| padding: 8px 12px; | |
| border-bottom: 1px solid #8f8f8f; | |
| font-weight: bold; | |
| text-shadow: 0 1px 0 #f4f4f4; | |
| } | |
| .tab-bar { | |
| display: flex; | |
| background: #dcdcdc; | |
| border-bottom: 1px solid #ccc; | |
| } | |
| .tab { | |
| padding: 4px 12px; | |
| cursor: pointer; | |
| border-right: 1px solid #999; | |
| font-size: 13px; | |
| color: #1f1f1f; | |
| user-select: none; | |
| } | |
| .tab:hover { | |
| background: #e8e8e8; | |
| } | |
| .tab.active { | |
| background: #f0f0f0; | |
| border-bottom: 0.5px solid #f0f0f0; | |
| margin-bottom: -0.5px; | |
| font-weight: bold; | |
| } | |
| .tab-content { | |
| display: none; | |
| padding: 8px 12px; | |
| background: #d3d3d3; | |
| min-height: 625px; | |
| max-height: 625px; | |
| overflow-y: auto; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| .section-title { | |
| font-weight: bold; | |
| margin: 8px 0 6px 0; | |
| padding: 2px 0; | |
| color: #8B0000; | |
| font-size: 13px; | |
| text-shadow: 0 1px 0 #f0f0f0; | |
| } | |
| .input-group { | |
| display: flex; | |
| align-items: center; | |
| margin: 3px 0; | |
| } | |
| .input-group label { | |
| min-width: 35px; | |
| font-weight: bold; | |
| font-size: 11px; | |
| margin-right: 3px; | |
| } | |
| input[type="number"] { | |
| width: 52px; | |
| padding: 2px 3px; | |
| border: 1px solid #7c7c7c; | |
| border-radius: 2px; | |
| font-size: 11px; | |
| text-align: right; | |
| font-family: Arial, sans-serif; | |
| background: #e9e9e9; | |
| box-shadow: inset 0 1px 0 rgba(255,255,255,0.6); | |
| } | |
| input[type="number"]:focus { | |
| outline: 1px solid #4a90e2; | |
| border-color: #4a90e2; | |
| } | |
| input[type="number"].highlight-green { | |
| background-color: #d5efd0 ; | |
| } | |
| input[type="number"].highlight-blue { | |
| background: #d7e5fe; | |
| } | |
| input[type="number"].wide { | |
| width: 90px; | |
| } | |
| input[type="number"].narrow { | |
| width: 60px; | |
| } | |
| .macro-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 11px; | |
| margin: 4px 0; | |
| } | |
| .macro-table th, | |
| .macro-table td { | |
| padding: 2px 3px; | |
| } | |
| .macro-table th:not(:first-child), | |
| .macro-table td:not(:first-child) { | |
| width: 56px; | |
| } | |
| .macro-table th:first-child, | |
| .macro-table td:first-child { | |
| width: 30px; | |
| } | |
| .macro-table input[type="number"] { | |
| width: 50px; | |
| } | |
| .macro-summary { | |
| width: auto ; | |
| } | |
| .salts-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 11px; | |
| background: #d6d6d6; | |
| margin: 8px 0; | |
| } | |
| .salts-table td, | |
| .salts-table th { | |
| padding: 2px 4px; | |
| } | |
| .salts-table td:first-child { | |
| width: 200px; | |
| } | |
| .salts-table input[type="number"] { | |
| width: 52px; | |
| } | |
| .salts-table .checkbox-cell { | |
| width: 40px; | |
| } | |
| .salts-table input[readonly] { | |
| width: 54px; | |
| } | |
| .matrix-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 8px 0; | |
| font-size: 11px; | |
| background: #d6d6d6; | |
| } | |
| .matrix-table th, | |
| .matrix-table td { | |
| border: 1px solid #9c9c9c; | |
| padding: 2px 4px; | |
| text-align: center; | |
| } | |
| .matrix-table th { | |
| background: linear-gradient(to bottom, #e8e8e8, #d2d2d2); | |
| font-weight: bold; | |
| padding: 3px 5px; | |
| } | |
| .matrix-table td input { | |
| width: 100%; | |
| text-align: center; | |
| padding: 1px 2px; | |
| font-size: 11px; | |
| border: 1px solid #7c7c7c; | |
| background: #e9e9e9; | |
| box-sizing: border-box; | |
| } | |
| .matrix-table .diagonal { | |
| background: #d6d6d6; | |
| font-weight: bold; | |
| } | |
| .matrix-table .highlight-green-cell { | |
| background: #d5efd0; | |
| } | |
| .matrix-table .highlight-blue-cell { | |
| background: #d7e5fe; | |
| } | |
| .salts-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin: 8px 0; | |
| font-size: 11px; | |
| } | |
| .salts-table thead th { | |
| padding: 2px 4px; | |
| background: transparent; | |
| border: none; | |
| font-weight: normal; | |
| } | |
| .salts-table td { | |
| padding: 2px 4px; | |
| border-bottom: 1px solid #c1c1c1; | |
| font-size: 11px; | |
| } | |
| .salts-table td:first-child { | |
| width: 35%; | |
| font-size: 11px; | |
| color: #111; | |
| } | |
| .salts-table td[style*="font-weight: bold"] { | |
| color: #8B0000; | |
| } | |
| .salts-table input[type="number"] { | |
| width: 100%; | |
| font-size: 11px; | |
| padding: 1px 2px; | |
| border: 1px solid #7c7c7c; | |
| background: #e9e9e9; | |
| box-sizing: border-box; | |
| } | |
| .salts-table input[readonly] { | |
| width: 100%; | |
| background: #f5f5f5; | |
| box-sizing: border-box; | |
| } | |
| .salts-table .checkbox-cell { | |
| width: 25px; | |
| text-align: center; | |
| } | |
| .salts-table label { | |
| font-weight: bold; | |
| margin-right: 2px; | |
| font-size: 10px; | |
| } | |
| .salts-table td { | |
| white-space: nowrap; | |
| } | |
| .info-panel { | |
| background: transparent; | |
| padding: 0; | |
| margin: 0; | |
| border: none; | |
| font-size: 11px; | |
| line-height: 1.45; | |
| color: #8B0000; | |
| } | |
| .info-panel .info-line { | |
| margin: 1px 0; | |
| } | |
| .bottom-panel { | |
| background: #d3d3d3; | |
| padding: 8px 10px; | |
| border-top: 1px solid #999; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-start; | |
| gap: 20px; | |
| font-size: 11px; | |
| } | |
| .bottom-panel .bottom-left { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .bottom-panel .bottom-row { | |
| display: grid; | |
| grid-template-columns: 140px 80px 130px 130px; | |
| column-gap: 8px; | |
| align-items: center; | |
| } | |
| .bottom-panel .bottom-row label { | |
| min-width: 140px; | |
| } | |
| .bottom-panel input { | |
| width: 80px; | |
| font-size: 11px; | |
| } | |
| .bottom-panel button { | |
| padding: 4px 12px; | |
| width: 130px; | |
| background: linear-gradient(to bottom, #e8e8e8, #cfcfcf); | |
| border: 1px solid #868686; | |
| cursor: pointer; | |
| border-radius: 2px; | |
| font-size: 11px; | |
| box-shadow: inset 0 1px 0 rgba(255,255,255,0.7); | |
| } | |
| .bottom-panel button:hover { | |
| background: linear-gradient(to bottom, #f0f0f0, #d4d4d4); | |
| } | |
| .bottom-panel button:active { | |
| background: linear-gradient(to bottom, #c9c9c9, #b2b2b2); | |
| } | |
| .bottom-panel label { | |
| font-size: 11px; | |
| margin-right: 3px; | |
| } | |
| .bottom-panel .weights { | |
| display: flex; | |
| gap: 8px; | |
| align-items: center; | |
| } | |
| .bottom-panel .cost-info { | |
| color: #8B0000; | |
| font-weight: bold; | |
| font-size: 16px; | |
| } | |
| .bottom-panel .bottom-right { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-end; | |
| gap: 6px; | |
| text-align: right; | |
| position: relative; | |
| min-height: 90px; | |
| } | |
| .bottom-panel .bottom-right::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 10px 0 0 0; | |
| opacity: 0.5; | |
| background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKIAAABGCAYAAABPJ0+PAAAAxXpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjabVBbDsMgDPvnFDtCXkA4Dh2dtBvs+AskndpqljBgRyYk7Z/3Kz0mCCVJrlpaKWCQJo26HRQcfTGCLHaJwsOrnqSEQSax7exXDR0PHX8BvnU75VOQPsPYrkaTyNdbUDzEs6PZ3YigFkFMbmAEdP8WlKb1/IVthyvUV5o0Kq4ybO7d71JteiPbO0y0MzIYMxdvgOfKifs0jIErudynYZxXKfpA/s3pQPoCaaFZvSULF9cAAAGDaUNDUElDQyBwcm9maWxlAAB4nH2RPUjDUBSFT1OlUioOZlBxyFCd7KIijrUKRagQaoVWHUxe+gdNWpIUF0fBteDgz2LVwcVZVwdXQRD8AXEXnBRdpMT7kkKLGB9c3sd57xzuuw8QmhWmWz1xQDdsM51MSNncqhR6RRghqmGICrNqc7Kcgu/6ukeA73cxnuV/78/Vr+UtBgQk4jirmTbxBvHMpl3jvE8sspKiEZ8TT5jUIPEj11WP3zgXXRZ4pmhm0vPEIrFU7GK1i1nJ1ImniaOablC+kPVY47zFWa/UWbtP/sJI3lhZ5jrVKJJYxBJkSFBRRxkV2IjRbpBiIU3nCR//iOuXyaWSqwxGjgVUoUNx/eB/8Hu2VmFq0kuKJIDeF8f5GANCu0Cr4Tjfx47TOgGCz8CV0fFXm8DsJ+mNjhY9Aga2gYvrjqbuAZc7wNBTTTEVVwpSCYUC8H5G35QDBm+B8Jo3t/Y5Th+ADM0qdQMcHALjRcpe93l3X/fc/r3Tnt8PVC5ymoM5BWcAAA14aVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjYxYzFlNTI4LTM4NWQtNDYzNC05MzM1LTJjOTI4NDNjODM3NyIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo5ZTczZDE0OC0zOTZlLTQ4MTctYjFlZi0wMWRmMzU5YjI1NWYiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxNjFkMDhiYS00MWMxLTQ3NDQtODhiZS1kZjg5YTdlMmNlODciCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE3NTk4MzI3NTY1MzI2NjYiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zNiIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjU6MTA6MDdUMjA6MjU6NTYrMTA6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDI1OjEwOjA3VDIwOjI1OjU2KzEwOjAwIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6NmU1MDYxYzQtZGYwYS00MzgyLWEwZTMtODMxNGFjMjcxNDcwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0iMjAyNS0xMC0wN1QyMDoyNTo1NisxMDowMCIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7Wt35cAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAEdcAABHXAFzXhzCAAAAB3RJTUUH6QoHChk4lG1UogAAB5dJREFUeNrtXduW5CAItHL6/3+ZfeptYwRBQZOJmZfZnW7jpSwKRE3pyQ8lSvv5E8+xu2A/d3iwnMXQWQdK1P3d/dzu+Sw3pd//8wBVy1Rv4L6cES1argcs0eXv5w8Asceh0IKl11nZYHyZsxLp1W6PeQMxmKMRDsIN4g3E2zwbjC/SiBEazhtAkXoxr+vWpYuAOAqY78Dl4Z1IFhsBirZeG4wPBOJd9emsiMDWiA8weU/WmFubLtCIT+v4GR47Jz9ezpT3W1nR6MVIINbeEaVPc/C9HIw7+6YGji8g8t+fMGFfCcS3re9Gt+HlpvkYnsH5j5ZpekG/crAi2Go7LkaNWNMvI3mF1gEotdQMVloJkhey46ECCtcxvSxn+c4KcGymmv58ugeqxpDeACvLWwHK6BWdrRONGrEcjHIdtRVikQYzHwBkPxGDxpXNlTeTHV/MxFAzXi/zRQ2OJzvOTLLYrGhgxK85Kr3hEoSrTJbHYOWxwv3cjBEtYRIPndgqk8u8yf89supSq+tq83itEw1HPR4BxN+A1htYA8N1aercbb17TsB0/ffdNUDWBoxM089DPkSLpbklLGREunRsjWk4BoGxM2psBuPgaiZDfd04QsemG8KnP0Zsa61LzT/ZwNWLbjktP7byNNEyA2u6/CkeKAZBjqEJdJtEC7BNJkVIo8Vo2pAIx4ZlmlSNnWtsaGlx9NbVPgD1hZIkaRPL/fDrDquJrTkWHCBazgVE4ucdoj6T3gdETzDCgd/W7OmeAMSW1uL1oR4UaLyPA8f1PX1saAV9BBBhHMgZOZg+SngIjJ9m0d+OqIGxNJckVEcXEkrsqkeLgSOfWCbUrSBFAbJvpyG84wVtL7hlvkr9ptWKEoNKLCxp2jFVzLP2KhB6TIje9qBhO5w9aD1ALMtgOiDrgGQF8SgQtYNnXWHCIAg9JgUGnSDZtRwC48GYQV/vUEqY0HS+1sue+eT1gtkHng9CqQwp0WSS/LGZPsuMgjFUZDEtI0D0YkQ9++j7cabDol/G1Th3w6x4dPtIPY3yMS/r2bCXyTV/m1m/EZpybsehBheSLtfwt9pSb5QVSK19MTNWBrze8exsQ7AmnMZbepg6DopqWmYhmbuCr9/T0rlWreh4TZvWeBvrfZg7S8tKEit6dVOeU+ixbuoN5r+T6YgmIIlxUpWAtAtWOeyjzc7xYUPupAR5W8I87mitoa90VuR6jFAIKZwbIyOOsmKk0bhjUusK1l0bWbGSB8uQH8PAt6smnRtjXRO2dsWs83FWgdBrRTi+BJzKqOWFVuKvH3UHanIOuZdCrKoPG7bkQavLsSjIHMVfZGgD/JfsTm9QkIQt7ciaJiZ9Ssqilt+gT01rtYEcmMqmSTGNqfRLniO2qo/PK4kWH0NRfZnYEAnc3rlw76J+YOvYlJxaAFcnDFOXBs7trjDkMYxz27qxjy7iVmlyhq19xsp83Lbapzs6d3GYMkD26SJNdg2fxaMHJxz1HBdemHMppb/2Gk0E0aV5eTs+A+Gblg5C0iU9/DThGBvOPl74rqyiPZplbd3V7zlMlf+ZOzsQuJMVoIo/2U4eu8M5Nu0WzZsUqOizeDY0ibPDrZvPWwrk19eaLgFthjaaD9I5rJ0vf0Zlnzuw4jGtG1uiX9pqEAsH2zEr/QDGVDDKLJiW9LXwlsMV95akVVJrTbiDbmRwy8lkq99cMK7oo05WHHNWyHmGSd+/SzJpbu78BoUWAJLEvp7Mip8hdhid0VJ4t3b+TvQFjpqTLbRH86GRvdlWz7jFpPNlxcElPgkIvWle0hbUGhBhSD2b7SRYdiDOM80wvBOTMqpYG3qoAdjj0aKzq0gttu+1QtCnt5AiFi6tn52TVtcZ0NZWjhpAyquAdD0LZ/bhwPc0Y3AApfb7GBrvgHGA+4Bc9eMvWYKy+Vf+3co8Ld2Yn0CBIPCl1M6DjDmJa/6F78HWJ9q0UZZqxQMxYuaV7gAG9DAXrtFtybSZ7chw1ejJZ4FgnKGxKOVRQhTiGIU3VXrSUojIvleklA0kerJULQ9Vpud8RFQmna72XN1wgQuYiW0PY/OWLBiMM28nxYkNcWnUb6EvFZ+rQVu3PRyKwDk/8FfY+LJh3o7z7+XfiQWw7Mjhos45/l+sGe9+Te75ulpdkup3ANsev2wZ0DTF3DbaVh5l7Rre8+/nVkuakU5QHtHz+g0cAWD83ByItmMxKKDDNHmVdeY4u2FI8UcI038boJ+waEoq9Qaov8KIXx4jUaGVRoaSfoGs/jmLoS+N+XVwJNYbswzn/iAHUPTenRjAjjMYESelAxZgeailtc8Dyqt7+RKsjlvuwiBp1mZJZNUYO0HObMVvxaiFyh7DiLaLHuXgrHSxIwrWgBpkct2ul1dey0dISKa++WjsnS1N2HZgHK/Du7uz0juHiTGJV3BTku9p0Zika+Jp7rFSE1g906N2IhelkX2CZN6COnIA6+014lUz6tVeusTUKNNufKdeP0UX77t8JzJQ2zZRWe4+oKRfse7dw0JZcApqueJ8UecsIKJwQWCYe+h6nxSGSUwnnk0N3+Ekeur6OunagEZiCVzGRbp7TAv+gbr8A6402iXlSyL4AAAAAElFTkSuQmCC'); | |
| background-repeat: no-repeat; | |
| background-position: right top; | |
| background-size: 150px auto; | |
| pointer-events: none; | |
| } | |
| .bottom-panel .version-text { | |
| color: #0b5fa4; | |
| font-weight: bold; | |
| font-size: 12px; | |
| } | |
| .profile-string { | |
| width: 100%; | |
| padding: 5px; | |
| font-family: monospace; | |
| font-size: 8px; | |
| border: 1px solid #999; | |
| background: #fff; | |
| margin: 5px 0; | |
| white-space: nowrap; | |
| overflow-x: auto; | |
| letter-spacing: -0.5px; | |
| } | |
| .help-button { | |
| background: #e8e8e8; | |
| border: 1px solid #999; | |
| padding: 3px 8px; | |
| cursor: pointer; | |
| border-radius: 2px; | |
| font-size: 11px; | |
| } | |
| .help-button:hover { | |
| background: #d8d8d8; | |
| } | |
| h3 { | |
| font-size: 13px; | |
| margin: 3px 0; | |
| color: #000; | |
| font-weight: bold; | |
| } | |
| /* Старые стили micro-inputs удалены - теперь используем абсолютное позиционирование как в легаси версии */ | |
| .concentrates-section { | |
| margin: 15px 0; | |
| } | |
| .preparation-section { | |
| margin: 0; | |
| } | |
| #preparation h3 { | |
| margin-top: 2px; | |
| margin-bottom: 3px; | |
| font-size: 13px; | |
| } | |
| .preparation-complex { | |
| margin: 2px 0; | |
| padding: 1px 0; | |
| } | |
| .prep-complex-text { | |
| font-size: 11px; | |
| color: #000; | |
| font-weight: normal; | |
| line-height: 1.2; | |
| } | |
| .preparation-header { | |
| display: flex; | |
| align-items: center; | |
| padding: 0; | |
| gap: 6px; | |
| font-weight: bold; | |
| font-size: 10px; | |
| color: #333; | |
| border-bottom: 1px solid #999; | |
| margin-bottom: 1px; | |
| height: 12px; | |
| } | |
| .prep-checkbox-header { | |
| width: 18px; | |
| text-align: center; | |
| flex-shrink: 0; | |
| font-size: 10px; | |
| font-weight: bold; | |
| } | |
| .prep-input-header { | |
| width: 54px; | |
| text-align: center; | |
| flex-shrink: 0; | |
| font-size: 10px; | |
| font-weight: bold; | |
| } | |
| .prep-value-header { | |
| width: 54px; | |
| text-align: center; | |
| flex-shrink: 0; | |
| font-size: 10px; | |
| font-weight: bold; | |
| } | |
| .prep-label-header { | |
| flex: 1; | |
| text-align: left; | |
| flex-shrink: 0; | |
| font-size: 10px; | |
| font-weight: bold; | |
| } | |
| .preparation-section h4 { | |
| color: #000; | |
| margin-bottom: 1px; | |
| margin-top: 1px; | |
| font-size: 11px; | |
| font-weight: bold; | |
| height: 12px; | |
| line-height: 1; | |
| } | |
| .preparation-list { | |
| background: #f8f9fa; | |
| border-radius: 6px; | |
| padding: 10px; | |
| margin-bottom: 15px; | |
| } | |
| .preparation-item { | |
| display: flex; | |
| align-items: center; | |
| padding: 0; | |
| gap: 6px; | |
| height: 18px; | |
| margin: 0; | |
| } | |
| .prep-label { | |
| font-weight: normal; | |
| color: #000; | |
| flex: 1; | |
| height: 14px; | |
| font-size: 10px; | |
| margin: 0; | |
| display: flex; | |
| align-items: center; | |
| flex-shrink: 0; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| line-height: 1.2; | |
| } | |
| .prep-value { | |
| font-weight: normal; | |
| color: #0000FF; | |
| background: transparent; | |
| padding: 0; | |
| border-radius: 0; | |
| font-size: 10px; | |
| width: 54px; | |
| height: 18px; | |
| text-align: right; | |
| display: flex; | |
| align-items: center; | |
| justify-content: flex-end; | |
| padding-right: 2px; | |
| box-sizing: border-box; | |
| flex-shrink: 0; | |
| line-height: 1.2; | |
| } | |
| .preparation-subgroup { | |
| margin-bottom: 4px; | |
| padding-left: 0; | |
| } | |
| .preparation-subgroup h5 { | |
| margin: 1px 0 0 0; | |
| color: #000; | |
| font-size: 10px; | |
| font-weight: bold; | |
| height: 16px; | |
| line-height: 1.2; | |
| } | |
| .prep-input { | |
| width: 54px; | |
| height: 18px; | |
| padding: 1px 2px; | |
| border: 1px solid #808080; | |
| border-radius: 0; | |
| font-size: 10px; | |
| text-align: right; | |
| box-sizing: border-box; | |
| background: #ffffff; | |
| color: #000; | |
| } | |
| .prep-checkbox { | |
| width: 18px; | |
| height: 18px; | |
| margin: 0; | |
| flex-shrink: 0; | |
| accent-color: #000; | |
| } | |
| .mixer-input { | |
| width: 240px; | |
| height: 21px; | |
| padding: 1px 2px; | |
| border: 1px solid #808080; | |
| border-radius: 0; | |
| font-size: 11px; | |
| box-sizing: border-box; | |
| background: #ffffff; | |
| color: #000; | |
| } | |
| .mixer-number { | |
| width: 68px; | |
| height: 21px; | |
| padding: 1px 2px; | |
| border: 1px solid #808080; | |
| border-radius: 0; | |
| font-size: 11px; | |
| text-align: right; | |
| box-sizing: border-box; | |
| background: #ffffff; | |
| color: #000; | |
| } | |
| .preparation-buttons { | |
| display: flex; | |
| flex-direction: row; | |
| gap: 8px; | |
| margin-top: 4px; | |
| align-items: center; | |
| } | |
| .prep-button { | |
| height: 23px; | |
| padding: 2px 8px; | |
| border: 1px solid #808080; | |
| cursor: pointer; | |
| box-sizing: border-box; | |
| font-size: 11px; | |
| white-space: nowrap; | |
| } | |
| .prep-button:hover { | |
| background: #e0e0e0; | |
| } | |
| .prep-button:active { | |
| background: #d0d0d0; | |
| } | |
| .price-section { | |
| margin: 10px 0; | |
| } | |
| .price-section h4 { | |
| color: #000; | |
| margin: 6px 0; | |
| font-size: 13px; | |
| font-weight: bold; | |
| } | |
| .price-inputs { | |
| padding: 0; | |
| margin: 0 0 10px 0; | |
| background: none; | |
| border-radius: 0; | |
| } | |
| .price-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 12px; | |
| margin: 2px 0; | |
| } | |
| .price-item input[type="number"], | |
| .price-item input[type="text"].price-input-custom { | |
| width: 70px; | |
| padding: 2px 20px 2px 4px; | |
| border: 1px solid #808080; | |
| text-align: right; | |
| font-size: 12px; | |
| box-sizing: border-box; | |
| } | |
| /* Контейнер для поля ввода с кастомными крутилками */ | |
| .price-input-wrapper { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| /* Кастомные крутилки */ | |
| .price-spinner { | |
| position: absolute; | |
| right: 1px; | |
| top: 1px; | |
| bottom: 1px; | |
| width: 16px; | |
| display: flex; | |
| flex-direction: column; | |
| background: #f0f0f0; | |
| border-left: 1px solid #808080; | |
| } | |
| .price-spinner button { | |
| flex: 1; | |
| margin: 0; | |
| padding: 0; | |
| border: none; | |
| background: #f0f0f0; | |
| cursor: pointer; | |
| font-size: 8px; | |
| line-height: 1; | |
| color: #333; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .price-spinner button:hover { | |
| background: #e0e0e0; | |
| } | |
| .price-spinner button:active { | |
| background: #d0d0d0; | |
| } | |
| .price-spinner button:first-child { | |
| border-bottom: 1px solid #808080; | |
| } | |
| .price-name { | |
| flex: 1; | |
| text-align: left; | |
| } | |
| .price-result { | |
| min-width: 130px; | |
| text-align: left; | |
| color: #000; | |
| font-weight: normal; | |
| } | |
| .price-total { | |
| margin-top: 10px; | |
| font-size: 13px; | |
| font-weight: bold; | |
| color: #a40000; | |
| } | |
| .concentrate-header { | |
| font-weight: bold; | |
| font-size: 14px; | |
| margin: 8px 0 4px 0; | |
| padding: 4px; | |
| background: #e8e8e8; | |
| } | |
| .concentrate-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| font-size: 11px; | |
| margin-bottom: 8px; | |
| } | |
| .concentrate-table th { | |
| background: #e0e0e0; | |
| padding: 3px 4px; | |
| border: 1px solid #999; | |
| font-weight: bold; | |
| font-size: 11px; | |
| } | |
| .concentrate-table td { | |
| padding: 2px 4px; | |
| border: 1px solid #ddd; | |
| } | |
| .concentrate-table input { | |
| width: 65px; | |
| font-size: 11px; | |
| padding: 1px 3px; | |
| } | |
| /* Корректор - легаси стиль с масштабированием /2 * 1.7 */ | |
| .corrector-container { | |
| position: relative; | |
| min-height: 571px; | |
| max-height: 571px; | |
| background: #f0f0f0; | |
| font-family: Arial, sans-serif; | |
| } | |
| .corrector-header { | |
| position: absolute; | |
| top: 7px; | |
| font-size: 11px; | |
| } | |
| .corrector-btn-fill { | |
| position: absolute; | |
| top: 27px; | |
| width: 71px; | |
| height: 27px; | |
| font-size: 11px; | |
| background: #e0e0e0; | |
| border: 1px solid #999; | |
| cursor: pointer; | |
| box-sizing: border-box; | |
| padding: 0 2px; | |
| } | |
| .corrector-btn-fill:hover { | |
| background: #d0d0d0; | |
| } | |
| .corrector-label { | |
| position: absolute; | |
| font-size: 15px; | |
| font-weight: bold; | |
| } | |
| .corrector-input { | |
| position: absolute; | |
| width: 71px ; | |
| height: 24px; | |
| font-size: 13px; | |
| font-family: Arial, sans-serif; | |
| text-align: right; | |
| padding: 2px 3px; | |
| border: 1px solid #999; | |
| background: white; | |
| box-sizing: border-box; | |
| } | |
| .corrector-input:read-only { | |
| background: #e8e8e8; | |
| color: #666; | |
| } | |
| .corrector-volume { | |
| position: absolute; | |
| top: 340px; | |
| width: 71px ; | |
| height: 24px; | |
| font-size: 13px; | |
| font-family: Arial, sans-serif; | |
| text-align: right; | |
| padding: 2px 3px; | |
| border: 1px solid #999; | |
| background: white; | |
| box-sizing: border-box; | |
| } | |
| .corrector-btn-calc { | |
| position: absolute; | |
| top: 372px; | |
| width: 71px; | |
| height: 27px; | |
| font-size: 11px; | |
| background: #e0e0e0; | |
| border: 1px solid #999; | |
| cursor: pointer; | |
| box-sizing: border-box; | |
| padding: 0 2px; | |
| } | |
| .corrector-btn-calc:hover { | |
| background: #d0d0d0; | |
| } | |
| .corrector-btn-journal { | |
| position: absolute; | |
| top: 401px; | |
| left: 321px; | |
| width: 71px; | |
| height: 27px; | |
| font-size: 11px; | |
| background: #e0e0e0; | |
| border: 1px solid #999; | |
| cursor: pointer; | |
| box-sizing: border-box; | |
| padding: 0 2px; | |
| } | |
| .corrector-btn-journal:hover { | |
| background: #d0d0d0; | |
| } | |
| .corrector-protocol { | |
| position: absolute; | |
| top: 27px; | |
| left: 394px; | |
| width: 297px; | |
| height: 539px; | |
| font-size: 10px; | |
| font-family: Arial, sans-serif; | |
| padding: 3px; | |
| border: 1px solid #999; | |
| background: white; | |
| overflow-y: auto; | |
| white-space: pre-wrap; | |
| line-height: 1.4; | |
| } | |
| .corrector-protocol-header { | |
| position: absolute; | |
| top: 7px; | |
| left: 442px; | |
| font-size: 11px; | |
| } | |
| .corrector-btn-help { | |
| position: absolute; | |
| top: 0px; | |
| left: 598px; | |
| width: 43px; | |
| height: 17px; | |
| font-size: 10px; | |
| background: #e0e0e0; | |
| border: 1px solid #999; | |
| cursor: pointer; | |
| } | |
| .corrector-btn-help:hover { | |
| background: #d0d0d0; | |
| } | |
| .tabs-inner { | |
| display: flex; | |
| background: #e8e8e8; | |
| border-bottom: 1px solid #ccc; | |
| margin: -5px 0 2px 0; | |
| } | |
| .tab-inner { | |
| padding: 5px 12px; | |
| cursor: pointer; | |
| border-right: 1px solid #999; | |
| background: #d8d8d8; | |
| user-select: none; | |
| font-size: 12px; | |
| border-bottom: 0.25px solid #999; | |
| position: relative; | |
| } | |
| .tab-inner:hover { | |
| background: #e0e0e0; | |
| } | |
| .tab-inner.active { | |
| background: #f0f0f0; | |
| border-bottom: 0.5px solid #f0f0f0; | |
| margin-bottom: -0.5px; | |
| z-index: 1; | |
| } | |
| .inner-tab-content { | |
| display: none; | |
| border-top: 1px solid #ccc; | |
| padding: 15px; | |
| background: #f0f0f0; | |
| } | |
| .inner-tab-content.active { | |
| display: block; | |
| } | |
| .volume-inputs { | |
| display: flex; | |
| gap: 8px; | |
| align-items: center; | |
| margin: 8px 0; | |
| } | |
| .volume-inputs label { | |
| font-weight: bold; | |
| font-size: 11px; | |
| } | |
| .volume-inputs input { | |
| width: 80px; | |
| font-size: 11px; | |
| } | |
| .volume-inputs span { | |
| font-size: 11px; | |
| } | |
| /* ========== МОБИЛЬНАЯ ВЕРСИЯ (полностью отдельная) ========== */ | |
| /* Скрываем десктопную версию на мобильных */ | |
| @media only screen and (max-width: 768px) { | |
| .container { | |
| display: none; | |
| } | |
| } | |
| /* Показываем мобильную версию только на мобильных */ | |
| .mobile-version { | |
| display: none; | |
| } | |
| @media only screen and (max-width: 768px) { | |
| .mobile-version { | |
| display: block; | |
| padding: 10px; | |
| background: #f0f0f0; | |
| font-family: Arial, sans-serif; | |
| } | |
| .mobile-header { | |
| background: #4a90e2; | |
| color: white; | |
| padding: 15px; | |
| text-align: center; | |
| font-size: 18px; | |
| font-weight: bold; | |
| margin: -10px -10px 15px -10px; | |
| } | |
| .mobile-notice { | |
| background: #fff3cd; | |
| border: 1px solid #ffc107; | |
| padding: 15px; | |
| border-radius: 5px; | |
| margin-bottom: 15px; | |
| } | |
| .mobile-notice h3 { | |
| margin: 0 0 10px 0; | |
| color: #856404; | |
| } | |
| .mobile-notice p { | |
| margin: 5px 0; | |
| line-height: 1.6; | |
| } | |
| .mobile-link { | |
| display: inline-block; | |
| background: #4a90e2; | |
| color: white; | |
| padding: 12px 20px; | |
| text-decoration: none; | |
| border-radius: 5px; | |
| margin-top: 10px; | |
| } | |
| .mobile-features { | |
| background: white; | |
| padding: 15px; | |
| border-radius: 5px; | |
| margin-top: 15px; | |
| } | |
| .mobile-features h4 { | |
| margin-top: 0; | |
| } | |
| .mobile-features ul { | |
| padding-left: 20px; | |
| } | |
| .mobile-features li { | |
| margin: 8px 0; | |
| } | |
| .mobile-input-group { | |
| margin-bottom: 12px; | |
| } | |
| .mobile-input-group label { | |
| display: block; | |
| margin-bottom: 5px; | |
| font-weight: bold; | |
| color: #333; | |
| } | |
| .mobile-input-group input { | |
| width: 100%; | |
| padding: 10px; | |
| font-size: 16px; | |
| border: 2px solid #ddd; | |
| border-radius: 5px; | |
| box-sizing: border-box; | |
| } | |
| .mobile-input-group input:focus { | |
| outline: none; | |
| border-color: #4a90e2; | |
| } | |
| /* Спойлеры (аккордеоны) */ | |
| .mobile-accordion { | |
| background: white; | |
| border-radius: 8px; | |
| margin-bottom: 10px; | |
| overflow: hidden; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .mobile-accordion-header { | |
| padding: 15px; | |
| background: linear-gradient(to right, #4a90e2, #357abd); | |
| color: white; | |
| cursor: pointer; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-weight: bold; | |
| font-size: 16px; | |
| user-select: none; | |
| } | |
| .mobile-accordion-header:active { | |
| background: linear-gradient(to right, #357abd, #2868a8); | |
| } | |
| .mobile-accordion-icon { | |
| transition: transform 0.3s; | |
| font-size: 20px; | |
| } | |
| .mobile-accordion-icon.open { | |
| transform: rotate(180deg); | |
| } | |
| .mobile-accordion-content { | |
| max-height: 0; | |
| overflow: hidden; | |
| transition: max-height 0.3s ease-out; | |
| padding: 0 15px; | |
| } | |
| .mobile-accordion-content.open { | |
| max-height: 2000px; | |
| padding: 15px; | |
| } | |
| /* Компактные поля для соотношений */ | |
| .ratio-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 8px; | |
| margin-top: 10px; | |
| } | |
| .ratio-item { | |
| padding: 8px; | |
| background: #f8f9fa; | |
| border-radius: 5px; | |
| text-align: center; | |
| font-size: 13px; | |
| } | |
| .ratio-item.optimal { | |
| background: #d4edda; | |
| border: 2px solid #28a745; | |
| font-weight: bold; | |
| } | |
| .ratio-label { | |
| font-size: 11px; | |
| color: #666; | |
| margin-bottom: 3px; | |
| } | |
| .ratio-value { | |
| font-size: 15px; | |
| font-weight: bold; | |
| color: #333; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header">Hydroponic Profile Generator (HPG)</div> | |
| <div class="tab-bar"> | |
| <div class="tab active" onclick="switchTab('macro', this)">Макро</div> | |
| <div class="tab" onclick="switchTab('micro', this)">Микро</div> | |
| <div class="tab" onclick="switchTab('concentrates', this)">Концентраты</div> | |
| <div class="tab" onclick="switchTab('corrector', this)">Корректор</div> | |
| <div class="tab" onclick="switchTab('file', this)">Файл</div> | |
| <div class="tab" onclick="switchTab('help', this)">Справка</div> | |
| </div> | |
| <!-- МАКРО --> | |
| <div id="macro" class="tab-content active"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;"> | |
| <h3>Макропрофиль в мг/л (ppm)</h3> | |
| <button class="help-button" onclick="showMacroHelp()">help</button> | |
| </div> | |
| <div class="section-title">Расчет макропрофиля и навесок солей</div> | |
| <table style="width: 100%; border-collapse: collapse; font-size: 11px; margin: 5px 0;"> | |
| <tr> | |
| <td style="width: 45px; font-weight: bold; text-align: right; padding: 2px 5px 2px 0;"></td> | |
| <td style="width: 75px; font-weight: bold; text-align: center; padding: 2px;">N</td> | |
| <td style="width: 75px; font-weight: bold; text-align: center; padding: 2px;">P</td> | |
| <td style="width: 75px; font-weight: bold; text-align: center; padding: 2px;">K</td> | |
| <td style="width: 75px; font-weight: bold; text-align: center; padding: 2px;">Ca</td> | |
| <td style="width: 75px; font-weight: bold; text-align: center; padding: 2px;">Mg</td> | |
| <td style="width: 75px; font-weight: bold; text-align: center; padding: 2px;">S</td> | |
| <td style="width: 75px; font-weight: bold; text-align: center; padding: 2px;">Cl</td> | |
| <td style="width: 75px; font-weight: bold; text-align: center; padding: 2px;">EC</td> | |
| </tr> | |
| <tr> | |
| <td></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="N" value="220.000" step="1" style="width: 68px;" oninput="onNChange()"></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="P" value="40.000" step="1" class="highlight-green" style="width: 68px;" oninput="onPChange()"></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="K" value="180.000" step="1" style="width: 68px;" oninput="onMacroChange()"></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="Ca" value="200.000" step="1" style="width: 68px;" oninput="onMacroChange()"></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="Mg" value="50.000" step="1" style="width: 68px;" oninput="onMacroChange()"></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="S" value="73.049" step="1" style="width: 68px;" oninput="onSChange()"></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="Cl" value="0.00" step="1" class="highlight-green" style="width: 68px;" oninput="onClChange()"></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="EC" value="2.102" step="0.1" class="highlight-green" style="width: 68px;" oninput="onECChange()"></td> | |
| </tr> | |
| <tr> | |
| <td style="font-weight: bold; text-align: right; padding: 2px 5px 2px 0;">NO3</td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="NO3" value="200.00" step="1" style="width: 68px;" oninput="onNO3Change()"></td> | |
| <td colspan="2" style="padding: 2px 0 2px 10px;"><span id="nh4no3-info">NH4:NO3 1:10</span></td> | |
| <td rowspan="2" colspan="5" style="text-align: center; vertical-align: middle; padding: 5px;"> | |
| <div style="display: inline-block; text-align: left; font-size: 11px; color: #8B0000;"> | |
| <div id="ratio-info" style="margin: 2px 0;">N:1 : 0.18 : 0.82 : 0.91 : 0.23 : 0.33 : 0 sPPM=763.05</div> | |
| <div id="npk-info" style="margin: 2px 0;">NPK: 22-9-22 CaO=28% MgO=8.3% SO3=18.2%</div> | |
| </div> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td style="font-weight: bold; text-align: right; padding: 2px 5px 2px 0;">NH4</td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="NH4" value="20.00" step="1" style="width: 68px;" oninput="onNH4Change()"></td> | |
| <td style="text-align: center; padding: 2px;"><input type="number" id="NH4_ratio" value="0.100" step="0.001" class="highlight-green" style="width: 68px;" oninput="onNH4RatioChange()"></td> | |
| <td></td> | |
| </tr> | |
| </table> | |
| <div class="section-title">Матрица соотношений элементов</div> | |
| <table class="matrix-table"> | |
| <thead> | |
| <tr> | |
| <th></th> | |
| <th>N</th> | |
| <th>P</th> | |
| <th>K</th> | |
| <th>Ca</th> | |
| <th>Mg</th> | |
| <th>S</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <th>N</th> | |
| <td class="diagonal">1</td> | |
| <td><input type="number" id="m_P_N" value="5.500" step="0.001" oninput="onMatrixChange('P','N')"></td> | |
| <td><input type="number" id="m_K_N" value="1.222" step="0.001" class="highlight-green" oninput="onMatrixChange('K','N')"></td> | |
| <td><input type="number" id="m_Ca_N" value="1.100" step="0.001" oninput="onMatrixChange('Ca','N')"></td> | |
| <td><input type="number" id="m_Mg_N" value="4.400" step="0.001" oninput="onMatrixChange('Mg','N')"></td> | |
| <td><input type="number" id="m_S_N" value="3.012" step="0.001" oninput="onMatrixChange('S','N')"></td> | |
| </tr> | |
| <tr> | |
| <th>P</th> | |
| <td><input type="number" id="m_N_P" value="0.182" step="0.001" oninput="onMatrixChange('N','P')"></td> | |
| <td class="diagonal">1</td> | |
| <td><input type="number" id="m_K_P" value="0.222" step="0.001" oninput="onMatrixChange('K','P')"></td> | |
| <td><input type="number" id="m_Ca_P" value="0.200" step="0.001" class="highlight-blue" oninput="onMatrixChange('P','Ca')"></td> | |
| <td><input type="number" id="m_Mg_P" value="0.800" step="0.001" oninput="onMatrixChange('Mg','P')"></td> | |
| <td><input type="number" id="m_S_P" value="0.548" step="0.001" oninput="onMatrixChange('S','P')"></td> | |
| </tr> | |
| <tr> | |
| <th>K</th> | |
| <td><input type="number" id="m_N_K" value="0.818" step="0.001" oninput="onMatrixChange('N','K')"></td> | |
| <td><input type="number" id="m_P_K" value="4.500" step="0.001" oninput="onMatrixChange('P','K')"></td> | |
| <td class="diagonal">1</td> | |
| <td><input type="number" id="m_Ca_K" value="0.900" step="0.001" oninput="onMatrixChange('Ca','K')"></td> | |
| <td><input type="number" id="m_Mg_K" value="3.600" step="0.001" oninput="onMatrixChange('Mg','K')"></td> | |
| <td><input type="number" id="m_S_K" value="2.464" step="0.001" oninput="onMatrixChange('S','K')"></td> | |
| </tr> | |
| <tr> | |
| <th>Ca</th> | |
| <td><input type="number" id="m_N_Ca" value="0.909" step="0.001" oninput="onMatrixChange('N','Ca')"></td> | |
| <td><input type="number" id="m_P_Ca" value="5.000" step="0.001" oninput="onMatrixChange('P','Ca')"></td> | |
| <td><input type="number" id="m_K_Ca" value="1.111" step="0.001" class="highlight-green" oninput="onMatrixChange('K','Ca')"></td> | |
| <td class="diagonal">1</td> | |
| <td><input type="number" id="m_Mg_Ca" value="4.000" step="0.001" oninput="onMatrixChange('Mg','Ca')"></td> | |
| <td><input type="number" id="m_S_Ca" value="2.738" step="0.001" oninput="onMatrixChange('S','Ca')"></td> | |
| </tr> | |
| <tr> | |
| <th>Mg</th> | |
| <td><input type="number" id="m_N_Mg" value="0.227" step="0.001" oninput="onMatrixChange('N','Mg')"></td> | |
| <td><input type="number" id="m_P_Mg" value="1.250" step="0.001" oninput="onMatrixChange('P','Mg')"></td> | |
| <td><input type="number" id="m_K_Mg" value="0.278" step="0.001" class="highlight-green" oninput="onMatrixChange('K','Mg')"></td> | |
| <td><input type="number" id="m_Ca_Mg" value="0.250" step="0.001" oninput="onMatrixChange('Ca','Mg')"></td> | |
| <td class="diagonal">1</td> | |
| <td><input type="number" id="m_S_Mg" value="0.684" step="0.001" oninput="onMatrixChange('S','Mg')"></td> | |
| </tr> | |
| <tr> | |
| <th>S</th> | |
| <td><input type="number" id="m_N_S" value="0.332" step="0.001" oninput="onMatrixChange('N','S')"></td> | |
| <td><input type="number" id="m_P_S" value="1.826" step="0.001" oninput="onMatrixChange('P','S')"></td> | |
| <td><input type="number" id="m_K_S" value="0.406" step="0.001" oninput="onMatrixChange('K','S')"></td> | |
| <td><input type="number" id="m_Ca_S" value="0.365" step="0.001" oninput="onMatrixChange('Ca','S')"></td> | |
| <td><input type="number" id="m_Mg_S" value="1.461" step="0.001" oninput="onMatrixChange('Mg','S')"></td> | |
| <td class="diagonal">1</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div class="section-title">Составы солей</div> | |
| <table class="salts-table"> | |
| <thead> | |
| <tr style="border-bottom: 1px solid #999;"> | |
| <th style="text-align: left; font-weight: normal;"></th> | |
| <th colspan="2" style="text-align: center; font-weight: normal; font-size: 11px; color: #8B0000;">%</th> | |
| <th colspan="2" style="text-align: center; font-weight: normal; font-size: 11px; color: #8B0000;">%</th> | |
| <th colspan="2" style="text-align: center; font-weight: normal; font-size: 11px; color: #8B0000;">%</th> | |
| <th style="text-align: center; font-weight: normal; font-size: 11px; color: #8B0000;">граммы</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><span id="label_CaNO3">Кальций азотнокислый Ca(NO3)2*4H2O</span></td> | |
| <td style="width: 30px; font-weight: bold;">Ca</td> | |
| <td style="width: 70px;"><input type="number" id="salt_CaNO3_Ca" value="16.972" step="0.001" oninput="onSaltCompositionChange('CaNO3','Ca')"></td> | |
| <td style="width: 40px; font-weight: bold;">NO3</td> | |
| <td style="width: 70px;"><input type="number" id="salt_CaNO3_NO3" value="11.863" step="0.001" oninput="onSaltCompositionChange('CaNO3','NO3')"></td> | |
| <td style="width: 40px; font-weight: bold;">NH4</td> | |
| <td style="width: 70px;"><input type="number" id="salt_CaNO3_NH4" value="0.000" step="0.001" oninput="onSaltCompositionChange('CaNO3','NH4')"></td> | |
| <td style="width: 70px;"><input type="number" id="g_CaNO3" value="11.78" step="0.01" oninput="onSaltWeightChange()"></td> | |
| </tr> | |
| <tr> | |
| <td><span id="label_KNO3">Калий азотнокислый KNO3</span></td> | |
| <td style="font-weight: bold;">K</td> | |
| <td><input type="number" id="salt_KNO3_K" value="38.672" step="0.001" oninput="onSaltCompositionChange('KNO3','K')"></td> | |
| <td style="font-weight: bold;">NO3</td> | |
| <td><input type="number" id="salt_KNO3_NO3" value="13.854" step="0.001" oninput="onSaltCompositionChange('KNO3','NO3')"></td> | |
| <td></td> | |
| <td></td> | |
| <td><input type="number" id="g_KNO3" value="2.90" step="0.01" oninput="onSaltWeightChange()"></td> | |
| </tr> | |
| <tr> | |
| <td><span id="label_NH4NO3">Аммоний азотнокислый NH4NO3</span></td> | |
| <td style="font-weight: bold;">NH4</td> | |
| <td><input type="number" id="salt_NH4NO3_NH4" value="17.499" step="0.001" oninput="onSaltCompositionChange('NH4NO3','NH4')"></td> | |
| <td style="font-weight: bold;">NO3</td> | |
| <td><input type="number" id="salt_NH4NO3_NO3" value="17.499" step="0.001" oninput="onSaltCompositionChange('NH4NO3','NO3')"></td> | |
| <td></td> | |
| <td></td> | |
| <td><input type="number" id="g_NH4NO3" value="1.14" step="0.01" oninput="onSaltWeightChange()"></td> | |
| </tr> | |
| <tr> | |
| <td><span id="label_MgSO4">Магний сернокислый MgSO4*7H2O</span></td> | |
| <td style="font-weight: bold;">Mg</td> | |
| <td><input type="number" id="salt_MgSO4_Mg" value="9.861" step="0.001" oninput="onSaltCompositionChange('MgSO4','Mg')"></td> | |
| <td style="font-weight: bold;">S</td> | |
| <td><input type="number" id="salt_MgSO4_S" value="13.010" step="0.001" oninput="onSaltCompositionChange('MgSO4','S')"></td> | |
| <td></td> | |
| <td></td> | |
| <td><input type="number" id="g_MgSO4" value="5.07" step="0.01" oninput="onSaltWeightChange()"></td> | |
| </tr> | |
| <tr> | |
| <td><span id="label_KH2PO4">Калий фосфорнокислый KH2PO4</span></td> | |
| <td style="font-weight: bold;">K</td> | |
| <td><input type="number" id="salt_KH2PO4_K" value="28.731" step="0.001" oninput="onSaltCompositionChange('KH2PO4','K')"></td> | |
| <td style="font-weight: bold;">P</td> | |
| <td><input type="number" id="salt_KH2PO4_P" value="22.761" step="0.001" oninput="onSaltCompositionChange('KH2PO4','P')"></td> | |
| <td></td> | |
| <td></td> | |
| <td><input type="number" id="g_KH2PO4" value="1.76" step="0.01" oninput="onSaltWeightChange()"></td> | |
| </tr> | |
| <tr> | |
| <td><span id="label_K2SO4">Калий сернокислый K2SO4</span></td> | |
| <td style="font-weight: bold;">K</td> | |
| <td><input type="number" id="salt_K2SO4_K" value="44.874" step="0.001" oninput="onSaltCompositionChange('K2SO4','K')"></td> | |
| <td style="font-weight: bold;">S</td> | |
| <td><input type="number" id="salt_K2SO4_S" value="18.401" step="0.001" oninput="onSaltCompositionChange('K2SO4','S')"></td> | |
| <td></td> | |
| <td class="checkbox-cell"><input type="checkbox" id="use_K2SO4" checked onchange="onSaltSelectionChange('K2SO4')"></td> | |
| <td><input type="number" id="g_K2SO4" value="0.38" step="0.01" oninput="onSaltWeightChange()"></td> | |
| </tr> | |
| <tr> | |
| <td><span id="label_MgNO3">Магний азотнокислый Mg(NO3)2*6H2O</span></td> | |
| <td style="font-weight: bold;">Mg</td> | |
| <td><input type="number" id="salt_MgNO3_Mg" value="9.479" step="0.001" oninput="onSaltCompositionChange('MgNO3','Mg')"></td> | |
| <td style="font-weight: bold;">NO3</td> | |
| <td><input type="number" id="salt_MgNO3_NO3" value="10.925" step="0.001" oninput="onSaltCompositionChange('MgNO3','NO3')"></td> | |
| <td></td> | |
| <td class="checkbox-cell"><input type="checkbox" id="use_MgNO3" onchange="onSaltSelectionChange('MgNO3')"></td> | |
| <td><input type="number" id="g_MgNO3" value="0.00" step="0.01" oninput="onSaltWeightChange()"></td> | |
| </tr> | |
| <tr> | |
| <td><span id="label_CaCl2">Хлорид кальция 6-водный CaCl2*6H2O</span></td> | |
| <td style="font-weight: bold;">Ca</td> | |
| <td><input type="number" id="salt_CaCl2_Ca" value="18.294" step="0.001" oninput="onSaltCompositionChange('CaCl2','Ca')"></td> | |
| <td style="font-weight: bold;">Cl</td> | |
| <td><input type="number" id="salt_CaCl2_Cl" value="32.366" step="0.001" oninput="onSaltCompositionChange('CaCl2','Cl')"></td> | |
| <td></td> | |
| <td></td> | |
| <td><input type="number" id="g_CaCl2" value="0.00" step="0.01" oninput="onSaltWeightChange()"></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <!-- МИКРО --> | |
| <div id="micro" class="tab-content"> | |
| <!-- Верхняя строка с заголовками --> | |
| <div style="position: relative; height: 30px; margin-bottom: 10px;"> | |
| <span style="position: absolute; left: 4px; top: 2px; font-size: 15px;">Микропрофиль в мкг/л</span> | |
| <span style="position: absolute; left: 360px; top: 2px; font-size: 15px; font-weight: bold;">Расчет навесок микроэлементов</span> | |
| <button class="help-button" onclick="showMicroHelp()" style="position: absolute; right: 3px; top: 0; width: 43px; height: 21px; font-size: 11px;">help</button> | |
| </div> | |
| <!-- Метки микроэлементов --> | |
| <div style="position: relative; height: 20px; margin-top: 10px;"> | |
| <label style="position: absolute; left: 25px; top: 0; font-weight: bold;">Fe</label> | |
| <label style="position: absolute; left: 95px; top: 0; font-weight: bold;">Mn</label> | |
| <label style="position: absolute; left: 170px; top: 0; font-weight: bold;">B</label> | |
| <label style="position: absolute; left: 245px; top: 0; font-weight: bold;">Zn</label> | |
| <label style="position: absolute; left: 315px; top: 0; font-weight: bold;">Cu</label> | |
| <label style="position: absolute; left: 390px; top: 0; font-weight: bold;">Mo</label> | |
| <label style="position: absolute; left: 460px; top: 0; font-weight: bold;">Co</label> | |
| <label style="position: absolute; left: 535px; top: 0; font-weight: bold;">Si</label> | |
| </div> | |
| <!-- Поля ввода микроэлементов --> | |
| <div style="position: relative; height: 35px; margin-bottom: 15px;"> | |
| <input type="number" id="Fe" value="2000" step="1" oninput="onMicroChange()" | |
| style="position: absolute; left: 10px; top: 0; width: 70px; height: 27px;"> | |
| <input type="number" id="Mn" value="550" step="1" oninput="onMicroChange()" | |
| style="position: absolute; left: 82px; top: 0; width: 70px; height: 27px;"> | |
| <input type="number" id="B" value="500" step="1" oninput="onMicroChange()" | |
| style="position: absolute; left: 154px; top: 0; width: 70px; height: 27px;"> | |
| <input type="number" id="Zn" value="330" step="1" oninput="onMicroChange()" | |
| style="position: absolute; left: 226px; top: 0; width: 70px; height: 27px;"> | |
| <input type="number" id="Cu" value="63" step="1" oninput="onMicroChange()" | |
| style="position: absolute; left: 298px; top: 0; width: 70px; height: 27px;"> | |
| <input type="number" id="Mo" value="63" step="1" oninput="onMicroChange()" | |
| style="position: absolute; left: 370px; top: 0; width: 70px; height: 27px;"> | |
| <input type="number" id="Co" value="0" step="1" oninput="onMicroChange()" | |
| style="position: absolute; left: 442px; top: 0; width: 70px; height: 27px;"> | |
| <input type="number" id="Si" value="0" step="1" oninput="onMicroChange()" | |
| style="position: absolute; left: 514px; top: 0; width: 70px; height: 27px;"> | |
| </div> | |
| <!-- Составы солей --> | |
| <div id="salts-header" style="position: relative; height: 30px; margin-top: 10px;"> | |
| <span style="position: absolute; left: 8px; top: 0; font-size: 15px;">Составы солей</span> | |
| <span style="position: absolute; left: 125px; top: 0; font-size: 15px;">доля (%)</span> | |
| <span style="position: absolute; left: 215px; top: 0; font-size: 15px;">граммы</span> | |
| </div> | |
| <!-- Таблица солей с абсолютным позиционированием --> | |
| <div id="salts-table" style="position: relative;"> | |
| <!-- Железо --> | |
| <div style="position: relative; height: 28px;"> | |
| <label id="micro_Fe_label" style="position: absolute; left: 8px; top: 5px; font-size: 14px;">Железо Fe=20.1%</label> | |
| <input type="number" id="micro_Fe_percent" value="20.1000" step="0.0001" oninput="onMicroCompositionChange()" | |
| style="position: absolute; left: 120px; top: 2px; width: 80px; height: 23px;"> | |
| <input type="number" id="g_Fe" value="0.09950" step="0.00001" readonly | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px;"> | |
| </div> | |
| <!-- Марганец --> | |
| <div style="position: relative; height: 28px;"> | |
| <label id="micro_Mn_label" style="position: absolute; left: 8px; top: 5px; font-size: 14px;">Марганец Mn=36.4%</label> | |
| <input type="number" id="micro_Mn_percent" value="36.4000" step="0.0001" oninput="onMicroCompositionChange()" | |
| style="position: absolute; left: 120px; top: 2px; width: 80px; height: 23px;"> | |
| <input type="number" id="g_Mn" value="0.01511" step="0.00001" readonly | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px;"> | |
| </div> | |
| <!-- Бор --> | |
| <div style="position: relative; height: 28px;"> | |
| <label id="micro_B_label" style="position: absolute; left: 8px; top: 5px; font-size: 14px;">Бор B=17.5%</label> | |
| <input type="number" id="micro_B_percent" value="17.5000" step="0.0001" oninput="onMicroCompositionChange()" | |
| style="position: absolute; left: 120px; top: 2px; width: 80px; height: 23px;"> | |
| <input type="number" id="g_B" value="0.02857" step="0.00001" readonly | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px;"> | |
| </div> | |
| <!-- Цинк --> | |
| <div style="position: relative; height: 28px;"> | |
| <label id="micro_Zn_label" style="position: absolute; left: 8px; top: 5px; font-size: 14px;">Цинк Zn=22.7%</label> | |
| <input type="number" id="micro_Zn_percent" value="22.7000" step="0.0001" oninput="onMicroCompositionChange()" | |
| style="position: absolute; left: 120px; top: 2px; width: 80px; height: 23px;"> | |
| <input type="number" id="g_Zn" value="0.01454" step="0.00001" readonly | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px;"> | |
| </div> | |
| <!-- Медь --> | |
| <div style="position: relative; height: 28px;"> | |
| <label id="micro_Cu_label" style="position: absolute; left: 8px; top: 5px; font-size: 14px;">Медь Cu=25.5%</label> | |
| <input type="number" id="micro_Cu_percent" value="25.5000" step="0.0001" oninput="onMicroCompositionChange()" | |
| style="position: absolute; left: 120px; top: 2px; width: 80px; height: 23px;"> | |
| <input type="number" id="g_Cu" value="0.00247" step="0.00001" readonly | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px;"> | |
| </div> | |
| <!-- Молибден --> | |
| <div style="position: relative; height: 28px;"> | |
| <label id="micro_Mo_label" style="position: absolute; left: 8px; top: 5px; font-size: 14px;">Молибден Mo=54.3%</label> | |
| <input type="number" id="micro_Mo_percent" value="54.3000" step="0.0001" oninput="onMicroCompositionChange()" | |
| style="position: absolute; left: 120px; top: 2px; width: 80px; height: 23px;"> | |
| <input type="number" id="g_Mo" value="0.00116" step="0.00001" readonly | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px;"> | |
| </div> | |
| <!-- Кобальт --> | |
| <div style="position: relative; height: 28px;"> | |
| <label id="micro_Co_label" style="position: absolute; left: 8px; top: 5px; font-size: 14px;">Кобальт Co=13.0%</label> | |
| <input type="number" id="micro_Co_percent" value="13.0000" step="0.0001" oninput="onMicroCompositionChange()" | |
| style="position: absolute; left: 120px; top: 2px; width: 80px; height: 23px;"> | |
| <input type="number" id="g_Co" value="0.00000" step="0.00001" readonly | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px;"> | |
| </div> | |
| <!-- Кремний --> | |
| <div style="position: relative; height: 28px;"> | |
| <label id="micro_Si_label" style="position: absolute; left: 8px; top: 5px; font-size: 14px;">Кремний Si=7.0%</label> | |
| <input type="number" id="micro_Si_percent" value="7.0000" step="0.0001" oninput="onMicroCompositionChange()" | |
| style="position: absolute; left: 120px; top: 2px; width: 80px; height: 23px;"> | |
| <input type="number" id="g_Si" value="0.00000" step="0.00001" readonly | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px;"> | |
| </div> | |
| </div> | |
| <!-- Комплекс по бору --> | |
| <div style="position: relative; height: 30px; margin-top: 5px;"> | |
| <input type="checkbox" id="use_complex" onchange="onComplexChange()" | |
| style="position: absolute; left: 8px; top: 6px;"> | |
| <label for="use_complex" style="position: absolute; left: 28px; top: 5px; font-size: 14px;">Комплекс по бору</label> | |
| <span id="total_micro_text" style="position: absolute; left: 205px; top: 5px; font-size: 14px; width: 80px; text-align: right;">0.16135</span> | |
| <input type="number" id="total_micro_grams" value="0.16135" step="0.00001" onchange="onTotalMicroGramsChange()" | |
| style="position: absolute; left: 205px; top: 2px; width: 80px; height: 23px; display: none;"> | |
| </div> | |
| <!-- Информация о составе --> | |
| <div style="position: relative; height: 25px; margin-top: 5px;"> | |
| <div id="micro-composition" style="position: absolute; left: 8px; top: 0; width: 670px; | |
| background: white; border: 1px solid #999; padding: 2px 4px; font-size: 13px;"> | |
| Состав: Fe=12,395% Mn=3,409% B=3,099% Zn=2,045% Cu=0,39% Mo=0,39% Co=0% Si=0% | |
| </div> | |
| </div> | |
| <!-- Заголовок разведения микроэлементов --> | |
| <div style="position: relative; height: 25px; margin-top: 20px;"> | |
| <span style="position: absolute; left: 120px; top: 0; font-size: 15px; font-weight: bold;"> | |
| Разведение микроэлементов в воде (жидкий концентрат микроэлементов) | |
| </span> | |
| </div> | |
| <!-- Объем --> | |
| <div style="position: relative; height: 55px; margin-top: 5px;"> | |
| <label style="position: absolute; left: 8px; top: 3px; font-size: 15px;">Объем до (мл):</label> | |
| <input type="number" id="micro_volume" value="500" step="1" oninput="onMicroVolumeChange()" | |
| style="position: absolute; left: 8px; top: 22px; width: 85px; height: 23px;"> | |
| <div id="micro-dilution-info" style="position: absolute; left: 100px; top: 22px; width: 480px; | |
| border: none; border-style: none; padding: 2px 0; font-size: 13px;"> | |
| Концентрация: 0,32 г/л, Кратность: 20:1, Расход: 50 мл/л раствора | |
| </div> | |
| </div> | |
| </div> | |
| <!-- КОНЦЕНТРАТЫ --> | |
| <div id="concentrates" class="tab-content"> | |
| <div class="tabs-inner"> | |
| <div class="tab-inner active" onclick="switchInnerTab('calc', this)">Расчет</div> | |
| <div class="tab-inner" onclick="switchInnerTab('preparation', this)">Изготовление</div> | |
| <div class="tab-inner" onclick="switchInnerTab('price', this)">Цена</div> | |
| </div> | |
| <div id="calc" class="inner-tab-content active"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;"> | |
| <h3 style="margin: 0;">Расчет монорастворов и концентратов</h3> | |
| <button class="help-button" onclick="showCalcHelp()">help</button> | |
| </div> | |
| <div class="concentrate-header"> | |
| Раствор A | |
| <span id="sumA" style="margin-left: 20px; font-weight: normal; font-size: 11px;">Объем: 42,63 мл, вес: 42,63 гр, плотность: 1 г/мл.</span> | |
| </div> | |
| <table class="concentrate-table"> | |
| <thead> | |
| <tr> | |
| <th></th> | |
| <th>г/л</th> | |
| <th>г/мл</th> | |
| <th>мл</th> | |
| <th>гр. жидк.</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td>Кальций азотнокислый Ca(NO3)2*4H2O</td> | |
| <td><input type="number" id="conc_glCaNO3" value="600.0" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlCaNO3" value="1.0000" step="0.0001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_mlCaNO3" value="19.63" step="0.01" readonly></td> | |
| <td><input type="number" id="conc_ggCaNO3" value="19.63" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Калий азотнокислый KNO3</td> | |
| <td><input type="number" id="conc_glKNO3" value="250.0" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlKNO3" value="1.0000" step="0.0001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_mlKNO3" value="11.60" step="0.01" readonly></td> | |
| <td><input type="number" id="conc_ggKNO3" value="11.60" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Аммоний азотнокислый NH4NO3</td> | |
| <td><input type="number" id="conc_glNH4NO3" value="100.0" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlNH4NO3" value="1.0000" step="0.0001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_mlNH4NO3" value="11.40" step="0.01" readonly></td> | |
| <td><input type="number" id="conc_ggNH4NO3" value="11.40" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Магний азотнокислый Mg(NO3)2*6H2O</td> | |
| <td><input type="number" id="conc_glMgNO3" value="500.0" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlMgNO3" value="1.0000" step="0.0001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_mlMgNO3" value="0.00" step="0.01" readonly></td> | |
| <td><input type="number" id="conc_ggMgNO3" value="0.00" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Хлорид кальция 6-водный CaCl2*6H2O</td> | |
| <td><input type="number" id="conc_glCaCl2" value="100.0" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlCaCl2" value="1.0000" step="0.0001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_mlCaCl2" value="0.00" step="0.01" readonly></td> | |
| <td><input type="number" id="conc_ggCaCl2" value="0.00" step="0.01" readonly></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div class="concentrate-header"> | |
| Раствор B | |
| <span id="sumB" style="margin-left: 20px; font-weight: normal; font-size: 11px;">Объем: 40,1 мл, вес: 39,87 гр, плотность: 0,99 г/мл</span> | |
| </div> | |
| <table class="concentrate-table"> | |
| <thead> | |
| <tr> | |
| <th></th> | |
| <th>г/л</th> | |
| <th>г/мл</th> | |
| <th>мл</th> | |
| <th>гр. жидк.</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td>Магний сернокислый MgSO4*7H2O</td> | |
| <td><input type="number" id="conc_glMgSO4" value="600.0" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlMgSO4" value="1.0000" step="0.0001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="8.45" step="0.01" readonly></td> | |
| <td><input type="number" value="8.45" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Калий фосфорнокислый KH2PO4</td> | |
| <td><input type="number" id="conc_glKH2PO4" value="150.0" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlKH2PO4" value="1.0000" step="0.0001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="11.73" step="0.01" readonly></td> | |
| <td><input type="number" value="11.73" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Калий сернокислый K2SO4</td> | |
| <td><input type="number" id="conc_glK2SO4" value="100.0" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlK2SO4" value="1.0000" step="0.0001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="3.80" step="0.01" readonly></td> | |
| <td><input type="number" value="3.80" step="0.01" readonly></td> | |
| </tr> | |
| <tr style="height: 10px;"><td colspan="5"></td></tr> | |
| <tr> | |
| <td>Железо Fe=20,1%</td> | |
| <td><input type="number" id="conc_glFe" value="10.00" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlFe" value="1.000" step="0.001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="9.95" step="0.01" readonly></td> | |
| <td><input type="number" value="9.95" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Марганец Mn=36,4%</td> | |
| <td><input type="number" id="conc_glMn" value="10.00" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlMn" value="1.000" step="0.001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="1.51" step="0.01" readonly></td> | |
| <td><input type="number" value="1.51" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Бор B=17,5%</td> | |
| <td><input type="number" id="conc_glB" value="10.00" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlB" value="1.000" step="0.001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="2.86" step="0.01" readonly></td> | |
| <td><input type="number" value="2.86" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Цинк Zn=22,7%</td> | |
| <td><input type="number" id="conc_glZn" value="10.00" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlZn" value="1.000" step="0.001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="1.45" step="0.01" readonly></td> | |
| <td><input type="number" value="1.45" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Медь Cu=25,5%</td> | |
| <td><input type="number" id="conc_glCu" value="10.00" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlCu" value="1.000" step="0.001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="0.25" step="0.01" readonly></td> | |
| <td><input type="number" value="0.25" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Молибден Mo=54,3%</td> | |
| <td><input type="number" id="conc_glMo" value="10.00" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlMo" value="1.000" step="0.001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="0.12" step="0.01" readonly></td> | |
| <td><input type="number" value="0.12" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Кобальт Co=13%</td> | |
| <td><input type="number" id="conc_glCo" value="10.00" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlCo" value="1.000" step="0.001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="0.00" step="0.01" readonly></td> | |
| <td><input type="number" value="0.00" step="0.01" readonly></td> | |
| </tr> | |
| <tr> | |
| <td>Кремний Si=7%</td> | |
| <td><input type="number" id="conc_glSi" value="10.00" step="0.1" oninput="calcConcentrates()"></td> | |
| <td><input type="number" id="conc_gmlSi" value="1.000" step="0.001" oninput="calcConcentrates()"></td> | |
| <td><input type="number" value="0.00" step="0.01" readonly></td> | |
| <td><input type="number" value="0.00" step="0.01" readonly></td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div id="preparation" class="inner-tab-content"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;"> | |
| <h3 style="margin: 0;">Изготовление</h3> | |
| <button class="help-button" onclick="showPreparationHelp()">help</button> | |
| </div> | |
| <!-- Макроэлементы --> | |
| <div class="preparation-section"> | |
| <h4>Макроэлементы</h4> | |
| <!-- Заголовки колонок --> | |
| <div class="preparation-header"> | |
| <div class="prep-input-header">Помпа</div> | |
| <div class="prep-checkbox-header">Ручн.</div> | |
| <div class="prep-value-header">жидк (гр.)</div> | |
| <div class="prep-label-header">Описание</div> | |
| </div> | |
| <!-- Раствор A --> | |
| <div class="preparation-subgroup"> | |
| <h5>Раствор A</h5> | |
| <div class="preparation-item"> | |
| <input type="text" id="mCaNO3" value="p1" class="prep-input"> | |
| <input type="checkbox" id="cbCaNO3" class="prep-checkbox"> | |
| <input type="text" id="g2gCaNO3" value="0.00" class="prep-input"> | |
| <label for="k2nCaNO3" class="prep-label">Кальций азотнокислый 4-водный (ч.)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mKNO3" value="p2" class="prep-input"> | |
| <input type="checkbox" id="cbKNO3" class="prep-checkbox"> | |
| <input type="text" id="g2gKNO3" value="0.00" class="prep-input"> | |
| <label for="k2nKNO3" class="prep-label">Калий азотнокислый (ч.)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mNH4NO3" value="p3" class="prep-input"> | |
| <input type="checkbox" id="cbNH4NO3" class="prep-checkbox"> | |
| <input type="text" id="g2gNH4NO3" value="0.00" class="prep-input"> | |
| <label for="k2nNH4NO3" class="prep-label">Аммоний азотнокислый (ч.)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mMgNO3" value="" class="prep-input"> | |
| <input type="checkbox" id="cbMgNO3" class="prep-checkbox"> | |
| <input type="text" id="g2gMgNO3" value="0.00" class="prep-input"> | |
| <label for="k2nMgNO3" class="prep-label">Магний азотнокислый 6-водный (ч.)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mCaCl2" value="" class="prep-input"> | |
| <input type="checkbox" id="cbCaCl2" class="prep-checkbox"> | |
| <input type="text" id="g2gCaCl2" value="0.00" class="prep-input"> | |
| <label for="k2nCaCl2" class="prep-label">Хлорид кальция 6-водный (ч.)</label> | |
| </div> | |
| </div> | |
| <!-- Раствор B --> | |
| <div class="preparation-subgroup"> | |
| <h5>Раствор B</h5> | |
| <div class="preparation-item"> | |
| <input type="text" id="mMgSO4" value="p4" class="prep-input"> | |
| <input type="checkbox" id="cbMgSO4" class="prep-checkbox"> | |
| <input type="text" id="g2gMgSO4" value="0.00" class="prep-input"> | |
| <label for="k2nMgSO4" class="prep-label">Магний сернокислый 7-водный (ч.)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mKH2PO4" value="p5" class="prep-input"> | |
| <input type="checkbox" id="cbKH2PO4" class="prep-checkbox"> | |
| <input type="text" id="g2gKH2PO4" value="0.00" class="prep-input"> | |
| <label for="k2nKH2PO4" class="prep-label">Калий фосфорнокислый 1-замещенный (ч.)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mK2SO4" value="p6" class="prep-input"> | |
| <input type="checkbox" id="cbK2SO4" class="prep-checkbox"> | |
| <input type="text" id="g2gK2SO4" value="0.00" class="prep-input"> | |
| <label for="k2nK2SO4" class="prep-label">Калий сернокислый безводный (ч.)</label> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Микроэлементы --> | |
| <div class="preparation-section"> | |
| <h4>Микроэлементы</h4> | |
| <!-- Заголовки колонок --> | |
| <div class="preparation-header"> | |
| <div class="prep-input-header">Помпа</div> | |
| <div class="prep-checkbox-header">Ручн.</div> | |
| <div class="prep-value-header">жидк (гр.)</div> | |
| <div class="prep-label-header">Описание</div> | |
| </div> | |
| <!-- Комплекс микроэлементов --> | |
| <div class="preparation-complex"> | |
| <span class="prep-complex-text">Комплекс микроэлементов 2285.520 г. (10 л)</span> | |
| </div> | |
| <!-- Отдельные микроэлементы --> | |
| <div class="preparation-item"> | |
| <input type="text" id="mFe" value="" class="prep-input"> | |
| <input type="checkbox" id="cbFe" class="prep-checkbox"> | |
| <input type="text" id="g2gFe" value="0.00" class="prep-input"> | |
| <label for="l2Fe" class="prep-label">Железо (%)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mMn" value="" class="prep-input"> | |
| <input type="checkbox" id="cbMn" class="prep-checkbox"> | |
| <input type="text" id="g2gMn" value="0.00" class="prep-input"> | |
| <label for="l2Mn" class="prep-label">Марганец (%)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mB" value="" class="prep-input"> | |
| <input type="checkbox" id="cbB" class="prep-checkbox"> | |
| <input type="text" id="g2gB" value="0.00" class="prep-input"> | |
| <label for="l2B" class="prep-label">Бор (%)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mZn" value="" class="prep-input"> | |
| <input type="checkbox" id="cbZn" class="prep-checkbox"> | |
| <input type="text" id="g2gZn" value="0.00" class="prep-input"> | |
| <label for="l2Zn" class="prep-label">Цинк (%)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mCu" value="" class="prep-input"> | |
| <input type="checkbox" id="cbCu" class="prep-checkbox"> | |
| <input type="text" id="g2gCu" value="0.00" class="prep-input"> | |
| <label for="l2Cu" class="prep-label">Медь (%)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mMo" value="" class="prep-input"> | |
| <input type="checkbox" id="cbMo" class="prep-checkbox"> | |
| <input type="text" id="g2gMo" value="0.00" class="prep-input"> | |
| <label for="l2Mo" class="prep-label">Молибден (%)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mCo" value="" class="prep-input"> | |
| <input type="checkbox" id="cbCo" class="prep-checkbox"> | |
| <input type="text" id="g2gCo" value="0.00" class="prep-input"> | |
| <label for="l2Co" class="prep-label">Кобальт (%)</label> | |
| </div> | |
| <div class="preparation-item"> | |
| <input type="text" id="mSi" value="" class="prep-input"> | |
| <input type="checkbox" id="cbSi" class="prep-checkbox"> | |
| <input type="text" id="g2gSi" value="0.00" class="prep-input"> | |
| <label for="l2Si" class="prep-label">Кремний (%)</label> | |
| </div> | |
| </div> | |
| <!-- Настройки миксера и кнопки управления --> | |
| <div class="preparation-section"> | |
| <h4>Настройки миксера</h4> | |
| <div style="display: flex; align-items: center; gap: 8px;"> | |
| <label for="addrMixer" style="font-size: 11px;">Адрес:</label> | |
| <input type="text" id="addrMixer" value="mixer.local" class="prep-input mixer-input"> | |
| <label for="nmix" style="font-size: 11px;">Номер:</label> | |
| <input type="number" id="nmix" value="1" class="prep-input mixer-number"> | |
| <button id="btnManufacture" class="prep-button">Изготовить</button> | |
| <button id="btnToProfile" class="prep-button">Перевести в профиль</button> | |
| <button id="btnToJournal" class="prep-button">Внести в журнал</button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="price" class="inner-tab-content"> | |
| <h3>Цена</h3> | |
| <div class="price-section"> | |
| <h4>Цена за 1 грамм</h4> | |
| <div class="price-inputs"> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_CaNO3" class="price-input-custom" value="0.4000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_CaNO3" data-delta="0.0001">▲</button> | |
| <button data-input="price_CaNO3" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Кальций азотнокислый 4-водный (ч.)</span> | |
| <span class="price-result" id="price_result_CaNO3">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_KNO3" class="price-input-custom" value="0.3500" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_KNO3" data-delta="0.0001">▲</button> | |
| <button data-input="price_KNO3" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Калий азотнокислый (ч.)</span> | |
| <span class="price-result" id="price_result_KNO3">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_NH4NO3" class="price-input-custom" value="0.2500" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_NH4NO3" data-delta="0.0001">▲</button> | |
| <button data-input="price_NH4NO3" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Аммоний азотнокислый (ч.)</span> | |
| <span class="price-result" id="price_result_NH4NO3">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_MgNO3" class="price-input-custom" value="0.3500" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_MgNO3" data-delta="0.0001">▲</button> | |
| <button data-input="price_MgNO3" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Магний азотнокислый 6-водный (ч.)</span> | |
| <span class="price-result" id="price_result_MgNO3">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_MgSO4" class="price-input-custom" value="0.1200" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_MgSO4" data-delta="0.0001">▲</button> | |
| <button data-input="price_MgSO4" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Магний сернокислый 7-водный (ч.)</span> | |
| <span class="price-result" id="price_result_MgSO4">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_KH2PO4" class="price-input-custom" value="0.4000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_KH2PO4" data-delta="0.0001">▲</button> | |
| <button data-input="price_KH2PO4" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Калий фосфорнокислый 1-замещенный (ч.)</span> | |
| <span class="price-result" id="price_result_KH2PO4">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_K2SO4" class="price-input-custom" value="0.3200" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_K2SO4" data-delta="0.0001">▲</button> | |
| <button data-input="price_K2SO4" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Калий сернокислый безводный (ч.)</span> | |
| <span class="price-result" id="price_result_K2SO4">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_CaCl2" class="price-input-custom" value="7.7000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_CaCl2" data-delta="0.0001">▲</button> | |
| <button data-input="price_CaCl2" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Хлорид кальция 6-водный (ч.)</span> | |
| <span class="price-result" id="price_result_CaCl2">0.00 г. цена: 0.00</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="price-section"> | |
| <h4>Микроэлементы</h4> | |
| <div class="price-inputs"> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_Fe" class="price-input-custom" value="3.0000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_Fe" data-delta="0.0001">▲</button> | |
| <button data-input="price_Fe" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Железо (%)</span> | |
| <span class="price-result" id="price_result_Fe">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_Mn" class="price-input-custom" value="0.3000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_Mn" data-delta="0.0001">▲</button> | |
| <button data-input="price_Mn" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Марганец (%)</span> | |
| <span class="price-result" id="price_result_Mn">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_B" class="price-input-custom" value="0.4000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_B" data-delta="0.0001">▲</button> | |
| <button data-input="price_B" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Бор (%)</span> | |
| <span class="price-result" id="price_result_B">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_Zn" class="price-input-custom" value="0.1500" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_Zn" data-delta="0.0001">▲</button> | |
| <button data-input="price_Zn" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Цинк (%)</span> | |
| <span class="price-result" id="price_result_Zn">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_Cu" class="price-input-custom" value="0.4000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_Cu" data-delta="0.0001">▲</button> | |
| <button data-input="price_Cu" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Медь (%)</span> | |
| <span class="price-result" id="price_result_Cu">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_Mo" class="price-input-custom" value="3.0000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_Mo" data-delta="0.0001">▲</button> | |
| <button data-input="price_Mo" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Молибден (%)</span> | |
| <span class="price-result" id="price_result_Mo">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_Co" class="price-input-custom" value="2.3000" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_Co" data-delta="0.0001">▲</button> | |
| <button data-input="price_Co" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Кобальт (%)</span> | |
| <span class="price-result" id="price_result_Co">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_Si" class="price-input-custom" value="0.2700" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_Si" data-delta="0.0001">▲</button> | |
| <button data-input="price_Si" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Кремний (%)</span> | |
| <span class="price-result" id="price_result_Si">0.00 г. цена: 0.00</span> | |
| </div> | |
| <div class="price-item" style="display: none;"> | |
| <div class="price-input-wrapper"> | |
| <input type="text" id="price_Cmplx" class="price-input-custom" value="0.0500" oninput="updatePriceResults()"> | |
| <div class="price-spinner"> | |
| <button data-input="price_Cmplx" data-delta="0.0001">▲</button> | |
| <button data-input="price_Cmplx" data-delta="-0.0001">▼</button> | |
| </div> | |
| </div> | |
| <span class="price-name">Комплекс микроэлементов</span> | |
| <span class="price-result" id="price_result_Cmplx">0.00 г. цена: 0.00</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="price-total"> | |
| Стоимость: <span id="total_price">0.00</span> за 1 литр: <span id="price_per_liter">0.00</span> | |
| </div> | |
| </div> | |
| <!-- Общий блок для всех подвкладок Концентратов --> | |
| <div class="volume-inputs"> | |
| <label>Тара A (мл)</label> | |
| <input type="number" id="tank_A" value="500" step="1" oninput="calcConcentrates()"> | |
| <span id="lVolA">Концентрат A (20:1) . Долить воды: 457мл. По 50 мл на 1л.</span> | |
| </div> | |
| <div class="volume-inputs"> | |
| <label>Тара B (мл)</label> | |
| <input type="number" id="tank_B" value="500" step="1" oninput="calcConcentrates()"> | |
| <span id="lVolB">Концентрат B (20:1) . Долить воды: 460мл. По 50 мл на 1л.</span> | |
| </div> | |
| </div> | |
| <!-- КОРРЕКТОР --> | |
| <div id="corrector" class="tab-content"> | |
| <div class="corrector-container"> | |
| <!-- Заголовки колонок --> | |
| <div class="corrector-header" style="left: 48px;">Исходный</div> | |
| <div class="corrector-header" style="left: 136px;">Текущий</div> | |
| <div class="corrector-header" style="left: 223px;">Корректирующий</div> | |
| <div class="corrector-header" style="left: 321px;">Итоговый</div> | |
| <div class="corrector-protocol-header">Протокол коррекции</div> | |
| <!-- Кнопки "Заполнить" --> | |
| <button class="corrector-btn-fill" style="left: 48px;" onclick="fillColumn('initial')">Заполнить</button> | |
| <button class="corrector-btn-fill" style="left: 223px;" onclick="fillColumn('correcting')">Заполнить</button> | |
| <button class="corrector-btn-fill" style="left: 321px;" onclick="fillColumn('final')">Заполнить</button> | |
| <!-- Метки элементов слева --> | |
| <label class="corrector-label" style="top: 68px; left: 20px;">N</label> | |
| <label class="corrector-label" style="top: 95px; left: 3px;">NO3</label> | |
| <label class="corrector-label" style="top: 121px; left: 3px;">NH4</label> | |
| <label class="corrector-label" style="top: 146px; left: 20px;">P</label> | |
| <label class="corrector-label" style="top: 173px; left: 20px;">K</label> | |
| <label class="corrector-label" style="top: 199px; left: 14px;">Ca</label> | |
| <label class="corrector-label" style="top: 226px; left: 14px;">Mg</label> | |
| <label class="corrector-label" style="top: 253px; left: 22px;">S</label> | |
| <label class="corrector-label" style="top: 279px; left: 19px;">Cl</label> | |
| <label class="corrector-label" style="top: 306px; left: 15px;">EC</label> | |
| <label class="corrector-label" style="top: 343px; left: 68px;">Объем</label> | |
| <!-- Колонка 1: Исходный (Left=48) --> | |
| <input type="number" class="corrector-input" style="top: 63px; left: 48px;" id="corr_init_N" value="220.00" step="0.01" readonly> | |
| <input type="number" class="corrector-input" style="top: 90px; left: 48px;" id="corr_init_NO3" value="200.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 116px; left: 48px;" id="corr_init_NH4" value="20.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 143px; left: 48px;" id="corr_init_P" value="40.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 168px; left: 48px;" id="corr_init_K" value="180.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 196px; left: 48px;" id="corr_init_Ca" value="200.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 221px; left: 48px;" id="corr_init_Mg" value="50.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 248px; left: 48px;" id="corr_init_S" value="73.049" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 274px; left: 48px;" id="corr_init_Cl" value="0.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 301px; left: 48px;" id="corr_init_EC" value="2.102" step="0.001" readonly> | |
| <input type="number" class="corrector-volume" style="left: 136px;" id="corr_init_vol" value="10.0" step="0.1" oninput="onCorrectorChange()"> | |
| <button class="corrector-btn-calc" style="left: 48px;" onclick="calcCorrection('initial')">В расчет</button> | |
| <!-- Колонка 2: Текущий (Left=136) --> | |
| <input type="number" class="corrector-input" style="top: 63px; left: 136px;" id="corr_curr_N" value="220.000" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 90px; left: 136px;" id="corr_curr_NO3" value="200.00" step="0.01" readonly> | |
| <input type="number" class="corrector-input" style="top: 116px; left: 136px;" id="corr_curr_NH4" value="20.00" step="0.01" readonly> | |
| <input type="number" class="corrector-input" style="top: 143px; left: 136px;" id="corr_curr_P" value="40.000" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 168px; left: 136px;" id="corr_curr_K" value="180.000" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 196px; left: 136px;" id="corr_curr_Ca" value="200.000" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 221px; left: 136px;" id="corr_curr_Mg" value="50.000" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 248px; left: 136px;" id="corr_curr_S" value="73.049" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 274px; left: 136px;" id="corr_curr_Cl" value="0.00" step="0.01" readonly> | |
| <input type="number" class="corrector-input" style="top: 301px; left: 136px;" id="corr_curr_EC" value="2.102" step="0.001" oninput="onCorrectorChange()"> | |
| <button class="corrector-btn-calc" style="left: 136px;" onclick="calcCorrection('current')">В расчет</button> | |
| <!-- Колонка 3: Корректирующий (Left=223) --> | |
| <input type="number" class="corrector-input" style="top: 63px; left: 223px;" id="corr_corr_N" value="220.000" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 90px; left: 223px;" id="corr_corr_NO3" value="200.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 116px; left: 223px;" id="corr_corr_NH4" value="20.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 143px; left: 223px;" id="corr_corr_P" value="40.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 168px; left: 223px;" id="corr_corr_K" value="180.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 196px; left: 223px;" id="corr_corr_Ca" value="200.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 221px; left: 223px;" id="corr_corr_Mg" value="50.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 248px; left: 223px;" id="corr_corr_S" value="73.049" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 274px; left: 223px;" id="corr_corr_Cl" value="0.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 301px; left: 223px;" id="corr_corr_EC" value="2.102" step="0.001" readonly> | |
| <input type="number" class="corrector-volume" style="left: 223px;" id="corr_corr_vol" value="20.0" step="0.1" readonly> | |
| <button class="corrector-btn-calc" style="left: 223px;" onclick="calcCorrection('correcting')">В расчет</button> | |
| <!-- Колонка 4: Итоговый (Left=321) --> | |
| <input type="number" class="corrector-input" style="top: 63px; left: 321px;" id="corr_final_N" value="220.000" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 90px; left: 321px;" id="corr_final_NO3" value="200.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 116px; left: 321px;" id="corr_final_NH4" value="20.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 143px; left: 321px;" id="corr_final_P" value="40.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 168px; left: 321px;" id="corr_final_K" value="180.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 196px; left: 321px;" id="corr_final_Ca" value="200.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 221px; left: 321px;" id="corr_final_Mg" value="50.000" step="0.001" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 248px; left: 321px;" id="corr_final_S" value="73.049" step="0.001" readonly> | |
| <input type="number" class="corrector-input" style="top: 274px; left: 321px;" id="corr_final_Cl" value="0.00" step="0.01" oninput="onCorrectorChange()"> | |
| <input type="number" class="corrector-input" style="top: 301px; left: 321px;" id="corr_final_EC" value="2.102" step="0.001" readonly> | |
| <input type="number" class="corrector-volume" style="left: 321px;" id="corr_final_vol" value="30.0" step="0.1" oninput="onCorrectorChange()"> | |
| <button class="corrector-btn-calc" style="left: 321px;" onclick="calcCorrection('final')">В расчет</button> | |
| <!-- Кнопка "В журнал" --> | |
| <button class="corrector-btn-journal" onclick="saveCorrectorToJournal()">В журнал</button> | |
| <!-- Протокол коррекции --> | |
| <textarea class="corrector-protocol" id="corrector_protocol" readonly>ОСНОВНОЕ: | |
| Изменение объема на: 200% | |
| Доля старого раствора: 33% | |
| Изменение EC на: 0% | |
| Изменение N общий на: 0% | |
| ПРОФИЛЬ: | |
| Коррекция NO3 на: 0% | |
| Коррекция NH4 на: 0% | |
| Коррекция P на: 0% | |
| Коррекция K на: 0% | |
| Коррекция Ca на: 0% | |
| Коррекция Mg на: 0% | |
| Коррекция S на: 0 ppm | |
| Коррекция Cl на: 0 ppm | |
| СООТНОШЕНИЯ: | |
| NH4:NO3 до 0,1 после 0,1 | |
| K:N до 0,818 после 0,818 | |
| K:Ca до 0,9 после 0,9 | |
| K:Mg до 3,6 после 3,6</textarea> | |
| <!-- Кнопка help --> | |
| <button class="corrector-btn-help" onclick="showCorrectorHelp()">help</button> | |
| </div> | |
| </div> | |
| <!-- ФАЙЛ --> | |
| <div id="file" class="tab-content" style="position: relative; max-height: 470px; overflow: hidden; font-family: Arial; font-size: 15px;"> | |
| <!-- Кнопка help справа вверху --> | |
| <button onclick="showFileHelp()" style="position: absolute; left: 673px; top: 3px; width: 49px; height: 21px; font-size: 14px; padding: 0;">help</button> | |
| <!-- Параметры сохранения --> | |
| <div style="position: absolute; left: 8px; top: 3px; font-weight: bold; font-size: 15px;">Параметры сохранения</div> | |
| <label style="position: absolute; left: 19px; top: 36px; font-size: 15px;">Имя файла:</label> | |
| <input type="text" id="file_filename" value="default.hpg" style="position: absolute; left: 120px; top: 30px; width: 582px; height: 24px; font-family: Arial; font-size: 15px;"> | |
| <label style="position: absolute; left: 19px; top: 65px; font-size: 15px;">Описание:</label> | |
| <input type="text" id="file_comment" value="По умолчанию" style="position: absolute; left: 120px; top: 62px; width: 582px; height: 24px; font-family: Arial; font-size: 15px;"> | |
| <!-- Кнопки управления файлами --> | |
| <button onclick="fileOpen()" style="position: absolute; left: 11px; top: 95px; width: 106px; height: 30px; font-size: 15px;">Открыть</button> | |
| <button onclick="fileSaveAs()" style="position: absolute; left: 123px; top: 95px; width: 138px; height: 30px; font-size: 15px;">Сохранить как</button> | |
| <button onclick="fileLoadFertilizers()" style="position: absolute; left: 267px; top: 95px; width: 208px; height: 30px; font-size: 15px;">Подгрузить удобрения</button> | |
| <button onclick="fileLoadProfile()" style="position: absolute; left: 481px; top: 95px; width: 208px; height: 30px; font-size: 15px;">Подгрузить профиль</button> | |
| <!-- Журнал действий --> | |
| <div style="position: absolute; left: 8px; top: 145px; font-weight: bold; font-size: 15px;">Журнал действий</div> | |
| <select id="file_journal_list" size="6" style="position: absolute; left: 8px; top: 171px; width: 691px; height: 127px; font-family: Arial; font-size: 15px;"> | |
| </select> | |
| <label style="position: absolute; left: 8px; top: 306px; font-size: 15px;">Описание:</label> | |
| <label style="position: absolute; left: 134px; top: 306px; font-size: 15px;">Дата:</label> | |
| <input type="date" id="file_journal_date" value="" style="position: absolute; left: 169px; top: 301px; width: 138px; height: 24px; font-family: Arial; font-size: 15px;"> | |
| <button onclick="journalAdd()" style="position: absolute; left: 315px; top: 301px; width: 101px; height: 24px; font-size: 15px;">Добавить</button> | |
| <button onclick="journalUpdate()" style="position: absolute; left: 423px; top: 301px; width: 66px; height: 24px; font-size: 15px;">Обнов.</button> | |
| <button onclick="journalDelete()" style="position: absolute; left: 494px; top: 301px; width: 80px; height: 24px; font-size: 15px;">Удалить</button> | |
| <textarea id="file_journal_memo" style="position: absolute; left: 8px; top: 329px; width: 691px; height: 105px; font-family: Arial; font-size: 15px; resize: none;"></textarea> | |
| <!-- Профиль в журнале --> | |
| <div style="position: absolute; left: 8px; top: 446px; font-size: 15px;">Профиль в журнале:</div> | |
| <!-- Полная строка профиля (как pr2 в легаси) --> | |
| <input type="text" id="file_profile_info" readonly style="position: absolute; left: 8px; top: 464px; width: 691px; height: 13px; font-family: Arial; font-size: 12px; border: none; background: transparent; color: #000000;"> | |
| <button onclick="acceptProfileFromJournal()" style="position: absolute; left: 253px; top: 482px; width: 237px; height: 23px; font-size: 15px;">Принять профиль из журнала</button> | |
| <!-- Детали профиля с процентами (позиции увеличены на 40%) --> | |
| <!-- Строка 1 --> | |
| <span id="rN" style="position: absolute; left: 6px; top: 519px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rNO3" style="position: absolute; left: 62px; top: 519px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rNH4" style="position: absolute; left: 123px; top: 519px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rFe" style="position: absolute; left: 197px; top: 519px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rCo" style="position: absolute; left: 274px; top: 519px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <!-- Строка 2 --> | |
| <span id="rP" style="position: absolute; left: 6px; top: 533px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rMn" style="position: absolute; left: 197px; top: 533px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rSi" style="position: absolute; left: 274px; top: 533px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <!-- Строка 2.5 (Cl) --> | |
| <span id="rCl" style="position: absolute; left: 62px; top: 547px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <!-- Строка 3 --> | |
| <span id="rK" style="position: absolute; left: 6px; top: 547px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rB" style="position: absolute; left: 197px; top: 547px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <!-- Строка 4 --> | |
| <span id="rCa" style="position: absolute; left: 6px; top: 561px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rZn" style="position: absolute; left: 197px; top: 561px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <!-- Строка 5 --> | |
| <span id="rMg" style="position: absolute; left: 6px; top: 575px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rCu" style="position: absolute; left: 197px; top: 575px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <!-- Строка 6 --> | |
| <span id="rS" style="position: absolute; left: 6px; top: 589px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| <span id="rMo" style="position: absolute; left: 197px; top: 589px; font-family: Arial; font-size: 10px; color: #000000;"></span> | |
| </div> | |
| <!-- СПРАВКА --> | |
| <div id="help" class="tab-content"> | |
| <h3>Справка - Hydroponic Profile Generator</h3> | |
| <div style="line-height: 1.6; padding: 10px;"> | |
| <p><strong>Версия:</strong> 0.221 (HTML5 порт - 10.10.2025)</p> | |
| <p><strong>Описание:</strong> Калькулятор для расчета профилей минерального питания растений при гидропонном методе выращивания.</p> | |
| <div style="background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; padding: 12px; margin: 15px 0;"> | |
| <h4 style="margin-top: 0; color: #856404;">⚠️ О портировании</h4> | |
| <p style="margin: 8px 0;">Это HTML5 версия оригинального приложения WEGA-HPG, портированная с помощью ИИ.</p> | |
| <p style="margin: 8px 0;"><strong>Цель портирования:</strong> Максимально точно воспроизвести функционал оригинальной десктопной версии (Lazarus/Free Pascal) в веб-формате с сохранением всех расчетов, алгоритмов и пользовательского интерфейса.</p> | |
| <p style="margin: 8px 0;"><strong>Важно:</strong></p> | |
| <ul style="margin: 8px 0 8px 20px;"> | |
| <li>Функционал может частично отличаться от оригинала</li> | |
| <li>Возможны ошибки и неточности в расчетах</li> | |
| <li>Порт находится в активной разработке и постоянно обновляется</li> | |
| <li>Формат файлов .hpg совместим с оригинальной версией</li> | |
| <li>Для критически важных расчетов рекомендуется сверяться с оригинальной версией</li> | |
| </ul> | |
| <p style="margin: 8px 0; font-size: 13px; color: #856404;"> | |
| <strong>Если поведение редактора различается с оригиналом</strong>, убедитесь что используете последнюю версию порта, затем сообщите об этом в канале проекта | |
| <a href="https://t.me/WEGA_SERVER/20744" target="_blank" style="color: #0066cc;">https://t.me/WEGA_SERVER/20744</a> | |
| и приложите: | |
| </p> | |
| <ul style="margin: 4px 0 8px 20px; font-size: 12px; color: #856404;"> | |
| <li>Файл .hpg с которым возникла проблема</li> | |
| <li>Скриншот из оригинального HPG</li> | |
| <li>Скриншот того, что показывает HTML5 порт</li> | |
| </ul> | |
| </div> | |
| <h4 style="margin-top: 20px;">Основные возможности:</h4> | |
| <ul style="margin-left: 20px;"> | |
| <li>Расчет на основе ионного баланса</li> | |
| <li>Создание профиля питания через NPKCaMgSCl</li> | |
| <li>Получение расчетного EC</li> | |
| <li>Матрица соотношений элементов</li> | |
| <li>Составление микрокомплекса</li> | |
| <li>Разделение концентратов на А и Б</li> | |
| <li>Расчет стоимости удобрений</li> | |
| <li>Корректировка раствора</li> | |
| <li>Загрузка и обмен профилями</li> | |
| <li>Журнал действий</li> | |
| </ul> | |
| <h4 style="margin-top: 20px;">Контакты:</h4> | |
| <p>Оригинальный проект: <a href="https://github.com/WEGA-project/WEGA-HPG" target="_blank">WEGA-project/WEGA-HPG</a></p> | |
| <p>HTML5 порт: <a href="https://github.com/WEGA-project/wega-hpg" target="_blank">WEGA-project/wega-hpg</a></p> | |
| <p>Wiki: <a href="https://github.com/WEGA-project/WEGA-HPG/wiki" target="_blank">Документация</a></p> | |
| </div> | |
| </div> | |
| <!-- Строка профиля - общая для всех вкладок --> | |
| <div style="background: #d3d3d3; padding: 8px 12px;"> | |
| <div style="display: flex; gap: 5px; align-items: center;"> | |
| <input type="text" class="profile-string" id="profile-string" value="N=220 NO3=200 NH4=20 P=40 K=180 Ca=200 Mg=50 S=73 Cl=0 Fe=2 Mn=0.55 B=0.5 Zn=0.33 Cu=0.063 Mo=0.063 Co=0 Si=0" style="flex: 1;"> | |
| <button id="parse-button" onclick="parseProfile()" style="background: #c0ffc0; border: 1px solid #999; padding: 5px 10px; cursor: pointer; font-weight: bold;">OK</button> | |
| </div> | |
| </div> | |
| <!-- НИЖНЯЯ ПАНЕЛЬ --> | |
| <div class="bottom-panel"> | |
| <div class="bottom-left"> | |
| <div class="bottom-row"> | |
| <label>Объем в литрах</label> | |
| <input type="number" id="volume" value="10.0" step="0.1" oninput="onVolumeChange()"> | |
| <button onclick="saveFullState()">Сохранить</button> | |
| <button onclick="loadCompositions()">Вернуть составы</button> | |
| </div> | |
| <div class="bottom-row"> | |
| <label>Солей в граммах:</label> | |
| <input type="number" id="total-salts" value="23.20" step="0.01" readonly> | |
| <button onclick="recalculate()">по умолчанию</button> | |
| <button onclick="loadProfileOnly()">Вернуть профиль</button> | |
| </div> | |
| <div class="cost-info">Стоимость: <span id="bottom_total_price">0.00</span> за 1 литр: <span id="bottom_price_per_liter">0.00</span></div> | |
| </div> | |
| <div class="bottom-right"> | |
| <div class="version-text">Hydroponic Profile Generator 0.221</div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Молярные массы элементов | |
| const molN = 14.0067; | |
| const molP = 30.973762; | |
| const molK = 39.0983; | |
| const molCa = 40.078; | |
| const molMg = 24.305; | |
| const molS = 32.065; | |
| const molCl = 35.453; | |
| // Глобальные переменные для хранения значений | |
| let currentProfile = { | |
| N: 220, NO3: 200, NH4: 20, P: 40, K: 180, Ca: 200, Mg: 50, S: 73.049, Cl: 0, EC: 2.102, | |
| Fe: 2000, Mn: 550, B: 500, Zn: 330, Cu: 63, Mo: 63, Co: 0, Si: 0, | |
| volume: 10.0 | |
| }; | |
| // Составы солей (процентовки) | |
| let saltCompositions = { | |
| CaNO3: { Ca: 16.972, NO3: 11.863, NH4: 0.000 }, | |
| KNO3: { K: 38.672, NO3: 13.854 }, | |
| NH4NO3: { NH4: 17.499, NO3: 17.499 }, | |
| MgSO4: { Mg: 9.861, S: 13.010 }, | |
| KH2PO4: { K: 28.731, P: 22.761 }, | |
| K2SO4: { K: 44.874, S: 18.401 }, | |
| MgNO3: { Mg: 9.479, NO3: 10.925 }, | |
| CaCl2: { Ca: 18.294, Cl: 32.366 } | |
| }; | |
| // Флаги использования солей | |
| let useK2SO4 = false; | |
| let useMgNO3 = false; | |
| // Флаг для предотвращения рекурсивных вызовов | |
| let isUpdating = false; | |
| const roundTo = (value, decimals) => { | |
| if (!Number.isFinite(value)) return NaN; | |
| const factor = Math.pow(10, decimals); | |
| return Math.round(value * factor) / factor; | |
| }; | |
| const setInputValue = (id, value, decimals, { force = false } = {}) => { | |
| const input = document.getElementById(id); | |
| if (!input || !Number.isFinite(value)) return; | |
| if (document.activeElement === input && !force) return; | |
| if (typeof decimals === 'number') { | |
| input.value = roundTo(value, decimals).toFixed(decimals); | |
| } else { | |
| input.value = value; | |
| } | |
| }; | |
| const parseInput = (id, fallback = 0) => { | |
| const el = document.getElementById(id); | |
| if (!el) return fallback; | |
| const value = parseFloat(String(el.value).replace(',', '.')); | |
| return Number.isFinite(value) ? value : fallback; | |
| }; | |
| const parseSaltValue = (id) => { | |
| const el = document.getElementById(id); | |
| if (!el) return 0; | |
| const value = parseFloat(String(el.value).replace(',', '.')); | |
| return Number.isFinite(value) ? value : 0; | |
| }; | |
| const getSaltPercent = (salt, ion) => parseSaltValue(`salt_${salt}_${ion}`); | |
| const setSaltPercent = (salt, ion, value) => { | |
| setInputValue(`salt_${salt}_${ion}`, value, 3, { force: true }); | |
| }; | |
| const equalsRounded = (value, target, decimals = 1) => roundTo(value, decimals) === target; | |
| const nearZero = (value, epsilon = 1e-6) => Math.abs(value) < epsilon; | |
| const formatPercent = (value) => roundTo(value, 1).toFixed(1); | |
| const getSaltWeight = (salt) => parseSaltValue(`g_${salt}`); | |
| function getValues() { | |
| const data = { | |
| NO3: parseInput('NO3'), | |
| NH4: parseInput('NH4'), | |
| NH4_ratio: parseInput('NH4_ratio'), | |
| P: parseInput('P'), | |
| K: parseInput('K'), | |
| Ca: parseInput('Ca'), | |
| Mg: parseInput('Mg'), | |
| S: parseInput('S'), | |
| Cl: parseInput('Cl'), | |
| EC: parseInput('EC'), | |
| volume: parseInput('volume', 10), | |
| Fe: parseInput('Fe'), | |
| Mn: parseInput('Mn'), | |
| B: parseInput('B'), | |
| Zn: parseInput('Zn'), | |
| Cu: parseInput('Cu'), | |
| Mo: parseInput('Mo'), | |
| Co: parseInput('Co'), | |
| Si: parseInput('Si') | |
| }; | |
| // N всегда вычисляется как сумма NO3 + NH4 (как в оригинале getVar) | |
| data.N = data.NO3 + data.NH4; | |
| // Обновляем N только если поле не в фокусе И не во время обработки onNChange | |
| const nInput = document.getElementById('N'); | |
| if (!isUpdating && document.activeElement !== nInput) { | |
| setInputValue('N', data.N, 3); | |
| } | |
| currentProfile = { ...currentProfile, ...data }; | |
| return data; | |
| } | |
| function calculateCa(values) { | |
| const data = values || getValues(); | |
| const vNH4 = data.NH4; | |
| const vP = data.P; | |
| const vMg = data.Mg; | |
| const vK = data.K; | |
| const vNO3 = data.NO3; | |
| const vS = data.S; | |
| const vCl = data.Cl; | |
| const numerator = -molCa * (vNH4 * molP * molMg * molK * molS * molCl - | |
| vP * molN * molMg * molK * molS * molCl + | |
| 2 * vMg * molN * molP * molK * molS * molCl + | |
| vK * molN * molP * molMg * molS * molCl - | |
| vNO3 * molP * molMg * molK * molS * molCl - | |
| 2 * vS * molN * molP * molMg * molK * molCl - | |
| vCl * molN * molP * molMg * molK * molS); | |
| const denominator = 2 * molN * molP * molMg * molK * molS * molCl; | |
| const vCa = numerator / denominator; | |
| if (Number.isFinite(vCa)) { | |
| data.Ca = vCa; | |
| currentProfile.Ca = vCa; | |
| setInputValue('Ca', vCa, 3, { force: true }); | |
| } | |
| return data; | |
| } | |
| function calcAll(values, options = {}) { | |
| const opts = { skipCalculateS: false, ...options }; | |
| const data = values || getValues(); | |
| if (!opts.skipCalculateS) { | |
| calculateS(data); | |
| } | |
| calcEC(data); | |
| calcWeight(data); | |
| calcRatios(data); | |
| genNH4NO3event(data); | |
| updateInfo(data); | |
| updateProfileString(data); | |
| // Расчет концентратов | |
| calcConcentrates(); | |
| // Обновление цен | |
| updatePriceResults(); | |
| Object.assign(currentProfile, data); | |
| return data; | |
| } | |
| // Полный пересчет включая Ca (для зеленых полей P, Cl) | |
| function calcAllWithCa(values, options = {}) { | |
| const data = values || getValues(); | |
| if (!options.skipCalculateS) { | |
| calculateS(data); | |
| } | |
| calculateCa(data); | |
| calcEC(data); | |
| calcWeight(data); | |
| calcRatios(data); | |
| genNH4NO3event(data); | |
| updateInfo(data); | |
| updateProfileString(data); | |
| // Расчет концентратов | |
| calcConcentrates(); | |
| // Обновление цен | |
| updatePriceResults(); | |
| Object.assign(currentProfile, data); | |
| return data; | |
| } | |
| // Переключение вкладок | |
| function switchTab(tabName, tabElement) { | |
| // Скрыть все вкладки | |
| document.querySelectorAll('.tab-content').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| document.querySelectorAll('.tab').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| // Показать выбранную вкладку | |
| document.getElementById(tabName).classList.add('active'); | |
| // Если передан элемент таба (при клике), активировать его | |
| // Иначе (при восстановлении) найти таб по тексту вкладки | |
| if (tabElement) { | |
| tabElement.classList.add('active'); | |
| } else { | |
| // Найти и активировать соответствующий таб в панели | |
| document.querySelectorAll('.tab').forEach(tab => { | |
| if (tab.getAttribute('onclick') && tab.getAttribute('onclick').includes(`'${tabName}'`)) { | |
| tab.classList.add('active'); | |
| } | |
| }); | |
| } | |
| // Сохранить активную вкладку в localStorage | |
| localStorage.setItem('activeTab', tabName); | |
| } | |
| // Переключение внутренних вкладок (в концентратах) | |
| function switchInnerTab(tabName, tabElement) { | |
| document.querySelectorAll('.inner-tab-content').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| document.querySelectorAll('.tab-inner').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| document.getElementById(tabName).classList.add('active'); | |
| if (tabElement) { | |
| tabElement.classList.add('active'); | |
| } else { | |
| // Найти и активировать соответствующий внутренний таб | |
| document.querySelectorAll('.tab-inner').forEach(tab => { | |
| if (tab.getAttribute('onclick') && tab.getAttribute('onclick').includes(`'${tabName}'`)) { | |
| tab.classList.add('active'); | |
| } | |
| }); | |
| } | |
| // Пересчитать концентраты при переключении на вкладки "calc" или "preparation" | |
| if (tabName === 'calc' || tabName === 'preparation') { | |
| calcConcentrates(); | |
| } | |
| // Сохранить активную внутреннюю вкладку в localStorage | |
| localStorage.setItem('activeInnerTab', tabName); | |
| } | |
| // Расчет EC | |
| function calcEC(values) { | |
| const data = values || getValues(); | |
| const vNH4 = data.NH4; | |
| const vCa = data.Ca; | |
| const vMg = data.Mg; | |
| const vK = data.K; | |
| const vN = data.N; | |
| const vEC = 0.095 * (vNH4 * molCa * molMg * molK + 2 * vCa * molN * molMg * molK + | |
| 2 * vMg * molN * molCa * molK + vK * molN * molCa * molMg + | |
| 2 * molN * molCa * molMg * molK) / (molN * molCa * molMg * molK); | |
| data.EC = vEC; | |
| currentProfile.EC = vEC; | |
| setInputValue('EC', vEC, 3, { force: true }); | |
| return data; | |
| } | |
| // Расчет серы | |
| function calculateS(values) { | |
| const data = values || getValues(); | |
| const vNH4 = data.NH4; | |
| const vCa = data.Ca; | |
| const vMg = data.Mg; | |
| const vK = data.K; | |
| const vNO3 = data.NO3; | |
| const vP = data.P; | |
| const vCl = data.Cl; | |
| const vS = (-molS * (-vNH4 * molCa * molMg * molK * molP * molCl - | |
| 2 * vCa * molN * molMg * molK * molP * molCl - | |
| 2 * vMg * molN * molCa * molK * molP * molCl - | |
| vK * molN * molCa * molMg * molP * molCl + | |
| vNO3 * molCa * molMg * molK * molP * molCl + | |
| vP * molN * molCa * molMg * molK * molCl + | |
| vCl * molN * molCa * molMg * molK * molP)) / | |
| (2 * (molN * molCa * molMg * molK * molP * molCl)); | |
| if (Number.isFinite(vS)) { | |
| data.S = vS; | |
| currentProfile.S = vS; | |
| setInputValue('S', vS, 3, { force: true }); | |
| } | |
| return data; | |
| } | |
| // Расчет навесок солей | |
| function calcWeight(values) { | |
| const data = values || getValues(); | |
| const V = data.volume; | |
| const vP = data.P; | |
| const vK = data.K; | |
| const vCa = data.Ca; | |
| const vMg = data.Mg; | |
| const vS = data.S; | |
| const vNH4 = data.NH4; | |
| const vCl = data.Cl; | |
| const vKH2PO4_P = saltCompositions.KH2PO4.P; | |
| const vKH2PO4_K = saltCompositions.KH2PO4.K; | |
| const vCaNO3_Ca = saltCompositions.CaNO3.Ca; | |
| const vCaNO3_NH4 = saltCompositions.CaNO3.NH4; | |
| const vCaCl2_Ca = saltCompositions.CaCl2.Ca; | |
| const vCaCl2_Cl = saltCompositions.CaCl2.Cl; | |
| const vNH4NO3_NH4 = saltCompositions.NH4NO3.NH4; | |
| const vMgSO4_Mg = saltCompositions.MgSO4.Mg; | |
| const vMgSO4_S = saltCompositions.MgSO4.S; | |
| const vKNO3_K = saltCompositions.KNO3.K; | |
| const vK2SO4_K = saltCompositions.K2SO4.K; | |
| const vK2SO4_S = saltCompositions.K2SO4.S; | |
| const vMgNO3_Mg = saltCompositions.MgNO3.Mg; | |
| let sKH2PO4 = 0, sKNO3 = 0, sCaNO3 = 0, sMgNO3 = 0, sMgSO4 = 0, sK2SO4 = 0, sNH4NO3 = 0, sCaCl2 = 0; | |
| const safe = (value) => Number.isFinite(value) ? value : 0; | |
| if (useK2SO4 && !useMgNO3) { | |
| sKH2PO4 = safe(vP / vKH2PO4_P); | |
| sKNO3 = safe(-(-vK * vKH2PO4_P * vK2SO4_S * vMgSO4_Mg + | |
| vP * vKH2PO4_K * vK2SO4_S * vMgSO4_Mg + | |
| vK2SO4_K * vKH2PO4_P * vS * vMgSO4_Mg - | |
| vK2SO4_K * vKH2PO4_P * vMg * vMgSO4_S) / | |
| (vKNO3_K * vKH2PO4_P * vK2SO4_S * vMgSO4_Mg)); | |
| sCaNO3 = safe((vCa * vCaCl2_Cl - vCl * vCaCl2_Ca) / (vCaNO3_Ca * vCaCl2_Cl)); | |
| sMgNO3 = 0; | |
| sMgSO4 = safe(vMg / vMgSO4_Mg); | |
| sK2SO4 = safe((vS * vMgSO4_Mg - vMg * vMgSO4_S) / (vK2SO4_S * vMgSO4_Mg)); | |
| sNH4NO3 = safe(-(-vNH4 * vCaNO3_Ca * vCaCl2_Cl + | |
| vCaNO3_NH4 * vCa * vCaCl2_Cl - | |
| vCaNO3_NH4 * vCl * vCaCl2_Ca) / | |
| (vNH4NO3_NH4 * vCaNO3_Ca * vCaCl2_Cl)); | |
| sCaCl2 = safe(vCl / vCaCl2_Cl); | |
| } else if (!useK2SO4 && useMgNO3) { | |
| sKH2PO4 = safe(vP / vKH2PO4_P); | |
| sKNO3 = safe((vK * vKH2PO4_P - vP * vKH2PO4_K) / (vKNO3_K * vKH2PO4_P)); | |
| sCaNO3 = safe((vCa * vCaCl2_Cl - vCl * vCaCl2_Ca) / (vCaNO3_Ca * vCaCl2_Cl)); | |
| sMgNO3 = safe((vMg * vMgSO4_S - vMgSO4_Mg * vS) / (vMgNO3_Mg * vMgSO4_S)); | |
| sMgSO4 = safe(vS / vMgSO4_S); | |
| sK2SO4 = 0; | |
| sNH4NO3 = safe((vNH4 * vCaNO3_Ca * vCaCl2_Cl - | |
| vCaNO3_NH4 * vCa * vCaCl2_Cl + | |
| vCaNO3_NH4 * vCl * vCaCl2_Ca) / | |
| (vNH4NO3_NH4 * vCaNO3_Ca * vCaCl2_Cl)); | |
| sCaCl2 = safe(vCl / vCaCl2_Cl); | |
| } else { | |
| sKH2PO4 = safe(vP / vKH2PO4_P); | |
| sKNO3 = safe((vK * vKH2PO4_P - vP * vKH2PO4_K) / (vKNO3_K * vKH2PO4_P)); | |
| sCaNO3 = safe((vCa * vCaCl2_Cl - vCl * vCaCl2_Ca) / (vCaNO3_Ca * vCaCl2_Cl)); | |
| sMgNO3 = 0; | |
| sMgSO4 = safe(vMg / vMgSO4_Mg); | |
| sK2SO4 = 0; | |
| sNH4NO3 = safe(-(-vNH4 * vCaNO3_Ca * vCaCl2_Cl + | |
| vCaNO3_NH4 * vCa * vCaCl2_Cl - | |
| vCaNO3_NH4 * vCl * vCaCl2_Ca) / | |
| (vNH4NO3_NH4 * vCaNO3_Ca * vCaCl2_Cl)); | |
| sCaCl2 = safe(vCl / vCaCl2_Cl); | |
| } | |
| // Динамическая точность в зависимости от объема (как в легаси) | |
| let vDecimalPlaces = 3; | |
| let vStep = 0.001; | |
| if (V >= 1) { vDecimalPlaces = 3; vStep = 0.001; } | |
| if (V >= 10) { vDecimalPlaces = 2; vStep = 0.01; } | |
| if (V >= 100) { vDecimalPlaces = 1; vStep = 0.1; } | |
| if (V >= 1000) { vDecimalPlaces = 0; vStep = 1; } | |
| // Устанавливаем step для всех полей с весами | |
| ['g_CaNO3', 'g_KNO3', 'g_NH4NO3', 'g_MgSO4', 'g_KH2PO4', 'g_K2SO4', 'g_MgNO3', 'g_CaCl2'].forEach(id => { | |
| const input = document.getElementById(id); | |
| if (input) input.step = vStep; | |
| }); | |
| const grams = (value) => roundTo(value * V / 10, vDecimalPlaces); | |
| setInputValue('g_CaNO3', grams(sCaNO3), vDecimalPlaces, { force: true }); | |
| setInputValue('g_KNO3', grams(sKNO3), vDecimalPlaces, { force: true }); | |
| setInputValue('g_NH4NO3', grams(sNH4NO3), vDecimalPlaces, { force: true }); | |
| setInputValue('g_MgSO4', grams(sMgSO4), vDecimalPlaces, { force: true }); | |
| setInputValue('g_KH2PO4', grams(sKH2PO4), vDecimalPlaces, { force: true }); | |
| setInputValue('g_K2SO4', grams(sK2SO4), vDecimalPlaces, { force: true }); | |
| setInputValue('g_MgNO3', grams(sMgNO3), vDecimalPlaces, { force: true }); | |
| setInputValue('g_CaCl2', grams(sCaCl2), vDecimalPlaces, { force: true }); | |
| const totalSalts = grams(sCaNO3 + sKNO3 + sNH4NO3 + sMgSO4 + sKH2PO4 + sK2SO4 + sMgNO3 + sCaCl2); | |
| setInputValue('total-salts', totalSalts, vDecimalPlaces, { force: true }); | |
| return data; | |
| } | |
| function calcRatios(values) { | |
| const data = values || getValues(); | |
| const { N, P, K, Ca, Mg, S, Cl, NH4, NO3 } = data; | |
| if (N <= 0 || P <= 0 || K <= 0 || Ca <= 0 || Mg <= 0 || S <= 0 || NH4 <= 0 || NO3 <= 0) { | |
| return data; | |
| } | |
| const ratio = (a, b) => (b !== 0 ? a / b : NaN); | |
| const setRatio = (id, value, decimals = 3) => { | |
| if (Number.isFinite(value)) setInputValue(id, value, decimals); | |
| }; | |
| setRatio('m_N_P', ratio(N, P)); | |
| setRatio('m_N_K', ratio(N, K)); | |
| setRatio('m_N_Ca', ratio(N, Ca)); | |
| setRatio('m_N_Mg', ratio(N, Mg)); | |
| setRatio('m_N_S', ratio(N, S)); | |
| setRatio('m_P_N', ratio(P, N)); | |
| setRatio('m_P_K', ratio(P, K)); | |
| setRatio('m_P_Ca', ratio(P, Ca)); | |
| setRatio('m_P_Mg', ratio(P, Mg)); | |
| setRatio('m_P_S', ratio(P, S)); | |
| setRatio('m_K_N', ratio(K, N)); | |
| setRatio('m_K_P', ratio(K, P)); | |
| setRatio('m_K_Ca', ratio(K, Ca)); | |
| setRatio('m_K_Mg', ratio(K, Mg)); | |
| setRatio('m_K_S', ratio(K, S)); | |
| setRatio('m_Ca_N', ratio(Ca, N)); | |
| setRatio('m_Ca_P', ratio(Ca, P)); | |
| setRatio('m_Ca_K', ratio(Ca, K)); | |
| setRatio('m_Ca_Mg', ratio(Ca, Mg)); | |
| setRatio('m_Ca_S', ratio(Ca, S)); | |
| setRatio('m_Mg_N', ratio(Mg, N)); | |
| setRatio('m_Mg_P', ratio(Mg, P)); | |
| setRatio('m_Mg_K', ratio(Mg, K)); | |
| setRatio('m_Mg_Ca', ratio(Mg, Ca)); | |
| setRatio('m_Mg_S', ratio(Mg, S)); | |
| setRatio('m_S_N', ratio(S, N)); | |
| setRatio('m_S_P', ratio(S, P)); | |
| setRatio('m_S_K', ratio(S, K)); | |
| setRatio('m_S_Ca', ratio(S, Ca)); | |
| setRatio('m_S_Mg', ratio(S, Mg)); | |
| const nh4ratio = ratio(NH4, NO3); | |
| if (Number.isFinite(nh4ratio)) setInputValue('NH4_ratio', nh4ratio, 3); | |
| return data; | |
| } | |
| function genNH4NO3event(values) { | |
| const data = values || getValues(); | |
| const ratioLabel = document.getElementById('nh4no3-info'); | |
| if (!ratioLabel) return; | |
| if (data.NH4 > 0) { | |
| ratioLabel.textContent = `NH4:NO3 1:${roundTo(data.NO3 / data.NH4, 0)}`; | |
| } else { | |
| ratioLabel.textContent = 'NO3=100%'; | |
| } | |
| return data; | |
| } | |
| // Обновление информационных панелей | |
| function updateInfo(values) { | |
| const data = values || getValues(); | |
| const vN = data.N; | |
| const vP = data.P; | |
| const vK = data.K; | |
| const vCa = data.Ca; | |
| const vMg = data.Mg; | |
| const vS = data.S; | |
| const vCl = data.Cl; | |
| const vSUM = vN + vP + vK + vCa + vMg + vS + vCl; | |
| const ratioLine = vN > 0 | |
| ? `N:1 : ${roundTo(vP / vN, 2)} : ${roundTo(vK / vN, 2)} : ${roundTo(vCa / vN, 2)} : ${roundTo(vMg / vN, 2)} : ${roundTo(vS / vN, 2)} : ${roundTo(vCl / vN, 0)} sPPM=${roundTo(vSUM, 2)}` | |
| : 'N: --'; | |
| document.getElementById('ratio-info').textContent = ratioLine; | |
| const npkN = Math.round(vN / 10); | |
| const npkP = Math.round(vP / 0.436421 / 10); | |
| const npkK = Math.round(vK / 0.830148 / 10); | |
| const CaO = roundTo(vCa / 0.714691 / 10 * 10, 1) / 10; | |
| const MgO = roundTo(vMg / 0.603036 / 10 * 10, 1) / 10; | |
| const SO3 = roundTo(vS / 0.400496 / 10 * 10, 1) / 10; | |
| const npkInfo = `NPK: ${npkN}-${npkP}-${npkK} CaO=${CaO.toFixed(1)}% MgO=${MgO.toFixed(1)}% SO3=${SO3.toFixed(1)}%`; | |
| document.getElementById('npk-info').textContent = npkInfo; | |
| return data; | |
| } | |
| // Обновление строки профиля | |
| function updateProfileString(values) { | |
| const data = values || currentProfile; | |
| const Fe = parseFloat(document.getElementById('Fe').value) || 0; | |
| const Mn = parseFloat(document.getElementById('Mn').value) || 0; | |
| const B = parseFloat(document.getElementById('B').value) || 0; | |
| const Zn = parseFloat(document.getElementById('Zn').value) || 0; | |
| const Cu = parseFloat(document.getElementById('Cu').value) || 0; | |
| const Mo = parseFloat(document.getElementById('Mo').value) || 0; | |
| const Co = parseFloat(document.getElementById('Co').value) || 0; | |
| const Si = parseFloat(document.getElementById('Si').value) || 0; | |
| const profileStr = `N=${Math.round(data.N)} NO3=${roundTo(data.NO3, 2)} NH4=${roundTo(data.NH4, 2)} P=${roundTo(data.P, 2)} K=${roundTo(data.K, 2)} Ca=${roundTo(data.Ca, 2)} Mg=${roundTo(data.Mg, 2)} S=${roundTo(data.S, 1)} Cl=${roundTo(data.Cl, 1)} Fe=${roundTo(Fe / 1000, 3)} Mn=${roundTo(Mn / 1000, 3)} B=${roundTo(B / 1000, 3)} Zn=${roundTo(Zn / 1000, 3)} Cu=${roundTo(Cu / 1000, 3)} Mo=${roundTo(Mo / 1000, 3)} Co=${roundTo(Co / 1000, 3)} Si=${roundTo(Si / 1000, 3)}`; | |
| document.getElementById('profile-string').value = profileStr; | |
| return data; | |
| } | |
| // Обработчики событий изменения макроэлементов | |
| function onMacroChange() { | |
| calcAll(); | |
| } | |
| function onNChange() { | |
| if (isUpdating) return; | |
| isUpdating = true; | |
| const N = parseInput('N'); | |
| const ratio = parseInput('NH4_ratio'); | |
| if (N !== 0) { | |
| const NO3 = N / (ratio + 1); | |
| const NH4 = ratio * N / (ratio + 1); | |
| // Устанавливаем значения напрямую без генерации событий | |
| const no3Input = document.getElementById('NO3'); | |
| const nh4Input = document.getElementById('NH4'); | |
| if (no3Input) no3Input.value = NO3.toFixed(2); | |
| if (nh4Input) nh4Input.value = NH4.toFixed(2); | |
| } | |
| // Используем setTimeout чтобы дать браузеру время обработать | |
| setTimeout(() => { | |
| calcAll(); | |
| isUpdating = false; | |
| }, 0); | |
| } | |
| function onNO3Change() { | |
| if (isUpdating) return; | |
| calcAll(); | |
| } | |
| function onNH4Change() { | |
| if (isUpdating) return; | |
| calcAll(); | |
| } | |
| function onNH4RatioChange() { | |
| const ratio = parseInput('NH4_ratio'); | |
| const N = parseInput('N'); | |
| const NO3 = N / (ratio + 1); | |
| const NH4 = ratio * N / (ratio + 1); | |
| setInputValue('NO3', NO3, 2, { force: true }); | |
| setInputValue('NH4', NH4, 2, { force: true }); | |
| calcECtoVal(); | |
| calcAll(); | |
| } | |
| function onPChange() { | |
| calcAllWithCa(); | |
| } | |
| function onClChange() { | |
| calcAllWithCa(); | |
| } | |
| function onSChange() { | |
| const values = getValues(); | |
| calculateCa(values); | |
| calcEC(values); | |
| calcWeight(values); | |
| calcRatios(values); | |
| genNH4NO3event(values); | |
| updateInfo(values); | |
| updateProfileString(values); | |
| Object.assign(currentProfile, values); | |
| } | |
| function calcECtoVal() { | |
| const vEC = parseInput('EC'); | |
| const vKN = parseInput('m_K_N'); | |
| const vKCa = parseInput('m_K_Ca'); | |
| const vKMg = parseInput('m_K_Mg'); | |
| const vNH4NO3 = parseInput('NH4_ratio'); | |
| const vP = parseInput('P'); | |
| const vCl = parseInput('Cl'); | |
| // Расчет соотношений элементов | |
| const rN = (vKMg * vKCa) / (vKCa * vKN + vKMg * vKN + vKMg * vKCa + vKMg * vKCa * vKN); | |
| const rK = (vKN * vKMg * vKCa) / (vKCa * vKN + vKMg * vKN + vKMg * vKCa + vKMg * vKCa * vKN); | |
| const rCa = (vKMg * vKN) / (vKCa * vKN + vKMg * vKN + vKMg * vKCa + vKMg * vKCa * vKN); | |
| const rMg = (vKCa * vKN) / (vKCa * vKN + vKMg * vKN + vKMg * vKCa + vKMg * vKCa * vKN); | |
| const rNH4 = (rN * vNH4NO3) / (1 + vNH4NO3); | |
| // Расчет масштабирующего коэффициента | |
| const r = (0.10526315789473684211 * molN * molCa * molMg * molK * (100 * vEC - 19)) / | |
| (rNH4 * molCa * molMg * molK + 2 * rCa * molN * molMg * molK + | |
| 2 * rMg * molN * molCa * molK + rK * molN * molCa * molMg); | |
| // Пересчет элементов | |
| const vN = rN * r; | |
| const vK = rK * r; | |
| const vCa = rCa * r; | |
| const vMg = rMg * r; | |
| const vNH4 = rNH4 * r; | |
| const vNO3 = vN - vNH4; | |
| // Расчет серы через ионный баланс | |
| const vS = (molS * (vNH4 * molCa * molMg * molK * molP + 2 * vCa * molN * molMg * molK * molP + | |
| 2 * vMg * molN * molCa * molK * molP + vK * molN * molCa * molMg * molP - | |
| vNO3 * molCa * molMg * molK * molP - vP * molN * molCa * molMg * molK)) / | |
| (2 * (molN * molCa * molMg * molK * molP)); | |
| // Обновление полей | |
| setInputValue('NO3', vNO3, 2, { force: true }); | |
| setInputValue('NH4', vNH4, 2, { force: true }); | |
| setInputValue('K', vK, 3, { force: true }); | |
| setInputValue('Ca', vCa, 3, { force: true }); | |
| setInputValue('Mg', vMg, 3, { force: true }); | |
| setInputValue('S', vS, 3, { force: true }); | |
| setInputValue('N', vN, 3, { force: true }); | |
| // Обновить остальные расчеты | |
| calcAll(); | |
| } | |
| function onECChange() { | |
| calcECtoVal(); | |
| } | |
| function onVolumeChange() { | |
| calcAll(); | |
| microToWeght(); | |
| calcConcentrates(); // Обновляем концентраты и изготовление | |
| } | |
| function parseProfile() { | |
| const profileStr = document.getElementById('profile-string').value.trim(); | |
| const parts = profileStr.split(/\s+/); | |
| const parseButton = document.getElementById('parse-button'); | |
| if (parts.length !== 17) { | |
| parseButton.style.background = '#ff8080'; | |
| parseButton.textContent = 'NO'; | |
| return; | |
| } | |
| try { | |
| const values = {}; | |
| parts.forEach(part => { | |
| const [key, val] = part.split('='); | |
| if (key && val !== undefined) { | |
| values[key] = parseFloat(val.replace(',', '.')); | |
| } | |
| }); | |
| if (values.N !== undefined) setInputValue('N', values.N, 3, { force: true }); | |
| if (values.NO3 !== undefined) setInputValue('NO3', values.NO3, 2, { force: true }); | |
| if (values.NH4 !== undefined) setInputValue('NH4', values.NH4, 2, { force: true }); | |
| if (values.P !== undefined) setInputValue('P', values.P, 3, { force: true }); | |
| if (values.K !== undefined) setInputValue('K', values.K, 3, { force: true }); | |
| if (values.Ca !== undefined) setInputValue('Ca', values.Ca, 3, { force: true }); | |
| if (values.Mg !== undefined) setInputValue('Mg', values.Mg, 3, { force: true }); | |
| if (values.S !== undefined) setInputValue('S', values.S, 3, { force: true }); | |
| if (values.Cl !== undefined) setInputValue('Cl', values.Cl, 3, { force: true }); | |
| if (values.Fe !== undefined) setInputValue('Fe', values.Fe * 1000, 0, { force: true }); | |
| if (values.Mn !== undefined) setInputValue('Mn', values.Mn * 1000, 0, { force: true }); | |
| if (values.B !== undefined) setInputValue('B', values.B * 1000, 0, { force: true }); | |
| if (values.Zn !== undefined) setInputValue('Zn', values.Zn * 1000, 0, { force: true }); | |
| if (values.Cu !== undefined) setInputValue('Cu', values.Cu * 1000, 0, { force: true }); | |
| if (values.Mo !== undefined) setInputValue('Mo', values.Mo * 1000, 0, { force: true }); | |
| if (values.Co !== undefined) setInputValue('Co', values.Co * 1000, 0, { force: true }); | |
| if (values.Si !== undefined) setInputValue('Si', values.Si * 1000, 0, { force: true }); | |
| calculateS(); | |
| calcEC(); | |
| calcAll(); | |
| parseButton.style.background = '#c0ffc0'; | |
| parseButton.textContent = 'OK'; | |
| } catch (e) { | |
| parseButton.style.background = '#ff8080'; | |
| parseButton.textContent = 'NO'; | |
| } | |
| } | |
| function onMatrixChange(row, col) { | |
| const fieldId = `m_${row}_${col}`; | |
| const inverseId = `m_${col}_${row}`; | |
| const value = parseFloat(document.getElementById(fieldId).value.replace(',', '.')); | |
| if (!Number.isFinite(value) || value === 0) return; | |
| const updateInverse = () => { | |
| const inverseField = document.getElementById(inverseId); | |
| if (inverseField && document.activeElement !== inverseField) { | |
| inverseField.value = (1 / value).toFixed(3); | |
| } | |
| }; | |
| if (fieldId === 'm_K_N' || fieldId === 'm_K_Ca' || fieldId === 'm_K_Mg') { | |
| const values = getValues(); | |
| if (fieldId === 'm_K_N') { | |
| values.K = values.N * value; | |
| } else if (fieldId === 'm_K_Ca') { | |
| values.K = values.Ca * value; | |
| } else if (fieldId === 'm_K_Mg') { | |
| values.K = values.Mg * value; | |
| } | |
| setInputValue('K', values.K, 3, { force: true }); | |
| updateInverse(); | |
| calcECtoVal(); | |
| calcAll(); | |
| return; | |
| } | |
| const values = getValues(); | |
| let needsCaRecalc = false; | |
| const applyRatio = (target, source, ratio) => { | |
| const newValue = source * ratio; | |
| if (Number.isFinite(newValue)) { | |
| values[target] = newValue; | |
| setInputValue(target, newValue, 3, { force: true }); | |
| if (target === 'S') needsCaRecalc = true; | |
| } | |
| }; | |
| if (row === 'N') { | |
| if (col === 'P') applyRatio('N', values.P, value); | |
| if (col === 'K') applyRatio('N', values.K, value); | |
| if (col === 'Ca') applyRatio('N', values.Ca, value); | |
| if (col === 'Mg') applyRatio('N', values.Mg, value); | |
| if (col === 'S') applyRatio('N', values.S, value); | |
| values.NO3 = values.N / (values.NH4_ratio + 1); | |
| values.NH4 = values.NH4_ratio * values.N / (values.NH4_ratio + 1); | |
| } else if (row === 'P') { | |
| if (col === 'N') applyRatio('P', values.N, value); | |
| if (col === 'K') applyRatio('P', values.K, value); | |
| if (col === 'Ca') applyRatio('P', values.Ca, value); | |
| if (col === 'Mg') applyRatio('P', values.Mg, value); | |
| if (col === 'S') applyRatio('P', values.S, value); | |
| } else if (row === 'K') { | |
| if (col === 'N') applyRatio('K', values.N, value); | |
| if (col === 'P') applyRatio('K', values.P, value); | |
| if (col === 'Ca') applyRatio('K', values.Ca, value); | |
| if (col === 'Mg') applyRatio('K', values.Mg, value); | |
| if (col === 'S') applyRatio('K', values.S, value); | |
| } else if (row === 'Ca') { | |
| if (col === 'N') applyRatio('Ca', values.N, value); | |
| if (col === 'P') applyRatio('Ca', values.P, value); | |
| if (col === 'K') applyRatio('Ca', values.K, value); | |
| if (col === 'Mg') applyRatio('Ca', values.Mg, value); | |
| if (col === 'S') applyRatio('Ca', values.S, value); | |
| } else if (row === 'Mg') { | |
| if (col === 'N') applyRatio('Mg', values.N, value); | |
| if (col === 'P') applyRatio('Mg', values.P, value); | |
| if (col === 'K') applyRatio('Mg', values.K, value); | |
| if (col === 'Ca') applyRatio('Mg', values.Ca, value); | |
| if (col === 'S') applyRatio('Mg', values.S, value); | |
| } else if (row === 'S') { | |
| if (col === 'N') applyRatio('S', values.N, value); | |
| if (col === 'P') applyRatio('S', values.P, value); | |
| if (col === 'K') applyRatio('S', values.K, value); | |
| if (col === 'Ca') applyRatio('S', values.Ca, value); | |
| if (col === 'Mg') applyRatio('S', values.Mg, value); | |
| } | |
| updateInverse(); | |
| setInputValue('N', values.N, 3, { force: true }); | |
| setInputValue('P', values.P, 3, { force: true }); | |
| setInputValue('K', values.K, 3, { force: true }); | |
| setInputValue('Ca', values.Ca, 3, { force: true }); | |
| setInputValue('Mg', values.Mg, 3, { force: true }); | |
| setInputValue('S', values.S, 3, { force: true }); | |
| setInputValue('NO3', values.NO3, 3, { force: true }); | |
| setInputValue('NH4', values.NH4, 3, { force: true }); | |
| if (needsCaRecalc) { | |
| calculateCa(values); | |
| } | |
| calcAll(); | |
| } | |
| function syncSaltCompositions() { | |
| saltCompositions.CaNO3.Ca = getSaltPercent('CaNO3', 'Ca'); | |
| saltCompositions.CaNO3.NO3 = getSaltPercent('CaNO3', 'NO3'); | |
| saltCompositions.CaNO3.NH4 = getSaltPercent('CaNO3', 'NH4'); | |
| saltCompositions.KNO3.K = getSaltPercent('KNO3', 'K'); | |
| saltCompositions.KNO3.NO3 = getSaltPercent('KNO3', 'NO3'); | |
| saltCompositions.NH4NO3.NH4 = getSaltPercent('NH4NO3', 'NH4'); | |
| saltCompositions.NH4NO3.NO3 = getSaltPercent('NH4NO3', 'NO3'); | |
| saltCompositions.MgSO4.Mg = getSaltPercent('MgSO4', 'Mg'); | |
| saltCompositions.MgSO4.S = getSaltPercent('MgSO4', 'S'); | |
| saltCompositions.KH2PO4.K = getSaltPercent('KH2PO4', 'K'); | |
| saltCompositions.KH2PO4.P = getSaltPercent('KH2PO4', 'P'); | |
| saltCompositions.K2SO4.K = getSaltPercent('K2SO4', 'K'); | |
| saltCompositions.K2SO4.S = getSaltPercent('K2SO4', 'S'); | |
| saltCompositions.MgNO3.Mg = getSaltPercent('MgNO3', 'Mg'); | |
| saltCompositions.MgNO3.NO3 = getSaltPercent('MgNO3', 'NO3'); | |
| saltCompositions.CaCl2.Ca = getSaltPercent('CaCl2', 'Ca'); | |
| saltCompositions.CaCl2.Cl = getSaltPercent('CaCl2', 'Cl'); | |
| } | |
| function updateSaltLabels() { | |
| const labelCaNO3 = document.getElementById('label_CaNO3'); | |
| if (labelCaNO3) { | |
| const ca = getSaltPercent('CaNO3', 'Ca'); | |
| const no3 = getSaltPercent('CaNO3', 'NO3'); | |
| const nh4 = getSaltPercent('CaNO3', 'NH4'); | |
| let text; | |
| if (nearZero(nh4) && equalsRounded(ca, 17, 0)) { | |
| text = 'Кальций азотнокислый Ca(NO3)2*4H2O'; | |
| } else if (nearZero(nh4) && equalsRounded(ca, 20, 0)) { | |
| text = 'Кальций азотнокислый Ca(NO3)2*2H2O'; | |
| } else if (nearZero(nh4) && equalsRounded(ca, 24.4, 1)) { | |
| text = 'Кальций азотнокислый Ca(NO3)2'; | |
| } else { | |
| text = `Селитра кальциевая CaO-${formatPercent(ca / 0.714691)}% N-${formatPercent(nh4 + no3)}%`; | |
| } | |
| labelCaNO3.textContent = text; | |
| } | |
| const labelKNO3 = document.getElementById('label_KNO3'); | |
| if (labelKNO3) { | |
| const k = getSaltPercent('KNO3', 'K'); | |
| const no3 = getSaltPercent('KNO3', 'NO3'); | |
| const text = equalsRounded(k, 38.7, 1) | |
| ? 'Калий азотнокислый KNO3' | |
| : `Селитра калиевая K2O-${formatPercent(k / 0.830148)}% N-${formatPercent(no3)}%`; | |
| labelKNO3.textContent = text; | |
| } | |
| const labelNH4NO3 = document.getElementById('label_NH4NO3'); | |
| if (labelNH4NO3) { | |
| const nh4 = getSaltPercent('NH4NO3', 'NH4'); | |
| const no3 = getSaltPercent('NH4NO3', 'NO3'); | |
| const text = equalsRounded(no3, 17.5, 1) | |
| ? 'Аммоний азотнокислый NH4NO3' | |
| : `Селитра аммиачная N-${formatPercent(nh4 + no3)}%`; | |
| labelNH4NO3.textContent = text; | |
| } | |
| const labelMgSO4 = document.getElementById('label_MgSO4'); | |
| if (labelMgSO4) { | |
| const mg = getSaltPercent('MgSO4', 'Mg'); | |
| const s = getSaltPercent('MgSO4', 'S'); | |
| let text; | |
| if (equalsRounded(mg, 9.9, 1)) { | |
| text = 'Магний сернокислый MgSO4*7H2O'; | |
| } else if (equalsRounded(mg, 20.2, 1)) { | |
| text = 'Магний сернокислый MgSO4'; | |
| } else { | |
| text = `Сульфат магния MgO-${formatPercent(mg / 0.603036)}% SO3-${formatPercent(s / 0.400496)}%`; | |
| } | |
| labelMgSO4.textContent = text; | |
| } | |
| const labelKH2PO4 = document.getElementById('label_KH2PO4'); | |
| if (labelKH2PO4) { | |
| const k = getSaltPercent('KH2PO4', 'K'); | |
| const p = getSaltPercent('KH2PO4', 'P'); | |
| const text = equalsRounded(k, 28.7, 1) | |
| ? 'Калий фосфорнокислый KH2PO4' | |
| : `Монофосфат калия K2O-${formatPercent(k / 0.830148)}% P2O5-${formatPercent(p / 0.436421)}%`; | |
| labelKH2PO4.textContent = text; | |
| } | |
| const labelK2SO4 = document.getElementById('label_K2SO4'); | |
| if (labelK2SO4) { | |
| const k = getSaltPercent('K2SO4', 'K'); | |
| const s = getSaltPercent('K2SO4', 'S'); | |
| const text = equalsRounded(k, 44.9, 1) | |
| ? 'Калий сернокислый K2SO4' | |
| : `Сульфат калия K2O-${formatPercent(k / 0.830148)}% SO3-${formatPercent(s / 0.400496)}%`; | |
| labelK2SO4.textContent = text; | |
| } | |
| const labelMgNO3 = document.getElementById('label_MgNO3'); | |
| if (labelMgNO3) { | |
| const mg = getSaltPercent('MgNO3', 'Mg'); | |
| const no3 = getSaltPercent('MgNO3', 'NO3'); | |
| let text; | |
| if (equalsRounded(mg, 9.5, 1)) { | |
| text = 'Магний азотнокислый Mg(NO3)2*6H2O'; | |
| } else if (equalsRounded(mg, 16.4, 1)) { | |
| text = 'Магний азотнокислый Mg(NO3)2'; | |
| } else { | |
| text = `Селитра магниевая MgO-${formatPercent(mg / 0.603036)}% N-${formatPercent(no3)}%`; | |
| } | |
| labelMgNO3.textContent = text; | |
| } | |
| const labelCaCl2 = document.getElementById('label_CaCl2'); | |
| if (labelCaCl2) { | |
| const ca = getSaltPercent('CaCl2', 'Ca'); | |
| const cl = getSaltPercent('CaCl2', 'Cl'); | |
| let text; | |
| if (equalsRounded(ca, 18.3, 1)) { | |
| text = 'Хлорид кальция 6-водный CaCl2*6H2O'; | |
| } else if (equalsRounded(ca, 36.1, 1)) { | |
| text = 'Хлорид кальция безводный CaCl2'; | |
| } else { | |
| text = `Кальций хлористый CaO-${formatPercent(ca / 0.714691)}% Cl-${formatPercent(cl)}%`; | |
| } | |
| labelCaCl2.textContent = text; | |
| } | |
| } | |
| function onSaltCompositionChange(salt, changedIon) { | |
| if (isUpdating) return; | |
| isUpdating = true; | |
| try { | |
| if (salt) { | |
| switch (salt) { | |
| case 'CaNO3': { | |
| let ca = getSaltPercent('CaNO3', 'Ca'); | |
| let no3 = getSaltPercent('CaNO3', 'NO3'); | |
| let nh4 = getSaltPercent('CaNO3', 'NH4'); | |
| if (changedIon === 'Ca') { | |
| no3 = (2 * ca * molN + nh4 * molCa) / molCa; | |
| setSaltPercent('CaNO3', 'NO3', no3); | |
| } else if (changedIon === 'NH4') { | |
| no3 = (2 * ca * molN + nh4 * molCa) / molCa; | |
| setSaltPercent('CaNO3', 'NO3', no3); | |
| ca = -molCa * (nh4 - no3) / (2 * molN); | |
| setSaltPercent('CaNO3', 'Ca', ca); | |
| } else if (changedIon === 'NO3') { | |
| ca = -molCa * (nh4 - no3) / (2 * molN); | |
| setSaltPercent('CaNO3', 'Ca', ca); | |
| } | |
| break; | |
| } | |
| case 'KNO3': { | |
| let k = getSaltPercent('KNO3', 'K'); | |
| let no3 = getSaltPercent('KNO3', 'NO3'); | |
| if (changedIon === 'K') { | |
| no3 = (k * molN) / molK; | |
| setSaltPercent('KNO3', 'NO3', no3); | |
| } else if (changedIon === 'NO3') { | |
| k = (no3 * molK) / molN; | |
| setSaltPercent('KNO3', 'K', k); | |
| } | |
| break; | |
| } | |
| case 'NH4NO3': { | |
| const value = getSaltPercent('NH4NO3', changedIon); | |
| setSaltPercent('NH4NO3', changedIon === 'NH4' ? 'NO3' : 'NH4', value); | |
| break; | |
| } | |
| case 'MgSO4': { | |
| let mg = getSaltPercent('MgSO4', 'Mg'); | |
| let s = getSaltPercent('MgSO4', 'S'); | |
| if (changedIon === 'Mg') { | |
| s = (mg * molS) / molMg; | |
| setSaltPercent('MgSO4', 'S', s); | |
| } else if (changedIon === 'S') { | |
| mg = (s * molMg) / molS; | |
| setSaltPercent('MgSO4', 'Mg', mg); | |
| } | |
| break; | |
| } | |
| case 'KH2PO4': { | |
| let k = getSaltPercent('KH2PO4', 'K'); | |
| let p = getSaltPercent('KH2PO4', 'P'); | |
| if (changedIon === 'K') { | |
| p = (k * molP) / molK; | |
| setSaltPercent('KH2PO4', 'P', p); | |
| } else if (changedIon === 'P') { | |
| k = (p * molK) / molP; | |
| setSaltPercent('KH2PO4', 'K', k); | |
| } | |
| break; | |
| } | |
| case 'K2SO4': { | |
| let k = getSaltPercent('K2SO4', 'K'); | |
| let s = getSaltPercent('K2SO4', 'S'); | |
| if (changedIon === 'K') { | |
| s = (k * molS) / (2 * molK); | |
| setSaltPercent('K2SO4', 'S', s); | |
| } else if (changedIon === 'S') { | |
| k = (s * 2 * molK) / molS; | |
| setSaltPercent('K2SO4', 'K', k); | |
| } | |
| break; | |
| } | |
| case 'MgNO3': { | |
| let mg = getSaltPercent('MgNO3', 'Mg'); | |
| let no3 = getSaltPercent('MgNO3', 'NO3'); | |
| if (changedIon === 'Mg') { | |
| no3 = (2 * mg * molN) / molMg; | |
| setSaltPercent('MgNO3', 'NO3', no3); | |
| } else if (changedIon === 'NO3') { | |
| mg = 0.5 * (no3 / molN) * molMg; | |
| setSaltPercent('MgNO3', 'Mg', mg); | |
| } | |
| break; | |
| } | |
| case 'CaCl2': { | |
| let ca = getSaltPercent('CaCl2', 'Ca'); | |
| let cl = getSaltPercent('CaCl2', 'Cl'); | |
| if (changedIon === 'Ca') { | |
| cl = (2 * ca / molCa) * molCl; | |
| setSaltPercent('CaCl2', 'Cl', cl); | |
| } else if (changedIon === 'Cl') { | |
| ca = 0.5 * (cl / molCl) * molCa; | |
| setSaltPercent('CaCl2', 'Ca', ca); | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| syncSaltCompositions(); | |
| updateSaltLabels(); | |
| updateSaltNames(); // Обновляем названия солей (как SoilName в легаси) | |
| calcWeight(getValues()); | |
| applyWeightsToProfile({ sync: false }); | |
| calcConcentrates(); // Обновляем концентраты и изготовление (как CalcConc в легаси) | |
| } finally { | |
| isUpdating = false; | |
| } | |
| } | |
| function applyWeightsToProfile({ sync = true } = {}) { | |
| const V = parseInput('volume', 10); | |
| if (!Number.isFinite(V) || V <= 0) return; | |
| if (sync) syncSaltCompositions(); | |
| const factor = 0.1 * V; | |
| if (factor === 0) return; | |
| const sCaNO3 = getSaltWeight('CaNO3'); | |
| const sKNO3 = getSaltWeight('KNO3'); | |
| const sNH4NO3 = getSaltWeight('NH4NO3'); | |
| const sMgSO4 = getSaltWeight('MgSO4'); | |
| const sKH2PO4 = getSaltWeight('KH2PO4'); | |
| const sK2SO4 = useK2SO4 ? getSaltWeight('K2SO4') : 0; | |
| const sMgNO3 = useMgNO3 ? getSaltWeight('MgNO3') : 0; | |
| const sCaCl2 = getSaltWeight('CaCl2'); | |
| const comps = saltCompositions; | |
| const vNO3 = (sCaNO3 * comps.CaNO3.NO3 + | |
| sNH4NO3 * comps.NH4NO3.NO3 + | |
| sKNO3 * comps.KNO3.NO3 + | |
| sMgNO3 * comps.MgNO3.NO3) / factor; | |
| const vNH4 = (sCaNO3 * comps.CaNO3.NH4 + | |
| sNH4NO3 * comps.NH4NO3.NH4) / factor; | |
| const vN = vNO3 + vNH4; | |
| const vP = (sKH2PO4 * comps.KH2PO4.P) / factor; | |
| const vK = (sKNO3 * comps.KNO3.K + | |
| sKH2PO4 * comps.KH2PO4.K + | |
| sK2SO4 * comps.K2SO4.K) / factor; | |
| const vCa = (sCaNO3 * comps.CaNO3.Ca + sCaCl2 * comps.CaCl2.Ca) / factor; | |
| const vMg = (sMgSO4 * comps.MgSO4.Mg + sMgNO3 * comps.MgNO3.Mg) / factor; | |
| const vCl = (sCaCl2 * comps.CaCl2.Cl) / factor; | |
| const vNH4NO3ratio = vNO3 !== 0 ? vNH4 / vNO3 : 0; | |
| setInputValue('NO3', vNO3, 3, { force: true }); | |
| setInputValue('NH4', vNH4, 3, { force: true }); | |
| setInputValue('N', vN, 3, { force: true }); | |
| setInputValue('P', vP, 3, { force: true }); | |
| setInputValue('K', vK, 3, { force: true }); | |
| setInputValue('Ca', vCa, 3, { force: true }); | |
| setInputValue('Mg', vMg, 3, { force: true }); | |
| setInputValue('Cl', vCl, 3, { force: true }); | |
| setInputValue('NH4_ratio', vNH4NO3ratio, 3, { force: true }); | |
| let values = getValues(); | |
| values.N = vN; | |
| values.NO3 = vNO3; | |
| values.NH4 = vNH4; | |
| values.P = vP; | |
| values.K = vK; | |
| values.Ca = vCa; | |
| values.Mg = vMg; | |
| values.Cl = vCl; | |
| values.NH4_ratio = vNH4NO3ratio; | |
| values = calculateS(values); | |
| values = calculateCa(values); | |
| calcAll(values, { skipCalculateS: true }); | |
| } | |
| function onSaltWeightChange() { | |
| if (isUpdating) return; | |
| isUpdating = true; | |
| try { | |
| applyWeightsToProfile({ sync: true }); | |
| calcConcentrates(); // Обновляем концентраты и изготовление (как CalcConc в легаси) | |
| } finally { | |
| isUpdating = false; | |
| } | |
| } | |
| function onSaltSelectionChange(changedCheckbox) { | |
| const k2so4Checkbox = document.getElementById('use_K2SO4'); | |
| const mgno3Checkbox = document.getElementById('use_MgNO3'); | |
| // Взаимоисключающая логика как в оригинале | |
| if (changedCheckbox === 'K2SO4') { | |
| if (k2so4Checkbox.checked) { | |
| mgno3Checkbox.checked = false; | |
| } else { | |
| mgno3Checkbox.checked = true; | |
| } | |
| } else if (changedCheckbox === 'MgNO3') { | |
| if (mgno3Checkbox.checked) { | |
| k2so4Checkbox.checked = false; | |
| } else { | |
| k2so4Checkbox.checked = true; | |
| } | |
| } | |
| useK2SO4 = k2so4Checkbox.checked; | |
| useMgNO3 = mgno3Checkbox.checked; | |
| calcAll(); | |
| } | |
| // Глобальные переменные для микро как в легаси | |
| let agFe = 20.1, agMn = 36.4, agB = 17.5, agZn = 22.7, agCu = 25.5, agMo = 54.3, agCo = 13.0, agSi = 7.0; | |
| let vdFe = 20.1, vdMn = 36.4, vdB = 17.5, vdZn = 22.7, vdCu = 25.5, vdMo = 54.3, vdCo = 13.0, vdSi = 7.0; | |
| let gmSUM = 0; | |
| // Микроэлементы | |
| function onMicroChange() { | |
| updateProfileString(); | |
| microToWeght(); | |
| updateMicroNames(); // Обновляем названия микроэлементов | |
| calcConcentrates(); | |
| } | |
| function onMicroCompositionChange() { | |
| microToWeght(); | |
| updateMicroNames(); // Обновляем названия микроэлементов | |
| calcConcentrates(); | |
| } | |
| function toMicrocomplex() { | |
| // Генерация строки состава микрокомплекса из составляющих (как в легаси) | |
| const gFe = parseFloat(document.getElementById('g_Fe').value) || 0; | |
| const gMn = parseFloat(document.getElementById('g_Mn').value) || 0; | |
| const gB = parseFloat(document.getElementById('g_B').value) || 0; | |
| const gZn = parseFloat(document.getElementById('g_Zn').value) || 0; | |
| const gCu = parseFloat(document.getElementById('g_Cu').value) || 0; | |
| const gMo = parseFloat(document.getElementById('g_Mo').value) || 0; | |
| const gCo = parseFloat(document.getElementById('g_Co').value) || 0; | |
| const gSi = parseFloat(document.getElementById('g_Si').value) || 0; | |
| gmSUM = gFe + gMn + gB + gZn + gCu + gMo + gCo + gSi; | |
| const V = parseFloat(document.getElementById('volume').value) || 10; | |
| const Fe = parseFloat(document.getElementById('Fe').value) || 0; | |
| const Mn = parseFloat(document.getElementById('Mn').value) || 0; | |
| const B = parseFloat(document.getElementById('B').value) || 0; | |
| const Zn = parseFloat(document.getElementById('Zn').value) || 0; | |
| const Cu = parseFloat(document.getElementById('Cu').value) || 0; | |
| const Mo = parseFloat(document.getElementById('Mo').value) || 0; | |
| const Co = parseFloat(document.getElementById('Co').value) || 0; | |
| const Si = parseFloat(document.getElementById('Si').value) || 0; | |
| if (gmSUM > 0) { | |
| agFe = (Fe * V) / (gmSUM * 10000); | |
| agMn = (Mn * V) / (gmSUM * 10000); | |
| agB = (B * V) / (gmSUM * 10000); | |
| agZn = (Zn * V) / (gmSUM * 10000); | |
| agCu = (Cu * V) / (gmSUM * 10000); | |
| agMo = (Mo * V) / (gmSUM * 10000); | |
| agCo = (Co * V) / (gmSUM * 10000); | |
| agSi = (Si * V) / (gmSUM * 10000); | |
| } | |
| } | |
| function onComplexChange() { | |
| const checkbox = document.getElementById('use_complex'); | |
| const complexField = document.getElementById('complex_value'); | |
| // Элементы таблицы солей | |
| const saltsTable = document.getElementById('salts-table'); | |
| const saltsHeader = document.getElementById('salts-header'); | |
| // Поля микроэлементов (БЕЗ бора! Бор остается редактируемым) | |
| const microFields = ['Fe', 'Mn', 'Zn', 'Cu', 'Mo', 'Co', 'Si']; | |
| if (checkbox.checked) { | |
| // Сохраняем текущие проценты перед включением комплекса | |
| vdFe = parseFloat(document.getElementById('micro_Fe_percent').value) || 20.1; | |
| vdMn = parseFloat(document.getElementById('micro_Mn_percent').value) || 36.4; | |
| vdB = parseFloat(document.getElementById('micro_B_percent').value) || 17.5; | |
| vdZn = parseFloat(document.getElementById('micro_Zn_percent').value) || 22.7; | |
| vdCu = parseFloat(document.getElementById('micro_Cu_percent').value) || 25.5; | |
| vdMo = parseFloat(document.getElementById('micro_Mo_percent').value) || 54.3; | |
| vdCo = parseFloat(document.getElementById('micro_Co_percent').value) || 13.0; | |
| vdSi = parseFloat(document.getElementById('micro_Si_percent').value) || 7.0; | |
| // Заполняем проценты вычисленными значениями (как в легаси chkComplexClick) | |
| document.getElementById('micro_Fe_percent').value = agFe.toFixed(4); | |
| document.getElementById('micro_Mn_percent').value = agMn.toFixed(4); | |
| document.getElementById('micro_B_percent').value = agB.toFixed(4); | |
| document.getElementById('micro_Zn_percent').value = agZn.toFixed(4); | |
| document.getElementById('micro_Cu_percent').value = agCu.toFixed(4); | |
| document.getElementById('micro_Mo_percent').value = agMo.toFixed(4); | |
| document.getElementById('micro_Co_percent').value = agCo.toFixed(4); | |
| document.getElementById('micro_Si_percent').value = agSi.toFixed(4); | |
| toMicrocomplex(); | |
| // В режиме комплекса - скрываем колонку граммов в таблице, оставляем проценты | |
| // Скрываем заголовок "граммы" | |
| if (saltsHeader) { | |
| const gramsLabel = saltsHeader.querySelector('span[style*="left: 215px"]'); | |
| if (gramsLabel) gramsLabel.style.display = 'none'; | |
| } | |
| // Скрываем все поля граммов в таблице | |
| ['g_Fe', 'g_Mn', 'g_B', 'g_Zn', 'g_Cu', 'g_Mo', 'g_Co', 'g_Si'].forEach(id => { | |
| const field = document.getElementById(id); | |
| if (field) field.style.display = 'none'; | |
| }); | |
| // Делаем микроэлементы readonly (КРОМЕ B - он остается редактируемым!) | |
| microFields.forEach(id => { | |
| const field = document.getElementById(id); | |
| if (field) { | |
| field.readOnly = true; | |
| field.style.background = '#e8e8e8'; | |
| } | |
| }); | |
| // B остается активным | |
| document.getElementById('B').readOnly = false; | |
| document.getElementById('B').style.background = ''; | |
| // Показываем поле ввода, скрываем текст | |
| document.getElementById('total_micro_grams').style.display = 'block'; | |
| document.getElementById('total_micro_text').style.display = 'none'; | |
| document.getElementById('total_micro_grams').value = gmSUM.toFixed(5); | |
| } else { | |
| // Восстанавливаем сохраненные проценты при выключении | |
| document.getElementById('micro_Fe_percent').value = vdFe.toFixed(4); | |
| document.getElementById('micro_Mn_percent').value = vdMn.toFixed(4); | |
| document.getElementById('micro_B_percent').value = vdB.toFixed(4); | |
| document.getElementById('micro_Zn_percent').value = vdZn.toFixed(4); | |
| document.getElementById('micro_Cu_percent').value = vdCu.toFixed(4); | |
| document.getElementById('micro_Mo_percent').value = vdMo.toFixed(4); | |
| document.getElementById('micro_Co_percent').value = vdCo.toFixed(4); | |
| document.getElementById('micro_Si_percent').value = vdSi.toFixed(4); | |
| // При выключенном комплексе - показываем колонку граммов | |
| // Показываем заголовок "граммы" | |
| if (saltsHeader) { | |
| const gramsLabel = saltsHeader.querySelector('span[style*="left: 215px"]'); | |
| if (gramsLabel) gramsLabel.style.display = ''; | |
| } | |
| // Показываем все поля граммов | |
| ['g_Fe', 'g_Mn', 'g_B', 'g_Zn', 'g_Cu', 'g_Mo', 'g_Co', 'g_Si'].forEach(id => { | |
| const field = document.getElementById(id); | |
| if (field) field.style.display = ''; | |
| }); | |
| // Убираем readonly со всех микроэлементов | |
| microFields.forEach(id => { | |
| const field = document.getElementById(id); | |
| if (field) { | |
| field.readOnly = false; | |
| field.style.background = ''; | |
| } | |
| }); | |
| // Скрываем поле ввода, показываем текст | |
| document.getElementById('total_micro_grams').style.display = 'none'; | |
| document.getElementById('total_micro_text').style.display = 'block'; | |
| document.getElementById('total_micro_text').textContent = gmSUM.toFixed(5); | |
| } | |
| microToWeght(); | |
| updateMicroNames(); // Обновляем названия микроэлементов | |
| // Обновляем видимость элементов цен (аналог price() из легаси строки 1096-1144) | |
| const priceItemsCmplx = document.querySelector('#price_result_Cmplx')?.closest('.price-item'); | |
| const priceItemsFe = document.querySelector('#price_result_Fe')?.closest('.price-item'); | |
| const priceItemsMn = document.querySelector('#price_result_Mn')?.closest('.price-item'); | |
| const priceItemsB = document.querySelector('#price_result_B')?.closest('.price-item'); | |
| const priceItemsZn = document.querySelector('#price_result_Zn')?.closest('.price-item'); | |
| const priceItemsCu = document.querySelector('#price_result_Cu')?.closest('.price-item'); | |
| const priceItemsMo = document.querySelector('#price_result_Mo')?.closest('.price-item'); | |
| const priceItemsCo = document.querySelector('#price_result_Co')?.closest('.price-item'); | |
| const priceItemsSi = document.querySelector('#price_result_Si')?.closest('.price-item'); | |
| if (checkbox.checked) { | |
| // Показываем комплекс, скрываем отдельные микроэлементы | |
| if (priceItemsCmplx) priceItemsCmplx.style.display = 'flex'; | |
| if (priceItemsFe) priceItemsFe.style.display = 'none'; | |
| if (priceItemsMn) priceItemsMn.style.display = 'none'; | |
| if (priceItemsB) priceItemsB.style.display = 'none'; | |
| if (priceItemsZn) priceItemsZn.style.display = 'none'; | |
| if (priceItemsCu) priceItemsCu.style.display = 'none'; | |
| if (priceItemsMo) priceItemsMo.style.display = 'none'; | |
| if (priceItemsCo) priceItemsCo.style.display = 'none'; | |
| if (priceItemsSi) priceItemsSi.style.display = 'none'; | |
| } else { | |
| // Скрываем комплекс, показываем отдельные микроэлементы | |
| if (priceItemsCmplx) priceItemsCmplx.style.display = 'none'; | |
| if (priceItemsFe) priceItemsFe.style.display = 'flex'; | |
| if (priceItemsMn) priceItemsMn.style.display = 'flex'; | |
| if (priceItemsB) priceItemsB.style.display = 'flex'; | |
| if (priceItemsZn) priceItemsZn.style.display = 'flex'; | |
| if (priceItemsCu) priceItemsCu.style.display = 'flex'; | |
| if (priceItemsMo) priceItemsMo.style.display = 'flex'; | |
| if (priceItemsCo) priceItemsCo.style.display = 'flex'; | |
| if (priceItemsSi) priceItemsSi.style.display = 'flex'; | |
| } | |
| // Обновляем цены после изменения видимости | |
| updatePriceResults(); | |
| } | |
| function onMicroVolumeChange() { | |
| microToWeght(); | |
| } | |
| function onTotalMicroGramsChange() { | |
| // При изменении общих граммов в режиме комплекса пересчитываем B | |
| if (document.getElementById('use_complex').checked) { | |
| const totalGrams = parseFloat(document.getElementById('total_micro_grams').value) || 0; | |
| const V = parseFloat(document.getElementById('volume').value) || 10; | |
| const pB = parseFloat(document.getElementById('micro_B_percent').value) || 1; | |
| if (totalGrams > 0 && V > 0 && pB > 0) { | |
| // Пересчитываем B из общих граммов: B = gCmplx * dB * 10000 / V | |
| const B = (totalGrams * pB * 10000) / V; | |
| document.getElementById('B').value = Math.round(B); | |
| // Обновляем остальные расчеты | |
| microToWeght(); | |
| updateProfileString(); | |
| updateMicroNames(); // Обновляем названия микроэлементов | |
| calcConcentrates(); // Обновляем концентраты и изготовление | |
| } | |
| } | |
| } | |
| function microToWeght() { | |
| // Точная копия логики из легаси procedure microToWeght | |
| const V = parseFloat(document.getElementById('volume').value) || 10; | |
| const Fe = parseFloat(document.getElementById('Fe').value) || 0; | |
| const Mn = parseFloat(document.getElementById('Mn').value) || 0; | |
| const B = parseFloat(document.getElementById('B').value) || 0; | |
| const Zn = parseFloat(document.getElementById('Zn').value) || 0; | |
| const Cu = parseFloat(document.getElementById('Cu').value) || 0; | |
| const Mo = parseFloat(document.getElementById('Mo').value) || 0; | |
| const Co = parseFloat(document.getElementById('Co').value) || 0; | |
| const Si = parseFloat(document.getElementById('Si').value) || 0; | |
| const pFe = parseFloat(document.getElementById('micro_Fe_percent').value) || 0; | |
| const pMn = parseFloat(document.getElementById('micro_Mn_percent').value) || 0; | |
| const pB = parseFloat(document.getElementById('micro_B_percent').value) || 0; | |
| const pZn = parseFloat(document.getElementById('micro_Zn_percent').value) || 0; | |
| const pCu = parseFloat(document.getElementById('micro_Cu_percent').value) || 0; | |
| const pMo = parseFloat(document.getElementById('micro_Mo_percent').value) || 0; | |
| const pCo = parseFloat(document.getElementById('micro_Co_percent').value) || 0; | |
| const pSi = parseFloat(document.getElementById('micro_Si_percent').value) || 0; | |
| const isComplexMode = document.getElementById('use_complex').checked; | |
| if (!isComplexMode) { | |
| // Обычный режим - как в легаси if (kF.chKComplex.Checked = False) | |
| // if Kf.dFe.value >0 then Kf.gFe.value:=Kf.Fe.value/Kf.dFe.value*Kf.V.value/10000 | |
| document.getElementById('g_Fe').value = (pFe > 0 ? (Fe / pFe * V / 10000).toFixed(5) : '0.00000'); | |
| document.getElementById('g_Mn').value = (pMn > 0 ? (Mn / pMn * V / 10000).toFixed(5) : '0.00000'); | |
| document.getElementById('g_B').value = (pB > 0 ? (B / pB * V / 10000).toFixed(5) : '0.00000'); | |
| document.getElementById('g_Zn').value = (pZn > 0 ? (Zn / pZn * V / 10000).toFixed(5) : '0.00000'); | |
| document.getElementById('g_Cu').value = (pCu > 0 ? (Cu / pCu * V / 10000).toFixed(5) : '0.00000'); | |
| document.getElementById('g_Mo').value = (pMo > 0 ? (Mo / pMo * V / 10000).toFixed(5) : '0.00000'); | |
| document.getElementById('g_Co').value = (pCo > 0 ? (Co / pCo * V / 10000).toFixed(5) : '0.00000'); | |
| document.getElementById('g_Si').value = (pSi > 0 ? (Si / pSi * V / 10000).toFixed(5) : '0.00000'); | |
| toMicrocomplex(); | |
| } else { | |
| // Режим комплекса - как в легаси else | |
| // if Kf.dB.value >0 then Kf.gCmplx.value:=Kf.B.value/Kf.dB.value*Kf.V.value/10000 | |
| const complexValue = pB > 0 ? (B / pB * V / 10000) : 0; | |
| gmSUM = complexValue; | |
| document.getElementById('total_micro_grams').value = complexValue.toFixed(5); | |
| // Пересчитываем микроэлементы из комплекса | |
| // Kf.Fe.value:=10000*Kf.gCmplx.value* (Kf.dFe.value/Kf.V.value) | |
| if (complexValue > 0) { | |
| document.getElementById('Fe').value = Math.round(10000 * complexValue * (pFe / V)); | |
| document.getElementById('Mn').value = Math.round(10000 * complexValue * (pMn / V)); | |
| // B остается как есть | |
| document.getElementById('Zn').value = Math.round(10000 * complexValue * (pZn / V)); | |
| document.getElementById('Cu').value = Math.round(10000 * complexValue * (pCu / V)); | |
| document.getElementById('Mo').value = Math.round(10000 * complexValue * (pMo / V)); | |
| document.getElementById('Co').value = Math.round(10000 * complexValue * (pCo / V)); | |
| document.getElementById('Si').value = Math.round(10000 * complexValue * (pSi / V)); | |
| } | |
| } | |
| // Всегда обновляем отображение общих граммов | |
| if (isComplexMode) { | |
| document.getElementById('total_micro_grams').value = gmSUM.toFixed(5); | |
| } else { | |
| document.getElementById('total_micro_text').textContent = gmSUM.toFixed(5); | |
| } | |
| // Обновление состава - используем глобальные agXX значения как в легаси | |
| // Формула из легаси: round(agFe*1000)/1000 - округление до 3 знаков | |
| const composition = `Состав: Fe=${(Math.round(agFe*1000)/1000).toFixed(3)}% Mn=${(Math.round(agMn*1000)/1000).toFixed(3)}% B=${(Math.round(agB*1000)/1000).toFixed(3)}% Zn=${(Math.round(agZn*1000)/1000).toFixed(3)}% Cu=${(Math.round(agCu*1000)/1000).toFixed(3)}% Mo=${(Math.round(agMo*1000)/1000).toFixed(3)}% Co=${(Math.round(agCo*1000)/1000).toFixed(3)}% Si=${(Math.round(agSi*1000)/1000).toFixed(3)}%`; | |
| document.getElementById('micro-composition').textContent = composition; | |
| // Разведение | |
| const microVol = parseFloat(document.getElementById('micro_volume').value) || 500; | |
| if (microVol > 0 && gmSUM > 0) { | |
| const concentration = (gmSUM * 1000 / microVol).toFixed(2); | |
| const dilution = Math.round(1000 * V / microVol); | |
| const usage = Math.round(microVol / V); | |
| document.getElementById('micro-dilution-info').textContent = | |
| `Концентрация: ${concentration} г/л, Кратность: ${dilution}:1, Расход: ${usage} мл/л раствора`; | |
| } | |
| } | |
| // Обратная функция: пересчет из граммов солей В микроэлементы (аналог WeghtTomicro из легаси строки 1960-1987) | |
| function weightToMicro() { | |
| const V = parseFloat(document.getElementById('volume').value) || 10; | |
| const gFe = parseFloat(document.getElementById('g_Fe').value) || 0; | |
| const gMn = parseFloat(document.getElementById('g_Mn').value) || 0; | |
| const gB = parseFloat(document.getElementById('g_B').value) || 0; | |
| const gZn = parseFloat(document.getElementById('g_Zn').value) || 0; | |
| const gCu = parseFloat(document.getElementById('g_Cu').value) || 0; | |
| const gMo = parseFloat(document.getElementById('g_Mo').value) || 0; | |
| const gCo = parseFloat(document.getElementById('g_Co').value) || 0; | |
| const gSi = parseFloat(document.getElementById('g_Si').value) || 0; | |
| const dFe = parseFloat(document.getElementById('micro_Fe_percent').value) || 0; | |
| const dMn = parseFloat(document.getElementById('micro_Mn_percent').value) || 0; | |
| const dB = parseFloat(document.getElementById('micro_B_percent').value) || 0; | |
| const dZn = parseFloat(document.getElementById('micro_Zn_percent').value) || 0; | |
| const dCu = parseFloat(document.getElementById('micro_Cu_percent').value) || 0; | |
| const dMo = parseFloat(document.getElementById('micro_Mo_percent').value) || 0; | |
| const dCo = parseFloat(document.getElementById('micro_Co_percent').value) || 0; | |
| const dSi = parseFloat(document.getElementById('micro_Si_percent').value) || 0; | |
| const isComplexMode = document.getElementById('use_complex').checked; | |
| if (!isComplexMode) { | |
| // Обычный режим: Kf.Fe.value:=10000*Kf.gFe.value* (Kf.dFe.value/Kf.V.value) | |
| document.getElementById('Fe').value = Math.round(10000 * gFe * (dFe / V)); | |
| document.getElementById('Mn').value = Math.round(10000 * gMn * (dMn / V)); | |
| document.getElementById('B').value = Math.round(10000 * gB * (dB / V)); | |
| document.getElementById('Zn').value = Math.round(10000 * gZn * (dZn / V)); | |
| document.getElementById('Cu').value = Math.round(10000 * gCu * (dCu / V)); | |
| document.getElementById('Mo').value = Math.round(10000 * gMo * (dMo / V)); | |
| document.getElementById('Co').value = Math.round(10000 * gCo * (dCo / V)); | |
| document.getElementById('Si').value = Math.round(10000 * gSi * (dSi / V)); | |
| } else { | |
| // Режим комплекса: используем gCmplx | |
| const gCmplx = parseFloat(document.getElementById('total_micro_grams').value) || 0; | |
| document.getElementById('Fe').value = Math.round(10000 * gCmplx * (dFe / V)); | |
| document.getElementById('Mn').value = Math.round(10000 * gCmplx * (dMn / V)); | |
| document.getElementById('B').value = Math.round(10000 * gCmplx * (dB / V)); | |
| document.getElementById('Zn').value = Math.round(10000 * gCmplx * (dZn / V)); | |
| document.getElementById('Cu').value = Math.round(10000 * gCmplx * (dCu / V)); | |
| document.getElementById('Mo').value = Math.round(10000 * gCmplx * (dMo / V)); | |
| document.getElementById('Co').value = Math.round(10000 * gCmplx * (dCo / V)); | |
| document.getElementById('Si').value = Math.round(10000 * gCmplx * (dSi / V)); | |
| } | |
| // Вызываем genProfile для обновления строки профиля (как в легаси строка 1986) | |
| updateProfileString(); | |
| } | |
| // Работа с профилями | |
| function saveProfile() { | |
| getValues(); | |
| const profileData = JSON.stringify(currentProfile, null, 2); | |
| document.getElementById('profile-data').value = profileData; | |
| // Сохранение в localStorage | |
| localStorage.setItem('hpg_profile', profileData); | |
| alert('Профиль сохранен в браузере'); | |
| } | |
| function loadProfile() { | |
| const savedProfile = localStorage.getItem('hpg_profile'); | |
| if (savedProfile) { | |
| try { | |
| currentProfile = JSON.parse(savedProfile); | |
| applyProfile(); | |
| alert('Профиль загружен'); | |
| } catch (e) { | |
| alert('Ошибка загрузки профиля'); | |
| } | |
| } else { | |
| alert('Сохраненный профиль не найден'); | |
| } | |
| } | |
| // ========== РАБОТА С ПОЛНЫМ СОСТОЯНИЕМ (как в легаси SaveFile/LoadFirt/loadPrf) ========== | |
| // Сохранение полного состояния (профиль + составы + настройки) | |
| // Аналог SaveFile в легаси - сохраняет ВСЁ в localStorage['hpg_full_state'] | |
| function saveFullState() { | |
| const fullState = { | |
| // Профиль | |
| profile: { | |
| N: parseFloat(document.getElementById('N')?.value) || 0, | |
| NO3: parseFloat(document.getElementById('NO3')?.value) || 0, | |
| NH4: parseFloat(document.getElementById('NH4')?.value) || 0, | |
| P: parseFloat(document.getElementById('P')?.value) || 0, | |
| K: parseFloat(document.getElementById('K')?.value) || 0, | |
| Ca: parseFloat(document.getElementById('Ca')?.value) || 0, | |
| Mg: parseFloat(document.getElementById('Mg')?.value) || 0, | |
| S: parseFloat(document.getElementById('S')?.value) || 0, | |
| Cl: parseFloat(document.getElementById('Cl')?.value) || 0, | |
| EC: parseFloat(document.getElementById('EC')?.value) || 0, | |
| Fe: parseFloat(document.getElementById('Fe')?.value) || 0, | |
| Mn: parseFloat(document.getElementById('Mn')?.value) || 0, | |
| B: parseFloat(document.getElementById('B')?.value) || 0, | |
| Zn: parseFloat(document.getElementById('Zn')?.value) || 0, | |
| Cu: parseFloat(document.getElementById('Cu')?.value) || 0, | |
| Mo: parseFloat(document.getElementById('Mo')?.value) || 0, | |
| Co: parseFloat(document.getElementById('Co')?.value) || 0, | |
| Si: parseFloat(document.getElementById('Si')?.value) || 0, | |
| volume: parseFloat(document.getElementById('volume')?.value) || 10 | |
| }, | |
| // Составы солей (%) | |
| saltCompositions: { | |
| CaNO3: { | |
| Ca: parseFloat(document.getElementById('salt_CaNO3_Ca')?.value) || 0, | |
| NO3: parseFloat(document.getElementById('salt_CaNO3_NO3')?.value) || 0, | |
| NH4: parseFloat(document.getElementById('salt_CaNO3_NH4')?.value) || 0 | |
| }, | |
| KNO3: { | |
| K: parseFloat(document.getElementById('salt_KNO3_K')?.value) || 0, | |
| NO3: parseFloat(document.getElementById('salt_KNO3_NO3')?.value) || 0 | |
| }, | |
| NH4NO3: { | |
| NH4: parseFloat(document.getElementById('salt_NH4NO3_NH4')?.value) || 0, | |
| NO3: parseFloat(document.getElementById('salt_NH4NO3_NO3')?.value) || 0 | |
| }, | |
| MgSO4: { | |
| Mg: parseFloat(document.getElementById('salt_MgSO4_Mg')?.value) || 0, | |
| S: parseFloat(document.getElementById('salt_MgSO4_S')?.value) || 0 | |
| }, | |
| KH2PO4: { | |
| K: parseFloat(document.getElementById('salt_KH2PO4_K')?.value) || 0, | |
| P: parseFloat(document.getElementById('salt_KH2PO4_P')?.value) || 0 | |
| }, | |
| K2SO4: { | |
| K: parseFloat(document.getElementById('salt_K2SO4_K')?.value) || 0, | |
| S: parseFloat(document.getElementById('salt_K2SO4_S')?.value) || 0 | |
| }, | |
| MgNO3: { | |
| Mg: parseFloat(document.getElementById('salt_MgNO3_Mg')?.value) || 0, | |
| NO3: parseFloat(document.getElementById('salt_MgNO3_NO3')?.value) || 0 | |
| }, | |
| CaCl2: { | |
| Ca: parseFloat(document.getElementById('salt_CaCl2_Ca')?.value) || 0, | |
| Cl: parseFloat(document.getElementById('salt_CaCl2_Cl')?.value) || 0 | |
| } | |
| }, | |
| // Доли микроэлементов (%) | |
| microCompositions: { | |
| Fe: parseFloat(document.getElementById('micro_Fe_percent')?.value) || 0, | |
| Mn: parseFloat(document.getElementById('micro_Mn_percent')?.value) || 0, | |
| B: parseFloat(document.getElementById('micro_B_percent')?.value) || 0, | |
| Zn: parseFloat(document.getElementById('micro_Zn_percent')?.value) || 0, | |
| Cu: parseFloat(document.getElementById('micro_Cu_percent')?.value) || 0, | |
| Mo: parseFloat(document.getElementById('micro_Mo_percent')?.value) || 0, | |
| Co: parseFloat(document.getElementById('micro_Co_percent')?.value) || 0, | |
| Si: parseFloat(document.getElementById('micro_Si_percent')?.value) || 0 | |
| }, | |
| // Концентрации (г/л) | |
| concentrations: { | |
| glCaNO3: parseFloat(document.getElementById('conc_glCaNO3')?.value) || 0, | |
| glKNO3: parseFloat(document.getElementById('conc_glKNO3')?.value) || 0, | |
| glNH4NO3: parseFloat(document.getElementById('conc_glNH4NO3')?.value) || 0, | |
| glMgNO3: parseFloat(document.getElementById('conc_glMgNO3')?.value) || 0, | |
| glMgSO4: parseFloat(document.getElementById('conc_glMgSO4')?.value) || 0, | |
| glKH2PO4: parseFloat(document.getElementById('conc_glKH2PO4')?.value) || 0, | |
| glK2SO4: parseFloat(document.getElementById('conc_glK2SO4')?.value) || 0, | |
| glCaCl2: parseFloat(document.getElementById('conc_glCaCl2')?.value) || 0, | |
| glCmplx: parseFloat(document.getElementById('conc_glCmplx')?.value) || 0, | |
| glFe: parseFloat(document.getElementById('conc_glFe')?.value) || 0, | |
| glMn: parseFloat(document.getElementById('conc_glMn')?.value) || 0, | |
| glB: parseFloat(document.getElementById('conc_glB')?.value) || 0, | |
| glZn: parseFloat(document.getElementById('conc_glZn')?.value) || 0, | |
| glCu: parseFloat(document.getElementById('conc_glCu')?.value) || 0, | |
| glMo: parseFloat(document.getElementById('conc_glMo')?.value) || 0, | |
| glCo: parseFloat(document.getElementById('conc_glCo')?.value) || 0, | |
| glSi: parseFloat(document.getElementById('conc_glSi')?.value) || 0 | |
| }, | |
| // Плотности (г/мл) | |
| densities: { | |
| gmlCaNO3: parseFloat(document.getElementById('conc_gmlCaNO3')?.value) || 0, | |
| gmlKNO3: parseFloat(document.getElementById('conc_gmlKNO3')?.value) || 0, | |
| gmlNH4NO3: parseFloat(document.getElementById('conc_gmlNH4NO3')?.value) || 0, | |
| gmlMgNO3: parseFloat(document.getElementById('conc_gmlMgNO3')?.value) || 0, | |
| gmlMgSO4: parseFloat(document.getElementById('conc_gmlMgSO4')?.value) || 0, | |
| gmlKH2PO4: parseFloat(document.getElementById('conc_gmlKH2PO4')?.value) || 0, | |
| gmlK2SO4: parseFloat(document.getElementById('conc_gmlK2SO4')?.value) || 0, | |
| gmlCaCl2: parseFloat(document.getElementById('conc_gmlCaCl2')?.value) || 0, | |
| gmlCmplx: parseFloat(document.getElementById('conc_gmlCmplx')?.value) || 0, | |
| gmlFe: parseFloat(document.getElementById('conc_gmlFe')?.value) || 0, | |
| gmlMn: parseFloat(document.getElementById('conc_gmlMn')?.value) || 0, | |
| gmlB: parseFloat(document.getElementById('conc_gmlB')?.value) || 0, | |
| gmlZn: parseFloat(document.getElementById('conc_gmlZn')?.value) || 0, | |
| gmlCu: parseFloat(document.getElementById('conc_gmlCu')?.value) || 0, | |
| gmlMo: parseFloat(document.getElementById('conc_gmlMo')?.value) || 0, | |
| gmlCo: parseFloat(document.getElementById('conc_gmlCo')?.value) || 0, | |
| gmlSi: parseFloat(document.getElementById('conc_gmlSi')?.value) || 0 | |
| }, | |
| // Цены | |
| prices: { | |
| CaNO3: parseFloat(document.getElementById('price_CaNO3')?.value) || 0, | |
| KNO3: parseFloat(document.getElementById('price_KNO3')?.value) || 0, | |
| NH4NO3: parseFloat(document.getElementById('price_NH4NO3')?.value) || 0, | |
| MgNO3: parseFloat(document.getElementById('price_MgNO3')?.value) || 0, | |
| MgSO4: parseFloat(document.getElementById('price_MgSO4')?.value) || 0, | |
| KH2PO4: parseFloat(document.getElementById('price_KH2PO4')?.value) || 0, | |
| K2SO4: parseFloat(document.getElementById('price_K2SO4')?.value) || 0, | |
| CaCl2: parseFloat(document.getElementById('price_CaCl2')?.value) || 0, | |
| Fe: parseFloat(document.getElementById('price_Fe')?.value) || 0, | |
| Mn: parseFloat(document.getElementById('price_Mn')?.value) || 0, | |
| B: parseFloat(document.getElementById('price_B')?.value) || 0, | |
| Zn: parseFloat(document.getElementById('price_Zn')?.value) || 0, | |
| Cu: parseFloat(document.getElementById('price_Cu')?.value) || 0, | |
| Mo: parseFloat(document.getElementById('price_Mo')?.value) || 0, | |
| Co: parseFloat(document.getElementById('price_Co')?.value) || 0, | |
| Si: parseFloat(document.getElementById('price_Si')?.value) || 0, | |
| Cmplx: parseFloat(document.getElementById('price_Cmplx')?.value) || 0 | |
| }, | |
| // Чекбоксы | |
| flags: { | |
| use_K2SO4: document.getElementById('use_K2SO4')?.checked || false, | |
| use_MgNO3: document.getElementById('use_MgNO3')?.checked || false, | |
| use_complex: document.getElementById('use_complex')?.checked || false | |
| }, | |
| // Названия помп | |
| pumpNames: { | |
| mCaNO3: document.getElementById('mCaNO3')?.value || '', | |
| mKNO3: document.getElementById('mKNO3')?.value || '', | |
| mNH4NO3: document.getElementById('mNH4NO3')?.value || '', | |
| mMgNO3: document.getElementById('mMgNO3')?.value || '', | |
| mCaCl2: document.getElementById('mCaCl2')?.value || '', | |
| mMgSO4: document.getElementById('mMgSO4')?.value || '', | |
| mKH2PO4: document.getElementById('mKH2PO4')?.value || '', | |
| mK2SO4: document.getElementById('mK2SO4')?.value || '', | |
| mFe: document.getElementById('mFe')?.value || '', | |
| mMn: document.getElementById('mMn')?.value || '', | |
| mB: document.getElementById('mB')?.value || '', | |
| mZn: document.getElementById('mZn')?.value || '', | |
| mCu: document.getElementById('mCu')?.value || '', | |
| mMo: document.getElementById('mMo')?.value || '', | |
| mCo: document.getElementById('mCo')?.value || '', | |
| mSi: document.getElementById('mSi')?.value || '', | |
| mCmplx: document.getElementById('mCmplx')?.value || '' | |
| }, | |
| // Настройки миксера | |
| mixer: { | |
| addrMixer: document.getElementById('addrMixer')?.value || 'mixer.local', | |
| nmix: document.getElementById('nmix')?.value || '1' | |
| }, | |
| // Объемы баков | |
| tanks: { | |
| tank_A: parseFloat(document.getElementById('tank_A')?.value) || 500, | |
| tank_B: parseFloat(document.getElementById('tank_B')?.value) || 500 | |
| }, | |
| // Метаданные | |
| meta: { | |
| filename: document.getElementById('file_filename')?.value || 'default.hpg', | |
| comment: document.getElementById('file_comment')?.value || 'По умолчанию' | |
| } | |
| }; | |
| // Сохраняем в localStorage | |
| localStorage.setItem('hpg_full_state', JSON.stringify(fullState)); | |
| alert('Все настройки сохранены в браузере'); | |
| } | |
| // Загрузка только составов и настроек (НЕ трогая профиль) | |
| // Аналог LoadFirt в легаси - загружает составы солей, концентрации, цены, чекбоксы и т.д. | |
| // НЕ трогает целевой профиль (N, P, K, Fe и т.д.) | |
| function loadCompositions() { | |
| const savedState = localStorage.getItem('hpg_full_state'); | |
| if (!savedState) { | |
| alert('Сохраненные настройки не найдены. Сначала нажмите "Сохранить"'); | |
| return; | |
| } | |
| try { | |
| const state = JSON.parse(savedState); | |
| // Загружаем составы солей | |
| if (state.saltCompositions) { | |
| Object.keys(state.saltCompositions).forEach(salt => { | |
| Object.keys(state.saltCompositions[salt]).forEach(element => { | |
| const id = `salt_${salt}_${element}`; | |
| const el = document.getElementById(id); | |
| if (el) el.value = state.saltCompositions[salt][element]; | |
| }); | |
| }); | |
| } | |
| // Загружаем доли микроэлементов | |
| if (state.microCompositions) { | |
| Object.keys(state.microCompositions).forEach(element => { | |
| const el = document.getElementById(`micro_${element}_percent`); | |
| if (el) el.value = state.microCompositions[element]; | |
| }); | |
| } | |
| // Загружаем концентрации | |
| if (state.concentrations) { | |
| Object.keys(state.concentrations).forEach(key => { | |
| const el = document.getElementById(`conc_${key}`); | |
| if (el) el.value = state.concentrations[key]; | |
| }); | |
| } | |
| // Загружаем плотности | |
| if (state.densities) { | |
| Object.keys(state.densities).forEach(key => { | |
| const el = document.getElementById(`conc_${key}`); | |
| if (el) el.value = state.densities[key]; | |
| }); | |
| } | |
| // Загружаем цены | |
| if (state.prices) { | |
| Object.keys(state.prices).forEach(key => { | |
| const el = document.getElementById(`price_${key}`); | |
| if (el) el.value = state.prices[key]; | |
| }); | |
| } | |
| // Загружаем чекбоксы | |
| if (state.flags) { | |
| if (state.flags.use_K2SO4 !== undefined) { | |
| const el = document.getElementById('use_K2SO4'); | |
| if (el) { | |
| el.checked = state.flags.use_K2SO4; | |
| useK2SO4 = state.flags.use_K2SO4; | |
| } | |
| } | |
| if (state.flags.use_MgNO3 !== undefined) { | |
| const el = document.getElementById('use_MgNO3'); | |
| if (el) { | |
| el.checked = state.flags.use_MgNO3; | |
| useMgNO3 = state.flags.use_MgNO3; | |
| } | |
| } | |
| if (state.flags.use_complex !== undefined) { | |
| const el = document.getElementById('use_complex'); | |
| if (el) el.checked = state.flags.use_complex; | |
| } | |
| } | |
| // Загружаем названия помп | |
| if (state.pumpNames) { | |
| Object.keys(state.pumpNames).forEach(key => { | |
| const el = document.getElementById(key); | |
| if (el) el.value = state.pumpNames[key]; | |
| }); | |
| } | |
| // Загружаем настройки миксера | |
| if (state.mixer) { | |
| if (state.mixer.addrMixer) { | |
| const el = document.getElementById('addrMixer'); | |
| if (el) el.value = state.mixer.addrMixer; | |
| } | |
| if (state.mixer.nmix) { | |
| const el = document.getElementById('nmix'); | |
| if (el) el.value = state.mixer.nmix; | |
| } | |
| } | |
| // Загружаем объемы баков | |
| if (state.tanks) { | |
| if (state.tanks.tank_A) { | |
| const el = document.getElementById('tank_A'); | |
| if (el) el.value = state.tanks.tank_A; | |
| } | |
| if (state.tanks.tank_B) { | |
| const el = document.getElementById('tank_B'); | |
| if (el) el.value = state.tanks.tank_B; | |
| } | |
| } | |
| // Синхронизируем составы солей с глобальной переменной | |
| syncSaltCompositions(); | |
| updateSaltLabels(); | |
| updateSaltNames(); | |
| updateMicroNames(); | |
| // Пересчитываем (как в легаси LoadFirt) | |
| calcAll(); | |
| onMicroChange(); | |
| calcConcentrates(); | |
| updatePriceResults(); | |
| alert('Составы и настройки загружены'); | |
| } catch (e) { | |
| console.error('Ошибка загрузки составов:', e); | |
| alert('Ошибка загрузки составов'); | |
| } | |
| } | |
| // Загрузка только профиля (НЕ трогая составы и настройки) | |
| // Аналог loadPrf в легаси - загружает только целевой профиль (N, P, K, Fe и т.д.) | |
| // НЕ трогает составы солей, концентрации, цены и другие настройки | |
| function loadProfileOnly() { | |
| const savedState = localStorage.getItem('hpg_full_state'); | |
| if (!savedState) { | |
| alert('Сохраненный профиль не найден. Сначала нажмите "Сохранить"'); | |
| return; | |
| } | |
| try { | |
| const state = JSON.parse(savedState); | |
| if (!state.profile) { | |
| alert('Профиль не найден в сохраненных данных'); | |
| return; | |
| } | |
| // Загружаем только профиль | |
| const profile = state.profile; | |
| // Макропрофиль | |
| if (profile.N !== undefined) document.getElementById('N').value = profile.N; | |
| if (profile.NO3 !== undefined) document.getElementById('NO3').value = profile.NO3; | |
| if (profile.NH4 !== undefined) document.getElementById('NH4').value = profile.NH4; | |
| if (profile.P !== undefined) document.getElementById('P').value = profile.P; | |
| if (profile.K !== undefined) document.getElementById('K').value = profile.K; | |
| if (profile.Ca !== undefined) document.getElementById('Ca').value = profile.Ca; | |
| if (profile.Mg !== undefined) document.getElementById('Mg').value = profile.Mg; | |
| if (profile.S !== undefined) document.getElementById('S').value = profile.S; | |
| if (profile.Cl !== undefined) document.getElementById('Cl').value = profile.Cl; | |
| // Микропрофиль | |
| if (profile.Fe !== undefined) document.getElementById('Fe').value = profile.Fe; | |
| if (profile.Mn !== undefined) document.getElementById('Mn').value = profile.Mn; | |
| if (profile.B !== undefined) document.getElementById('B').value = profile.B; | |
| if (profile.Zn !== undefined) document.getElementById('Zn').value = profile.Zn; | |
| if (profile.Cu !== undefined) document.getElementById('Cu').value = profile.Cu; | |
| if (profile.Mo !== undefined) document.getElementById('Mo').value = profile.Mo; | |
| if (profile.Co !== undefined) document.getElementById('Co').value = profile.Co; | |
| if (profile.Si !== undefined) document.getElementById('Si').value = profile.Si; | |
| // Объем | |
| if (profile.volume !== undefined) document.getElementById('volume').value = profile.volume; | |
| // Пересчитываем (как в легаси loadPrf) | |
| calcAll(); | |
| onMicroChange(); | |
| calcConcentrates(); | |
| updatePriceResults(); | |
| alert('Профиль загружен'); | |
| } catch (e) { | |
| console.error('Ошибка загрузки профиля:', e); | |
| alert('Ошибка загрузки профиля'); | |
| } | |
| } | |
| function exportProfile() { | |
| getValues(); | |
| updateProfileString(); | |
| const profileStr = document.getElementById('profile-string').textContent; | |
| const blob = new Blob([profileStr], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = document.getElementById('filename').value || 'profile.hpg'; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| } | |
| function importProfile() { | |
| const input = document.createElement('input'); | |
| input.type = 'file'; | |
| input.accept = '.hpg,.txt'; | |
| input.onchange = (e) => { | |
| const file = e.target.files[0]; | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| const content = event.target.result; | |
| parseProfileString(content); | |
| }; | |
| reader.readAsText(file); | |
| }; | |
| input.click(); | |
| } | |
| function parseProfileString(str) { | |
| // Парсинг строки профиля: N=220 NO3=200 NH4=20 P=40 ... | |
| const parts = str.trim().split(/\s+/); | |
| parts.forEach(part => { | |
| const [key, value] = part.split('='); | |
| const numValue = parseFloat(value); | |
| if (!isNaN(numValue)) { | |
| if (key === 'N') currentProfile.N = numValue; | |
| else if (key === 'NO3') currentProfile.NO3 = numValue; | |
| else if (key === 'NH4') currentProfile.NH4 = numValue; | |
| else if (key === 'P') currentProfile.P = numValue; | |
| else if (key === 'K') currentProfile.K = numValue; | |
| else if (key === 'Ca') currentProfile.Ca = numValue; | |
| else if (key === 'Mg') currentProfile.Mg = numValue; | |
| else if (key === 'S') currentProfile.S = numValue; | |
| else if (key === 'Cl') currentProfile.Cl = numValue; | |
| else if (key === 'Fe') currentProfile.Fe = numValue * 1000; | |
| else if (key === 'Mn') currentProfile.Mn = numValue * 1000; | |
| else if (key === 'B') currentProfile.B = numValue * 1000; | |
| else if (key === 'Zn') currentProfile.Zn = numValue * 1000; | |
| else if (key === 'Cu') currentProfile.Cu = numValue * 1000; | |
| else if (key === 'Mo') currentProfile.Mo = numValue * 1000; | |
| else if (key === 'Co') currentProfile.Co = numValue * 1000; | |
| else if (key === 'Si') currentProfile.Si = numValue * 1000; | |
| } | |
| }); | |
| applyProfile(); | |
| alert('Профиль импортирован'); | |
| } | |
| function applyProfile() { | |
| document.getElementById('N').value = currentProfile.N.toFixed(3); | |
| document.getElementById('P').value = currentProfile.P.toFixed(3); | |
| document.getElementById('K').value = currentProfile.K.toFixed(3); | |
| document.getElementById('Ca').value = currentProfile.Ca.toFixed(3); | |
| document.getElementById('Mg').value = currentProfile.Mg.toFixed(3); | |
| document.getElementById('S').value = currentProfile.S.toFixed(3); | |
| document.getElementById('Cl').value = currentProfile.Cl.toFixed(2); | |
| document.getElementById('EC').value = currentProfile.EC.toFixed(3); | |
| document.getElementById('NO3').value = currentProfile.NO3.toFixed(2); | |
| document.getElementById('NH4').value = currentProfile.NH4.toFixed(2); | |
| document.getElementById('Fe').value = currentProfile.Fe || 2000; | |
| document.getElementById('Mn').value = currentProfile.Mn || 550; | |
| document.getElementById('B').value = currentProfile.B || 500; | |
| document.getElementById('Zn').value = currentProfile.Zn || 330; | |
| document.getElementById('Cu').value = currentProfile.Cu || 63; | |
| document.getElementById('Mo').value = currentProfile.Mo || 63; | |
| document.getElementById('Co').value = currentProfile.Co || 0; | |
| document.getElementById('Si').value = currentProfile.Si || 0; | |
| document.getElementById('volume').value = currentProfile.volume || 10; | |
| onMacroChange(); | |
| onMicroChange(); | |
| } | |
| // ===== ФУНКЦИИ ВКЛАДКИ "ФАЙЛ" ===== | |
| // Массив для хранения записей журнала | |
| let journalEntries = []; | |
| function fileOpen() { | |
| const input = document.createElement('input'); | |
| input.type = 'file'; | |
| input.accept = '.hpg,.txt'; | |
| input.onchange = (e) => { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| document.getElementById('file_filename').value = file.name; | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| const content = event.target.result; | |
| parseFullFile(content); | |
| }; | |
| reader.readAsText(file); | |
| } | |
| }; | |
| input.click(); | |
| } | |
| // Парсинг ТОЛЬКО составов из файла (аналог LoadFirt в легаси) | |
| // НЕ трогает профиль (N, P, K, Fe и т.д.) | |
| function parseCompositionsOnly(content) { | |
| const lines = content.split('\n'); | |
| lines.forEach(line => { | |
| line = line.trim(); | |
| if (!line || !line.includes('=') || line.startsWith('#') || line.startsWith('date=')) return; | |
| const [key, value] = line.split('='); | |
| if (!value) return; | |
| const trimmedValue = value.trim(); | |
| const numValue = parseFloat(trimmedValue); | |
| // Составы солей (проценты макроэлементов) | |
| if (key === 'CaNO3_Ca' && !isNaN(numValue)) setSaltPercent('CaNO3', 'Ca', numValue); | |
| else if (key === 'CaNO3_NO3' && !isNaN(numValue)) setSaltPercent('CaNO3', 'NO3', numValue); | |
| else if (key === 'CaNO3_NH4' && !isNaN(numValue)) setSaltPercent('CaNO3', 'NH4', numValue); | |
| else if (key === 'KNO3_K' && !isNaN(numValue)) setSaltPercent('KNO3', 'K', numValue); | |
| else if (key === 'KNO3_NO3' && !isNaN(numValue)) setSaltPercent('KNO3', 'NO3', numValue); | |
| else if (key === 'NH4NO3_NH4' && !isNaN(numValue)) setSaltPercent('NH4NO3', 'NH4', numValue); | |
| else if (key === 'NH4NO3_NO3' && !isNaN(numValue)) setSaltPercent('NH4NO3', 'NO3', numValue); | |
| else if (key === 'MgSO4_Mg' && !isNaN(numValue)) setSaltPercent('MgSO4', 'Mg', numValue); | |
| else if (key === 'MgSO4_S' && !isNaN(numValue)) setSaltPercent('MgSO4', 'S', numValue); | |
| else if (key === 'KH2PO4_K' && !isNaN(numValue)) setSaltPercent('KH2PO4', 'K', numValue); | |
| else if (key === 'KH2PO4_P' && !isNaN(numValue)) setSaltPercent('KH2PO4', 'P', numValue); | |
| else if (key === 'K2SO4_K' && !isNaN(numValue)) setSaltPercent('K2SO4', 'K', numValue); | |
| else if (key === 'K2SO4_S' && !isNaN(numValue)) setSaltPercent('K2SO4', 'S', numValue); | |
| else if (key === 'MgNO3_Mg' && !isNaN(numValue)) setSaltPercent('MgNO3', 'Mg', numValue); | |
| else if (key === 'MgNO3_NO3' && !isNaN(numValue)) setSaltPercent('MgNO3', 'NO3', numValue); | |
| else if (key === 'CaCl2_Ca' && !isNaN(numValue)) setSaltPercent('CaCl2', 'Ca', numValue); | |
| else if (key === 'CaCl2_Cl' && !isNaN(numValue)) setSaltPercent('CaCl2', 'Cl', numValue); | |
| // Доли микроэлементов в солях | |
| else if (key === 'dFe' && !isNaN(numValue)) setInputValue('micro_Fe_percent', numValue, 4); | |
| else if (key === 'dMn' && !isNaN(numValue)) setInputValue('micro_Mn_percent', numValue, 4); | |
| else if (key === 'dB' && !isNaN(numValue)) setInputValue('micro_B_percent', numValue, 4); | |
| else if (key === 'dZn' && !isNaN(numValue)) setInputValue('micro_Zn_percent', numValue, 4); | |
| else if (key === 'dCu' && !isNaN(numValue)) setInputValue('micro_Cu_percent', numValue, 4); | |
| else if (key === 'dMo' && !isNaN(numValue)) setInputValue('micro_Mo_percent', numValue, 4); | |
| else if (key === 'dCo' && !isNaN(numValue)) setInputValue('micro_Co_percent', numValue, 4); | |
| else if (key === 'dSi' && !isNaN(numValue)) setInputValue('micro_Si_percent', numValue, 4); | |
| // Концентрации (г/л) | |
| else if (key === 'glCaNO3' && !isNaN(numValue)) setInputValue('conc_glCaNO3', numValue, 1); | |
| else if (key === 'glKNO3' && !isNaN(numValue)) setInputValue('conc_glKNO3', numValue, 1); | |
| else if (key === 'glNH4NO3' && !isNaN(numValue)) setInputValue('conc_glNH4NO3', numValue, 1); | |
| else if (key === 'glMgNO3' && !isNaN(numValue)) setInputValue('conc_glMgNO3', numValue, 1); | |
| else if (key === 'glMgSO4' && !isNaN(numValue)) setInputValue('conc_glMgSO4', numValue, 1); | |
| else if (key === 'glK2SO4' && !isNaN(numValue)) setInputValue('conc_glK2SO4', numValue, 1); | |
| else if (key === 'glKH2PO4' && !isNaN(numValue)) setInputValue('conc_glKH2PO4', numValue, 1); | |
| else if (key === 'glCaCl2' && !isNaN(numValue)) setInputValue('conc_glCaCl2', numValue, 1); | |
| else if (key === 'glCmplx' && !isNaN(numValue)) setInputValue('conc_glCmplx', numValue, 2); | |
| else if (key === 'glFe' && !isNaN(numValue)) setInputValue('conc_glFe', numValue, 2); | |
| else if (key === 'glMn' && !isNaN(numValue)) setInputValue('conc_glMn', numValue, 2); | |
| else if (key === 'glB' && !isNaN(numValue)) setInputValue('conc_glB', numValue, 2); | |
| else if (key === 'glZn' && !isNaN(numValue)) setInputValue('conc_glZn', numValue, 2); | |
| else if (key === 'glCu' && !isNaN(numValue)) setInputValue('conc_glCu', numValue, 2); | |
| else if (key === 'glMo' && !isNaN(numValue)) setInputValue('conc_glMo', numValue, 2); | |
| else if (key === 'glCo' && !isNaN(numValue)) setInputValue('conc_glCo', numValue, 2); | |
| else if (key === 'glSi' && !isNaN(numValue)) setInputValue('conc_glSi', numValue, 2); | |
| // Плотности (г/мл) | |
| else if (key === 'gmlCaNO3' && !isNaN(numValue)) setInputValue('conc_gmlCaNO3', numValue, 4); | |
| else if (key === 'gmlKNO3' && !isNaN(numValue)) setInputValue('conc_gmlKNO3', numValue, 4); | |
| else if (key === 'gmlNH4NO3' && !isNaN(numValue)) setInputValue('conc_gmlNH4NO3', numValue, 4); | |
| else if (key === 'gmlMgNO3' && !isNaN(numValue)) setInputValue('conc_gmlMgNO3', numValue, 4); | |
| else if (key === 'gmlMgSO4' && !isNaN(numValue)) setInputValue('conc_gmlMgSO4', numValue, 4); | |
| else if (key === 'gmlK2SO4' && !isNaN(numValue)) setInputValue('conc_gmlK2SO4', numValue, 4); | |
| else if (key === 'gmlKH2PO4' && !isNaN(numValue)) setInputValue('conc_gmlKH2PO4', numValue, 4); | |
| else if (key === 'gmlCaCl2' && !isNaN(numValue)) setInputValue('conc_gmlCaCl2', numValue, 4); | |
| else if (key === 'gmlCmplx' && !isNaN(numValue)) setInputValue('conc_gmlCmplx', numValue, 3); | |
| else if (key === 'gmlFe' && !isNaN(numValue)) setInputValue('conc_gmlFe', numValue, 3); | |
| else if (key === 'gmlMn' && !isNaN(numValue)) setInputValue('conc_gmlMn', numValue, 3); | |
| else if (key === 'gmlB' && !isNaN(numValue)) setInputValue('conc_gmlB', numValue, 3); | |
| else if (key === 'gmlZn' && !isNaN(numValue)) setInputValue('conc_gmlZn', numValue, 3); | |
| else if (key === 'gmlCu' && !isNaN(numValue)) setInputValue('conc_gmlCu', numValue, 3); | |
| else if (key === 'gmlMo' && !isNaN(numValue)) setInputValue('conc_gmlMo', numValue, 3); | |
| else if (key === 'gmlCo' && !isNaN(numValue)) setInputValue('conc_gmlCo', numValue, 3); | |
| else if (key === 'gmlSi' && !isNaN(numValue)) setInputValue('conc_gmlSi', numValue, 3); | |
| // Чекбоксы | |
| else if (key === 'chK2SO4') { | |
| const el = document.getElementById('use_K2SO4'); | |
| if (el) { | |
| el.checked = (trimmedValue.toLowerCase() === 'true'); | |
| useK2SO4 = el.checked; | |
| } | |
| } | |
| else if (key === 'chMgNO3') { | |
| const el = document.getElementById('use_MgNO3'); | |
| if (el) { | |
| el.checked = (trimmedValue.toLowerCase() === 'true'); | |
| useMgNO3 = el.checked; | |
| } | |
| } | |
| else if (key === 'chkComplex') { | |
| const el = document.getElementById('use_complex'); | |
| if (el) el.checked = (trimmedValue.toLowerCase() === 'true'); | |
| } | |
| // Названия солей (m) | |
| else if (key === 'mCaNO3') { const el = document.getElementById('mCaNO3'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mKNO3') { const el = document.getElementById('mKNO3'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mNH4NO3') { const el = document.getElementById('mNH4NO3'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mMgNO3') { const el = document.getElementById('mMgNO3'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mCaCl2') { const el = document.getElementById('mCaCl2'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mMgSO4') { const el = document.getElementById('mMgSO4'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mKH2PO4') { const el = document.getElementById('mKH2PO4'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mK2SO4') { const el = document.getElementById('mK2SO4'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mFe') { const el = document.getElementById('mFe'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mMn') { const el = document.getElementById('mMn'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mB') { const el = document.getElementById('mB'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mZn') { const el = document.getElementById('mZn'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mCu') { const el = document.getElementById('mCu'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mMo') { const el = document.getElementById('mMo'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mCo') { const el = document.getElementById('mCo'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mSi') { const el = document.getElementById('mSi'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mCmplx') { const el = document.getElementById('mCmplx'); if (el) el.value = trimmedValue; } | |
| // Цены | |
| else if (key === 'pCaNO3' && !isNaN(numValue)) setInputValue('price_CaNO3', numValue, 4); | |
| else if (key === 'pKNO3' && !isNaN(numValue)) setInputValue('price_KNO3', numValue, 4); | |
| else if (key === 'pNH4NO3' && !isNaN(numValue)) setInputValue('price_NH4NO3', numValue, 4); | |
| else if (key === 'pMgNO3' && !isNaN(numValue)) setInputValue('price_MgNO3', numValue, 4); | |
| else if (key === 'pMgSO4' && !isNaN(numValue)) setInputValue('price_MgSO4', numValue, 4); | |
| else if (key === 'pKH2PO4' && !isNaN(numValue)) setInputValue('price_KH2PO4', numValue, 4); | |
| else if (key === 'pK2SO4' && !isNaN(numValue)) setInputValue('price_K2SO4', numValue, 4); | |
| else if (key === 'pCaCl2' && !isNaN(numValue)) setInputValue('price_CaCl2', numValue, 4); | |
| else if (key === 'pFe' && !isNaN(numValue)) setInputValue('price_Fe', numValue, 4); | |
| else if (key === 'pMn' && !isNaN(numValue)) setInputValue('price_Mn', numValue, 4); | |
| else if (key === 'pB' && !isNaN(numValue)) setInputValue('price_B', numValue, 4); | |
| else if (key === 'pZn' && !isNaN(numValue)) setInputValue('price_Zn', numValue, 4); | |
| else if (key === 'pCu' && !isNaN(numValue)) setInputValue('price_Cu', numValue, 4); | |
| else if (key === 'pMo' && !isNaN(numValue)) setInputValue('price_Mo', numValue, 4); | |
| else if (key === 'pCo' && !isNaN(numValue)) setInputValue('price_Co', numValue, 4); | |
| else if (key === 'pSi' && !isNaN(numValue)) setInputValue('price_Si', numValue, 4); | |
| else if (key === 'pCmplx' && !isNaN(numValue)) setInputValue('price_Cmplx', numValue, 4); | |
| // Настройки миксера | |
| else if (key === 'addrMixer') { const el = document.getElementById('addrMixer'); if (el) el.value = trimmedValue; } | |
| else if (key === 'nmix') { const el = document.getElementById('nmix'); if (el) el.value = trimmedValue; } | |
| // Объемы баков | |
| else if (key === 'tank_A' && !isNaN(numValue)) setInputValue('tank_A', numValue, 0); | |
| else if (key === 'tank_B' && !isNaN(numValue)) setInputValue('tank_B', numValue, 0); | |
| }); | |
| // Синхронизируем и пересчитываем (как в легаси LoadFirt) | |
| syncSaltCompositions(); | |
| updateSaltLabels(); | |
| updateSaltNames(); | |
| updateMicroNames(); | |
| calcAll(); | |
| onMicroChange(); | |
| calcConcentrates(); | |
| updatePriceResults(); | |
| } | |
| // Парсинг ТОЛЬКО профиля из файла (аналог loadPrf в легаси) | |
| // НЕ трогает составы, концентрации, цены и другие настройки | |
| function parseProfileOnly(content) { | |
| const lines = content.split('\n'); | |
| lines.forEach(line => { | |
| line = line.trim(); | |
| if (!line || !line.includes('=') || line.startsWith('#') || line.startsWith('date=')) return; | |
| const [key, value] = line.split('='); | |
| if (!value) return; | |
| const numValue = parseFloat(value.trim()); | |
| // Макроэлементы | |
| if (key === 'N' && !isNaN(numValue)) setInputValue('N', numValue, 3); | |
| else if (key === 'NO3' && !isNaN(numValue)) setInputValue('NO3', numValue, 2); | |
| else if (key === 'NH4' && !isNaN(numValue)) setInputValue('NH4', numValue, 2); | |
| else if (key === 'P' && !isNaN(numValue)) setInputValue('P', numValue, 3); | |
| else if (key === 'K' && !isNaN(numValue)) setInputValue('K', numValue, 3); | |
| else if (key === 'Ca' && !isNaN(numValue)) setInputValue('Ca', numValue, 3); | |
| else if (key === 'Mg' && !isNaN(numValue)) setInputValue('Mg', numValue, 3); | |
| else if (key === 'S' && !isNaN(numValue)) setInputValue('S', numValue, 3); | |
| else if (key === 'Cl' && !isNaN(numValue)) setInputValue('Cl', numValue, 1); | |
| // Микроэлементы | |
| else if (key === 'Fe' && !isNaN(numValue)) setInputValue('Fe', numValue, 3); | |
| else if (key === 'Mn' && !isNaN(numValue)) setInputValue('Mn', numValue, 3); | |
| else if (key === 'B' && !isNaN(numValue)) setInputValue('B', numValue, 3); | |
| else if (key === 'Zn' && !isNaN(numValue)) setInputValue('Zn', numValue, 3); | |
| else if (key === 'Cu' && !isNaN(numValue)) setInputValue('Cu', numValue, 3); | |
| else if (key === 'Mo' && !isNaN(numValue)) setInputValue('Mo', numValue, 3); | |
| else if (key === 'Co' && !isNaN(numValue)) setInputValue('Co', numValue, 3); | |
| else if (key === 'Si' && !isNaN(numValue)) setInputValue('Si', numValue, 3); | |
| // Объем | |
| else if (key === 'V' && !isNaN(numValue)) setInputValue('volume', numValue, 1); | |
| }); | |
| // Пересчитываем (как в легаси loadPrf) | |
| calcAll(); | |
| onMicroChange(); | |
| calcConcentrates(); | |
| updatePriceResults(); | |
| } | |
| // Парсинг полного файла .hpg включая журнал | |
| function parseFullFile(content) { | |
| const lines = content.split('\n'); | |
| let comment = ''; | |
| const tempJournal = []; | |
| lines.forEach(line => { | |
| line = line.trim(); | |
| if (!line) return; | |
| // Парсинг комментария | |
| if (line.startsWith('Comment=')) { | |
| comment = line.substring(8); | |
| document.getElementById('file_comment').value = comment; | |
| } | |
| // Парсинг журнала | |
| else if (line.startsWith('date=')) { | |
| // Формат: date=2024-10-10;Описание;N=220 NO3=200... | |
| const parts = line.substring(5).split(';'); | |
| if (parts.length >= 3) { | |
| const date = parts[0]; | |
| const desc = parts[1]; | |
| const profileStr = parts[2]; | |
| // Парсим профиль из строки | |
| const profile = {}; | |
| const profileParts = profileStr.trim().split(/\s+/); | |
| profileParts.forEach(part => { | |
| const [key, value] = part.split('='); | |
| const numValue = parseFloat(value); | |
| if (!isNaN(numValue)) { | |
| profile[key] = numValue; | |
| } | |
| }); | |
| // Добавляем в временный журнал | |
| tempJournal.push({ | |
| date: date, | |
| desc: desc, | |
| memo: '', // В легаси формате нет отдельного memo | |
| profile: profileStr, | |
| fullProfile: profile | |
| }); | |
| } | |
| } | |
| // Парсинг параметров профиля | |
| else if (line.includes('=') && !line.startsWith('#')) { | |
| const [key, value] = line.split('='); | |
| if (!value) return; // Пропускаем если нет значения | |
| const trimmedValue = value.trim(); | |
| const numValue = parseFloat(trimmedValue); | |
| // Макроэлементы | |
| if (key === 'N' && !isNaN(numValue)) currentProfile.N = numValue; | |
| else if (key === 'NO3' && !isNaN(numValue)) currentProfile.NO3 = numValue; | |
| else if (key === 'NH4' && !isNaN(numValue)) currentProfile.NH4 = numValue; | |
| else if (key === 'P' && !isNaN(numValue)) currentProfile.P = numValue; | |
| else if (key === 'K' && !isNaN(numValue)) currentProfile.K = numValue; | |
| else if (key === 'Ca' && !isNaN(numValue)) currentProfile.Ca = numValue; | |
| else if (key === 'Mg' && !isNaN(numValue)) currentProfile.Mg = numValue; | |
| else if (key === 'S' && !isNaN(numValue)) currentProfile.S = numValue; | |
| else if (key === 'Cl' && !isNaN(numValue)) currentProfile.Cl = numValue; | |
| // Микроэлементы | |
| else if (key === 'Fe' && !isNaN(numValue)) currentProfile.Fe = numValue; | |
| else if (key === 'Mn' && !isNaN(numValue)) currentProfile.Mn = numValue; | |
| else if (key === 'B' && !isNaN(numValue)) currentProfile.B = numValue; | |
| else if (key === 'Zn' && !isNaN(numValue)) currentProfile.Zn = numValue; | |
| else if (key === 'Cu' && !isNaN(numValue)) currentProfile.Cu = numValue; | |
| else if (key === 'Mo' && !isNaN(numValue)) currentProfile.Mo = numValue; | |
| else if (key === 'Co' && !isNaN(numValue)) currentProfile.Co = numValue; | |
| else if (key === 'Si' && !isNaN(numValue)) currentProfile.Si = numValue; | |
| else if (key === 'V' && !isNaN(numValue)) currentProfile.volume = numValue; | |
| // Составы солей (проценты макроэлементов) | |
| else if (key === 'CaNO3_Ca' && !isNaN(numValue)) setSaltPercent('CaNO3', 'Ca', numValue); | |
| else if (key === 'CaNO3_NO3' && !isNaN(numValue)) setSaltPercent('CaNO3', 'NO3', numValue); | |
| else if (key === 'CaNO3_NH4' && !isNaN(numValue)) setSaltPercent('CaNO3', 'NH4', numValue); | |
| else if (key === 'KNO3_K' && !isNaN(numValue)) setSaltPercent('KNO3', 'K', numValue); | |
| else if (key === 'KNO3_NO3' && !isNaN(numValue)) setSaltPercent('KNO3', 'NO3', numValue); | |
| else if (key === 'NH4NO3_NH4' && !isNaN(numValue)) setSaltPercent('NH4NO3', 'NH4', numValue); | |
| else if (key === 'NH4NO3_NO3' && !isNaN(numValue)) setSaltPercent('NH4NO3', 'NO3', numValue); | |
| else if (key === 'MgSO4_Mg' && !isNaN(numValue)) setSaltPercent('MgSO4', 'Mg', numValue); | |
| else if (key === 'MgSO4_S' && !isNaN(numValue)) setSaltPercent('MgSO4', 'S', numValue); | |
| else if (key === 'KH2PO4_K' && !isNaN(numValue)) setSaltPercent('KH2PO4', 'K', numValue); | |
| else if (key === 'KH2PO4_P' && !isNaN(numValue)) setSaltPercent('KH2PO4', 'P', numValue); | |
| else if (key === 'K2SO4_K' && !isNaN(numValue)) setSaltPercent('K2SO4', 'K', numValue); | |
| else if (key === 'K2SO4_S' && !isNaN(numValue)) setSaltPercent('K2SO4', 'S', numValue); | |
| else if (key === 'MgNO3_Mg' && !isNaN(numValue)) setSaltPercent('MgNO3', 'Mg', numValue); | |
| else if (key === 'MgNO3_NO3' && !isNaN(numValue)) setSaltPercent('MgNO3', 'NO3', numValue); | |
| else if (key === 'CaCl2_Ca' && !isNaN(numValue)) setSaltPercent('CaCl2', 'Ca', numValue); | |
| else if (key === 'CaCl2_Cl' && !isNaN(numValue)) setSaltPercent('CaCl2', 'Cl', numValue); | |
| // Доли микроэлементов в солях | |
| else if (key === 'dFe' && !isNaN(numValue)) setInputValue('micro_Fe_percent', numValue, 4); | |
| else if (key === 'dMn' && !isNaN(numValue)) setInputValue('micro_Mn_percent', numValue, 4); | |
| else if (key === 'dB' && !isNaN(numValue)) setInputValue('micro_B_percent', numValue, 4); | |
| else if (key === 'dZn' && !isNaN(numValue)) setInputValue('micro_Zn_percent', numValue, 4); | |
| else if (key === 'dCu' && !isNaN(numValue)) setInputValue('micro_Cu_percent', numValue, 4); | |
| else if (key === 'dMo' && !isNaN(numValue)) setInputValue('micro_Mo_percent', numValue, 4); | |
| else if (key === 'dCo' && !isNaN(numValue)) setInputValue('micro_Co_percent', numValue, 4); | |
| else if (key === 'dSi' && !isNaN(numValue)) setInputValue('micro_Si_percent', numValue, 4); | |
| // Концентрации растворов (gl) | |
| else if (key === 'glCaNO3' && !isNaN(numValue)) setInputValue('conc_glCaNO3', numValue, 1); | |
| else if (key === 'glKNO3' && !isNaN(numValue)) setInputValue('conc_glKNO3', numValue, 1); | |
| else if (key === 'glNH4NO3' && !isNaN(numValue)) setInputValue('conc_glNH4NO3', numValue, 1); | |
| else if (key === 'glMgNO3' && !isNaN(numValue)) setInputValue('conc_glMgNO3', numValue, 1); | |
| else if (key === 'glMgSO4' && !isNaN(numValue)) setInputValue('conc_glMgSO4', numValue, 1); | |
| else if (key === 'glK2SO4' && !isNaN(numValue)) setInputValue('conc_glK2SO4', numValue, 1); | |
| else if (key === 'glKH2PO4' && !isNaN(numValue)) setInputValue('conc_glKH2PO4', numValue, 1); | |
| else if (key === 'glCaCl2' && !isNaN(numValue)) setInputValue('conc_glCaCl2', numValue, 1); | |
| else if (key === 'glCmplx' && !isNaN(numValue)) setInputValue('conc_glCmplx', numValue, 2); | |
| else if (key === 'glFe' && !isNaN(numValue)) setInputValue('conc_glFe', numValue, 2); | |
| else if (key === 'glMn' && !isNaN(numValue)) setInputValue('conc_glMn', numValue, 2); | |
| else if (key === 'glB' && !isNaN(numValue)) setInputValue('conc_glB', numValue, 2); | |
| else if (key === 'glZn' && !isNaN(numValue)) setInputValue('conc_glZn', numValue, 2); | |
| else if (key === 'glCu' && !isNaN(numValue)) setInputValue('conc_glCu', numValue, 2); | |
| else if (key === 'glMo' && !isNaN(numValue)) setInputValue('conc_glMo', numValue, 2); | |
| else if (key === 'glCo' && !isNaN(numValue)) setInputValue('conc_glCo', numValue, 2); | |
| else if (key === 'glSi' && !isNaN(numValue)) setInputValue('conc_glSi', numValue, 2); | |
| // Плотности растворов (gml) | |
| else if (key === 'gmlCaNO3' && !isNaN(numValue)) setInputValue('conc_gmlCaNO3', numValue, 4); | |
| else if (key === 'gmlKNO3' && !isNaN(numValue)) setInputValue('conc_gmlKNO3', numValue, 4); | |
| else if (key === 'gmlNH4NO3' && !isNaN(numValue)) setInputValue('conc_gmlNH4NO3', numValue, 4); | |
| else if (key === 'gmlMgNO3' && !isNaN(numValue)) setInputValue('conc_gmlMgNO3', numValue, 4); | |
| else if (key === 'gmlMgSO4' && !isNaN(numValue)) setInputValue('conc_gmlMgSO4', numValue, 4); | |
| else if (key === 'gmlK2SO4' && !isNaN(numValue)) setInputValue('conc_gmlK2SO4', numValue, 4); | |
| else if (key === 'gmlKH2PO4' && !isNaN(numValue)) setInputValue('conc_gmlKH2PO4', numValue, 4); | |
| else if (key === 'gmlCaCl2' && !isNaN(numValue)) setInputValue('conc_gmlCaCl2', numValue, 4); | |
| else if (key === 'gmlCmplx' && !isNaN(numValue)) setInputValue('conc_gmlCmplx', numValue, 3); | |
| else if (key === 'gmlFe' && !isNaN(numValue)) setInputValue('conc_gmlFe', numValue, 3); | |
| else if (key === 'gmlMn' && !isNaN(numValue)) setInputValue('conc_gmlMn', numValue, 3); | |
| else if (key === 'gmlB' && !isNaN(numValue)) setInputValue('conc_gmlB', numValue, 3); | |
| else if (key === 'gmlZn' && !isNaN(numValue)) setInputValue('conc_gmlZn', numValue, 3); | |
| else if (key === 'gmlCu' && !isNaN(numValue)) setInputValue('conc_gmlCu', numValue, 3); | |
| else if (key === 'gmlMo' && !isNaN(numValue)) setInputValue('conc_gmlMo', numValue, 3); | |
| else if (key === 'gmlCo' && !isNaN(numValue)) setInputValue('conc_gmlCo', numValue, 3); | |
| else if (key === 'gmlSi' && !isNaN(numValue)) setInputValue('conc_gmlSi', numValue, 3); | |
| // Чекбоксы (как в легаси StrToBool - регистронезависимо) | |
| else if (key === 'chkComplex') { | |
| const checkbox = document.getElementById('use_complex'); | |
| if (checkbox) { | |
| const lowerValue = trimmedValue.toLowerCase(); | |
| checkbox.checked = (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes'); | |
| console.log('Загружен chkComplex:', trimmedValue, '→', checkbox.checked); | |
| } | |
| } | |
| else if (key === 'chK2SO4') { | |
| const checkbox = document.getElementById('use_K2SO4'); // Правильный ID с большой буквы | |
| if (checkbox) { | |
| const lowerValue = trimmedValue.toLowerCase(); | |
| checkbox.checked = (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes'); | |
| useK2SO4 = checkbox.checked; // Обновляем глобальную переменную | |
| } | |
| } | |
| else if (key === 'chMgNO3') { | |
| const checkbox = document.getElementById('use_MgNO3'); // Правильный ID с большой буквы | |
| if (checkbox) { | |
| const lowerValue = trimmedValue.toLowerCase(); | |
| checkbox.checked = (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes'); | |
| useMgNO3 = checkbox.checked; // Обновляем глобальную переменную | |
| } | |
| } | |
| // Названия солей (m) - текстовые поля | |
| else if (key === 'mCaNO3') { const el = document.getElementById('mCaNO3'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mKNO3') { const el = document.getElementById('mKNO3'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mNH4NO3') { const el = document.getElementById('mNH4NO3'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mMgNO3') { const el = document.getElementById('mMgNO3'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mMgSO4') { const el = document.getElementById('mMgSO4'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mKH2PO4') { const el = document.getElementById('mKH2PO4'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mK2SO4') { const el = document.getElementById('mK2SO4'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mCaCl2') { const el = document.getElementById('mCaCl2'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mCmplx') { const el = document.getElementById('mCmplx'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mFe') { const el = document.getElementById('mFe'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mMn') { const el = document.getElementById('mMn'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mB') { const el = document.getElementById('mB'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mZn') { const el = document.getElementById('mZn'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mCu') { const el = document.getElementById('mCu'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mMo') { const el = document.getElementById('mMo'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mCo') { const el = document.getElementById('mCo'); if (el) el.value = trimmedValue; } | |
| else if (key === 'mSi') { const el = document.getElementById('mSi'); if (el) el.value = trimmedValue; } | |
| else if (key === 'addrMixer') { const el = document.getElementById('addrMixer'); if (el) el.value = trimmedValue; } | |
| else if (key === 'nmix' && !isNaN(numValue)) setInputValue('nmix', numValue, 0); | |
| // Объемы баков | |
| else if (key === 'tAml' && !isNaN(numValue)) setInputValue('tank_A', numValue, 0); | |
| else if (key === 'tBml' && !isNaN(numValue)) setInputValue('tank_B', numValue, 0); | |
| // Цены (cg) | |
| else if (key === 'cgCaNO3' && !isNaN(numValue)) setInputValue('price_CaNO3', numValue, 4); | |
| else if (key === 'cgKNO3' && !isNaN(numValue)) setInputValue('price_KNO3', numValue, 4); | |
| else if (key === 'cgNH4NO3' && !isNaN(numValue)) setInputValue('price_NH4NO3', numValue, 4); | |
| else if (key === 'cgMgNO3' && !isNaN(numValue)) setInputValue('price_MgNO3', numValue, 4); | |
| else if (key === 'cgMgSO4' && !isNaN(numValue)) setInputValue('price_MgSO4', numValue, 4); | |
| else if (key === 'cgK2SO4' && !isNaN(numValue)) setInputValue('price_K2SO4', numValue, 4); | |
| else if (key === 'cgKH2PO4' && !isNaN(numValue)) setInputValue('price_KH2PO4', numValue, 4); | |
| else if (key === 'cgCaCl2' && !isNaN(numValue)) setInputValue('price_CaCl2', numValue, 4); | |
| else if (key === 'cgCmplx' && !isNaN(numValue)) setInputValue('price_Cmplx', numValue, 4); | |
| else if (key === 'cgFe' && !isNaN(numValue)) setInputValue('price_Fe', numValue, 4); | |
| else if (key === 'cgMn' && !isNaN(numValue)) setInputValue('price_Mn', numValue, 4); | |
| else if (key === 'cgB' && !isNaN(numValue)) setInputValue('price_B', numValue, 4); | |
| else if (key === 'cgZn' && !isNaN(numValue)) setInputValue('price_Zn', numValue, 4); | |
| else if (key === 'cgCu' && !isNaN(numValue)) setInputValue('price_Cu', numValue, 4); | |
| else if (key === 'cgMo' && !isNaN(numValue)) setInputValue('price_Mo', numValue, 4); | |
| else if (key === 'cgCo' && !isNaN(numValue)) setInputValue('price_Co', numValue, 4); | |
| else if (key === 'cgSi' && !isNaN(numValue)) setInputValue('price_Si', numValue, 4); | |
| } | |
| }); | |
| // Обновляем журнал | |
| journalEntries = tempJournal; | |
| updateJournalList(); | |
| // Применяем профиль | |
| applyProfile(); | |
| alert('Файл загружен. Профиль и журнал (' + tempJournal.length + ' записей) импортированы.'); | |
| } | |
| // Вспомогательные функции для сохранения файла (дополнительные) | |
| function getMicroPercent(element) { | |
| const id = 'micro_' + element + '_percent'; | |
| const input = document.getElementById(id); | |
| return input ? (parseFloat(input.value) || 0) : 0; | |
| } | |
| function getConcentration(salt) { | |
| const id = 'conc_gl' + salt; | |
| const input = document.getElementById(id); | |
| return input ? (parseFloat(input.value) || 0) : 0; | |
| } | |
| function getDensity(salt) { | |
| const id = 'conc_gml' + salt; | |
| const input = document.getElementById(id); | |
| return input ? (parseFloat(input.value) || 0) : 0; | |
| } | |
| function getSaltName(salt) { | |
| const id = 'm' + salt; | |
| const input = document.getElementById(id); | |
| return input ? input.value : ''; | |
| } | |
| function getTankVolume(tank) { | |
| const id = 'tank_' + tank; | |
| const input = document.getElementById(id); | |
| return input ? (parseFloat(input.value) || 0) : 0; | |
| } | |
| function getPrice(salt) { | |
| const id = 'price_' + salt; | |
| const input = document.getElementById(id); | |
| return input ? (parseFloat(input.value) || 0) : 0; | |
| } | |
| function fileSaveAs() { | |
| getValues(); | |
| const comment = document.getElementById('file_comment').value; | |
| // Формируем содержимое файла в формате .hpg (как в легаси SaveFile) | |
| let content = ''; | |
| content += 'version=Hydroponic Profile Generator HTML5 https://github.com/siv237/HPG\n'; | |
| content += 'Comment=' + (comment || '') + '\n'; | |
| // Макроэлементы профиля | |
| content += 'N=' + currentProfile.N + '\n'; | |
| content += 'NH4=' + currentProfile.NH4 + '\n'; | |
| content += 'NO3=' + currentProfile.NO3 + '\n'; | |
| content += 'P=' + currentProfile.P + '\n'; | |
| content += 'K=' + currentProfile.K + '\n'; | |
| content += 'Ca=' + currentProfile.Ca + '\n'; | |
| content += 'Mg=' + currentProfile.Mg + '\n'; | |
| content += 'S=' + currentProfile.S + '\n'; | |
| content += 'Cl=' + currentProfile.Cl + '\n'; | |
| // Составы солей - проценты макроэлементов (Macro %) | |
| content += 'CaNO3_Ca=' + getSaltPercent('CaNO3', 'Ca') + '\n'; | |
| content += 'CaNO3_NO3=' + getSaltPercent('CaNO3', 'NO3') + '\n'; | |
| content += 'CaNO3_NH4=' + getSaltPercent('CaNO3', 'NH4') + '\n'; | |
| content += 'KNO3_K=' + getSaltPercent('KNO3', 'K') + '\n'; | |
| content += 'KNO3_NO3=' + getSaltPercent('KNO3', 'NO3') + '\n'; | |
| content += 'NH4NO3_NH4=' + getSaltPercent('NH4NO3', 'NH4') + '\n'; | |
| content += 'NH4NO3_NO3=' + getSaltPercent('NH4NO3', 'NO3') + '\n'; | |
| content += 'MgSO4_Mg=' + getSaltPercent('MgSO4', 'Mg') + '\n'; | |
| content += 'MgSO4_S=' + getSaltPercent('MgSO4', 'S') + '\n'; | |
| content += 'KH2PO4_K=' + getSaltPercent('KH2PO4', 'K') + '\n'; | |
| content += 'KH2PO4_P=' + getSaltPercent('KH2PO4', 'P') + '\n'; | |
| content += 'K2SO4_K=' + getSaltPercent('K2SO4', 'K') + '\n'; | |
| content += 'K2SO4_S=' + getSaltPercent('K2SO4', 'S') + '\n'; | |
| content += 'MgNO3_Mg=' + getSaltPercent('MgNO3', 'Mg') + '\n'; | |
| content += 'MgNO3_NO3=' + getSaltPercent('MgNO3', 'NO3') + '\n'; | |
| content += 'CaCl2_Ca=' + getSaltPercent('CaCl2', 'Ca') + '\n'; | |
| content += 'CaCl2_Cl=' + getSaltPercent('CaCl2', 'Cl') + '\n'; | |
| // Микроэлементы профиля | |
| content += 'Fe=' + currentProfile.Fe + '\n'; | |
| content += 'Mn=' + currentProfile.Mn + '\n'; | |
| content += 'B=' + currentProfile.B + '\n'; | |
| content += 'Zn=' + currentProfile.Zn + '\n'; | |
| content += 'Cu=' + currentProfile.Cu + '\n'; | |
| content += 'Mo=' + currentProfile.Mo + '\n'; | |
| content += 'Co=' + currentProfile.Co + '\n'; | |
| content += 'Si=' + currentProfile.Si + '\n'; | |
| // Доли микроэлементов в солях (Micro %) | |
| content += 'dFe=' + getMicroPercent('Fe') + '\n'; | |
| content += 'dMn=' + getMicroPercent('Mn') + '\n'; | |
| content += 'dB=' + getMicroPercent('B') + '\n'; | |
| content += 'dZn=' + getMicroPercent('Zn') + '\n'; | |
| content += 'dCu=' + getMicroPercent('Cu') + '\n'; | |
| content += 'dMo=' + getMicroPercent('Mo') + '\n'; | |
| content += 'dCo=' + getMicroPercent('Co') + '\n'; | |
| content += 'dSi=' + getMicroPercent('Si') + '\n'; | |
| // Концентрации растворов (gl) | |
| content += 'glCaNO3=' + getConcentration('CaNO3') + '\n'; | |
| content += 'glKNO3=' + getConcentration('KNO3') + '\n'; | |
| content += 'glNH4NO3=' + getConcentration('NH4NO3') + '\n'; | |
| content += 'glMgNO3=' + getConcentration('MgNO3') + '\n'; | |
| content += 'glMgSO4=' + getConcentration('MgSO4') + '\n'; | |
| content += 'glK2SO4=' + getConcentration('K2SO4') + '\n'; | |
| content += 'glKH2PO4=' + getConcentration('KH2PO4') + '\n'; | |
| content += 'glCaCl2=' + getConcentration('CaCl2') + '\n'; | |
| content += 'glCmplx=' + getConcentration('Cmplx') + '\n'; | |
| content += 'glFe=' + getConcentration('Fe') + '\n'; | |
| content += 'glMn=' + getConcentration('Mn') + '\n'; | |
| content += 'glB=' + getConcentration('B') + '\n'; | |
| content += 'glZn=' + getConcentration('Zn') + '\n'; | |
| content += 'glCu=' + getConcentration('Cu') + '\n'; | |
| content += 'glMo=' + getConcentration('Mo') + '\n'; | |
| content += 'glCo=' + getConcentration('Co') + '\n'; | |
| content += 'glSi=' + getConcentration('Si') + '\n'; | |
| // Плотности растворов (gml) | |
| content += 'gmlCaNO3=' + getDensity('CaNO3') + '\n'; | |
| content += 'gmlKNO3=' + getDensity('KNO3') + '\n'; | |
| content += 'gmlNH4NO3=' + getDensity('NH4NO3') + '\n'; | |
| content += 'gmlMgNO3=' + getDensity('MgNO3') + '\n'; | |
| content += 'gmlMgSO4=' + getDensity('MgSO4') + '\n'; | |
| content += 'gmlK2SO4=' + getDensity('K2SO4') + '\n'; | |
| content += 'gmlKH2PO4=' + getDensity('KH2PO4') + '\n'; | |
| content += 'gmlCaCl2=' + getDensity('CaCl2') + '\n'; | |
| content += 'gmlCmplx=' + getDensity('Cmplx') + '\n'; | |
| content += 'gmlFe=' + getDensity('Fe') + '\n'; | |
| content += 'gmlMn=' + getDensity('Mn') + '\n'; | |
| content += 'gmlB=' + getDensity('B') + '\n'; | |
| content += 'gmlZn=' + getDensity('Zn') + '\n'; | |
| content += 'gmlCu=' + getDensity('Cu') + '\n'; | |
| content += 'gmlMo=' + getDensity('Mo') + '\n'; | |
| content += 'gmlCo=' + getDensity('Co') + '\n'; | |
| content += 'gmlSi=' + getDensity('Si') + '\n'; | |
| // Чекбоксы (как в легаси - True/False с большой буквы) | |
| content += 'chkComplex=' + (document.getElementById('use_complex')?.checked ? 'True' : 'False') + '\n'; | |
| content += 'chK2SO4=' + (document.getElementById('use_K2SO4')?.checked ? 'True' : 'False') + '\n'; | |
| content += 'chMgNO3=' + (document.getElementById('use_MgNO3')?.checked ? 'True' : 'False') + '\n'; | |
| // Объем | |
| content += 'V=' + (currentProfile.volume || 10) + '\n'; | |
| // Названия солей (m) | |
| content += 'mCaNO3=' + getSaltName('CaNO3') + '\n'; | |
| content += 'mKNO3=' + getSaltName('KNO3') + '\n'; | |
| content += 'mNH4NO3=' + getSaltName('NH4NO3') + '\n'; | |
| content += 'mMgNO3=' + getSaltName('MgNO3') + '\n'; | |
| content += 'mMgSO4=' + getSaltName('MgSO4') + '\n'; | |
| content += 'mKH2PO4=' + getSaltName('KH2PO4') + '\n'; | |
| content += 'mK2SO4=' + getSaltName('K2SO4') + '\n'; | |
| content += 'mCaCl2=' + getSaltName('CaCl2') + '\n'; | |
| content += 'mCmplx=' + getSaltName('Cmplx') + '\n'; | |
| content += 'mFe=' + getSaltName('Fe') + '\n'; | |
| content += 'mMn=' + getSaltName('Mn') + '\n'; | |
| content += 'mB=' + getSaltName('B') + '\n'; | |
| content += 'mZn=' + getSaltName('Zn') + '\n'; | |
| content += 'mCu=' + getSaltName('Cu') + '\n'; | |
| content += 'mMo=' + getSaltName('Mo') + '\n'; | |
| content += 'mCo=' + getSaltName('Co') + '\n'; | |
| content += 'mSi=' + getSaltName('Si') + '\n'; | |
| content += 'addrMixer=' + (document.getElementById('addrMixer')?.value || '') + '\n'; | |
| content += 'nmix=' + (document.getElementById('nmix')?.value || '') + '\n'; | |
| // Объемы баков | |
| content += 'tAml=' + getTankVolume('A') + '\n'; | |
| content += 'tBml=' + getTankVolume('B') + '\n'; | |
| // Цены (cg) | |
| content += 'cgCaNO3=' + getPrice('CaNO3') + '\n'; | |
| content += 'cgKNO3=' + getPrice('KNO3') + '\n'; | |
| content += 'cgNH4NO3=' + getPrice('NH4NO3') + '\n'; | |
| content += 'cgMgNO3=' + getPrice('MgNO3') + '\n'; | |
| content += 'cgMgSO4=' + getPrice('MgSO4') + '\n'; | |
| content += 'cgK2SO4=' + getPrice('K2SO4') + '\n'; | |
| content += 'cgKH2PO4=' + getPrice('KH2PO4') + '\n'; | |
| content += 'cgCaCl2=' + getPrice('CaCl2') + '\n'; | |
| content += 'cgCmplx=' + getPrice('Cmplx') + '\n'; | |
| content += 'cgFe=' + getPrice('Fe') + '\n'; | |
| content += 'cgMn=' + getPrice('Mn') + '\n'; | |
| content += 'cgB=' + getPrice('B') + '\n'; | |
| content += 'cgZn=' + getPrice('Zn') + '\n'; | |
| content += 'cgCu=' + getPrice('Cu') + '\n'; | |
| content += 'cgMo=' + getPrice('Mo') + '\n'; | |
| content += 'cgCo=' + getPrice('Co') + '\n'; | |
| content += 'cgSi=' + getPrice('Si') + '\n'; | |
| // Журнал (в конце файла) | |
| journalEntries.forEach(entry => { | |
| // Формат: date=2024-10-10;Описание;N=220 NO3=200... | |
| content += 'date=' + entry.date + ';' + entry.desc + ';' + entry.profile + '\n'; | |
| }); | |
| const blob = new Blob([content], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = document.getElementById('file_filename').value || 'profile.hpg'; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| } | |
| // Подгрузить удобрения из файла (аналог Button4Click в легаси) | |
| // Загружает ТОЛЬКО составы, концентрации, цены и настройки, НЕ трогая профиль | |
| function fileLoadFertilizers() { | |
| const input = document.createElement('input'); | |
| input.type = 'file'; | |
| input.accept = '.hpg,.txt'; | |
| input.onchange = (e) => { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| try { | |
| const content = event.target.result; | |
| parseCompositionsOnly(content); | |
| alert(`Составы и настройки загружены из файла: ${file.name}`); | |
| } catch (err) { | |
| console.error('Ошибка загрузки удобрений:', err); | |
| alert('Ошибка загрузки файла удобрений'); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| } | |
| }; | |
| input.click(); | |
| } | |
| // Подгрузить профиль из файла (аналог Button5Click в легаси) | |
| // Загружает ТОЛЬКО профиль (N, P, K, Fe и т.д.), НЕ трогая составы и настройки | |
| function fileLoadProfile() { | |
| const input = document.createElement('input'); | |
| input.type = 'file'; | |
| input.accept = '.hpg,.txt'; | |
| input.onchange = (e) => { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| try { | |
| const content = event.target.result; | |
| parseProfileOnly(content); | |
| alert(`Профиль загружен из файла: ${file.name}`); | |
| } catch (err) { | |
| console.error('Ошибка загрузки профиля:', err); | |
| alert('Ошибка загрузки файла профиля'); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| } | |
| }; | |
| input.click(); | |
| } | |
| function showMacroHelp() { | |
| window.open('https://github.com/siv237/HPG/wiki/Macro', '_blank'); | |
| } | |
| function showMicroHelp() { | |
| window.open('https://github.com/siv237/HPG/wiki/Micro', '_blank'); | |
| } | |
| function showCalcHelp() { | |
| window.open('https://github.com/siv237/HPG/wiki/raschet', '_blank'); | |
| } | |
| function showPreparationHelp() { | |
| window.open('https://github.com/siv237/HPG/wiki/izgotovlenie', '_blank'); | |
| } | |
| function showCorrectorHelp() { | |
| window.open('https://github.com/siv237/HPG/wiki/correction', '_blank'); | |
| } | |
| function showFileHelp() { | |
| window.open('https://github.com/siv237/HPG/wiki/file', '_blank'); | |
| } | |
| function journalAdd() { | |
| const memo = document.getElementById('file_journal_memo').value; | |
| const date = document.getElementById('file_journal_date').value; | |
| if (!memo || !date) { | |
| alert('Заполните описание и дату'); | |
| return; | |
| } | |
| getValues(); | |
| updateProfileString(); | |
| const profileStr = document.getElementById('profile-string').value; | |
| const entry = { | |
| desc: memo, // Описание берётся из memo | |
| date: date, | |
| memo: memo, | |
| profile: profileStr, | |
| fullProfile: {...currentProfile} | |
| }; | |
| journalEntries.push(entry); | |
| updateJournalList(); | |
| // Очистка полей | |
| document.getElementById('file_journal_date').value = ''; | |
| document.getElementById('file_journal_memo').value = ''; | |
| } | |
| function journalUpdate() { | |
| const select = document.getElementById('file_journal_list'); | |
| const selectedIndex = select.selectedIndex; | |
| if (selectedIndex < 0) { | |
| alert('Выберите запись для обновления'); | |
| return; | |
| } | |
| const memo = document.getElementById('file_journal_memo').value; | |
| const date = document.getElementById('file_journal_date').value; | |
| if (!memo || !date) { | |
| alert('Заполните описание и дату'); | |
| return; | |
| } | |
| getValues(); | |
| updateProfileString(); | |
| const profileStr = document.getElementById('profile-string').value; | |
| journalEntries[selectedIndex] = { | |
| desc: memo, // Описание берётся из memo | |
| date: date, | |
| memo: memo, | |
| profile: profileStr, | |
| fullProfile: {...currentProfile} | |
| }; | |
| updateJournalList(); | |
| } | |
| function journalDelete() { | |
| const select = document.getElementById('file_journal_list'); | |
| const selectedIndex = select.selectedIndex; | |
| if (selectedIndex < 0) { | |
| alert('Выберите запись для удаления'); | |
| return; | |
| } | |
| if (confirm('Удалить выбранную запись?')) { | |
| journalEntries.splice(selectedIndex, 1); | |
| updateJournalList(); | |
| document.getElementById('file_journal_date').value = ''; | |
| document.getElementById('file_journal_memo').value = ''; | |
| clearFileProfileDetails(); | |
| } | |
| } | |
| function updateJournalList() { | |
| const select = document.getElementById('file_journal_list'); | |
| select.innerHTML = ''; | |
| journalEntries.forEach((entry, index) => { | |
| const option = document.createElement('option'); | |
| option.value = index; | |
| option.textContent = `${entry.date} - ${entry.desc}`; | |
| select.appendChild(option); | |
| }); | |
| // Если журнал пустой, очищаем поля | |
| if (journalEntries.length === 0) { | |
| document.getElementById('file_journal_date').value = ''; | |
| document.getElementById('file_journal_memo').value = ''; | |
| document.getElementById('file_profile_info').value = ''; // Используем .value для input | |
| clearFileProfileDetails(); | |
| } | |
| // Сохранение в localStorage | |
| localStorage.setItem('hpg_journal', JSON.stringify(journalEntries)); | |
| } | |
| function acceptProfileFromJournal() { | |
| const select = document.getElementById('file_journal_list'); | |
| const selectedIndex = select.selectedIndex; | |
| if (selectedIndex < 0) { | |
| alert('Выберите запись из журнала'); | |
| return; | |
| } | |
| // Копируем строку профиля из file_profile_info в profile-string (как в легаси: profile.Caption := pr2.Caption) | |
| const profileFromJournal = document.getElementById('file_profile_info').value; | |
| document.getElementById('profile-string').value = profileFromJournal; | |
| // Вызываем парсер профиля (как в легаси: LoadProfile) | |
| parseProfile(); | |
| } | |
| function clearFileProfileDetails() { | |
| document.getElementById('rN').textContent = ''; | |
| document.getElementById('rNO3').textContent = ''; | |
| document.getElementById('rNH4').textContent = ''; | |
| document.getElementById('rP').textContent = ''; | |
| document.getElementById('rK').textContent = ''; | |
| document.getElementById('rCa').textContent = ''; | |
| document.getElementById('rMg').textContent = ''; | |
| document.getElementById('rS').textContent = ''; | |
| document.getElementById('rCl').textContent = ''; | |
| document.getElementById('rFe').textContent = ''; | |
| document.getElementById('rMn').textContent = ''; | |
| document.getElementById('rB').textContent = ''; | |
| document.getElementById('rZn').textContent = ''; | |
| document.getElementById('rCu').textContent = ''; | |
| document.getElementById('rMo').textContent = ''; | |
| document.getElementById('rCo').textContent = ''; | |
| document.getElementById('rSi').textContent = ''; | |
| } | |
| // Обработчик выбора записи в журнале | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const select = document.getElementById('file_journal_list'); | |
| if (select) { | |
| select.addEventListener('change', function() { | |
| const selectedIndex = this.selectedIndex; | |
| if (selectedIndex >= 0 && selectedIndex < journalEntries.length) { | |
| const entry = journalEntries[selectedIndex]; | |
| document.getElementById('file_journal_date').value = entry.date; | |
| document.getElementById('file_journal_memo').value = entry.memo || entry.desc; // Совместимость со старым форматом | |
| document.getElementById('file_profile_info').value = entry.profile; | |
| // Получаем текущие значения профиля | |
| getValues(); | |
| // Отображаем детали профиля с процентами отклонения (как в легаси) | |
| const p = entry.fullProfile; | |
| // Функция расчета процента отклонения | |
| const calcPercent = (current, journal) => { | |
| if (journal > 0) { | |
| return Math.round((current - journal) / journal * 100); | |
| } | |
| return 0; | |
| }; | |
| // Макроэлементы | |
| const vN = currentProfile.N || 0; | |
| const vNO3 = currentProfile.NO3 || 0; | |
| const vNH4 = currentProfile.NH4 || 0; | |
| const vP = currentProfile.P || 0; | |
| const vK = currentProfile.K || 0; | |
| const vCa = currentProfile.Ca || 0; | |
| const vMg = currentProfile.Mg || 0; | |
| const vS = currentProfile.S || 0; | |
| const vCl = currentProfile.Cl || 0; | |
| // Микроэлементы | |
| const vFe = currentProfile.Fe || 0; | |
| const vMn = currentProfile.Mn || 0; | |
| const vB = currentProfile.B || 0; | |
| const vZn = currentProfile.Zn || 0; | |
| const vCu = currentProfile.Cu || 0; | |
| const vMo = currentProfile.Mo || 0; | |
| const vCo = currentProfile.Co || 0; | |
| const vSi = currentProfile.Si || 0; | |
| // Отображаем с процентами | |
| document.getElementById('rN').textContent = p.N > 0 ? `N:(${calcPercent(vN, p.N)}%)` : ''; | |
| document.getElementById('rNO3').textContent = p.NO3 > 0 ? `NO3:(${calcPercent(vNO3, p.NO3)}%)` : ''; | |
| document.getElementById('rNH4').textContent = p.NH4 > 0 ? `NH4:(${calcPercent(vNH4, p.NH4)}%)` : ''; | |
| document.getElementById('rP').textContent = p.P > 0 ? `P:(${calcPercent(vP, p.P)}%)` : ''; | |
| document.getElementById('rK').textContent = p.K > 0 ? `K:(${calcPercent(vK, p.K)}%)` : ''; | |
| document.getElementById('rCa').textContent = p.Ca > 0 ? `Ca:(${calcPercent(vCa, p.Ca)}%)` : ''; | |
| document.getElementById('rMg').textContent = p.Mg > 0 ? `Mg:(${calcPercent(vMg, p.Mg)}%)` : ''; | |
| document.getElementById('rS').textContent = p.S > 0 ? `S:(${calcPercent(vS, p.S)}%)` : ''; | |
| document.getElementById('rCl').textContent = p.Cl > 0 ? `Cl:(${calcPercent(vCl, p.Cl)}%)` : '-'; | |
| // Для микроэлементов делим текущее значение на 1000 (мкг/л -> мг/л) | |
| document.getElementById('rFe').textContent = p.Fe > 0 ? `Fe:(${calcPercent(vFe/1000, p.Fe)}%)` : 'Fe: -'; | |
| document.getElementById('rMn').textContent = p.Mn > 0 ? `Mn:(${calcPercent(vMn/1000, p.Mn)}%)` : 'Mn: -'; | |
| document.getElementById('rB').textContent = p.B > 0 ? `B:(${calcPercent(vB/1000, p.B)}%)` : 'B: -'; | |
| document.getElementById('rZn').textContent = p.Zn > 0 ? `Zn:(${calcPercent(vZn/1000, p.Zn)}%)` : 'Zn: -'; | |
| document.getElementById('rCu').textContent = p.Cu > 0 ? `Cu:(${calcPercent(vCu/1000, p.Cu)}%)` : 'Cu: -'; | |
| document.getElementById('rMo').textContent = p.Mo > 0 ? `Mo:(${calcPercent(vMo/1000, p.Mo)}%)` : 'Mo: -'; | |
| document.getElementById('rCo').textContent = p.Co > 0 ? `Co:(${calcPercent(vCo/1000, p.Co)}%)` : 'Co: -'; | |
| document.getElementById('rSi').textContent = p.Si > 0 ? `Si:(${calcPercent(vSi/1000, p.Si)}%)` : 'Si: -'; | |
| } | |
| }); | |
| } | |
| // Загрузка журнала из localStorage | |
| const savedJournal = localStorage.getItem('hpg_journal'); | |
| if (savedJournal) { | |
| try { | |
| journalEntries = JSON.parse(savedJournal); | |
| updateJournalList(); | |
| } catch (e) { | |
| console.error('Ошибка загрузки журнала:', e); | |
| } | |
| } | |
| }); | |
| // Корректор | |
| function fillColumn(columnName) { | |
| getValues(); | |
| const prefix = columnName === 'initial' ? 'init' : | |
| columnName === 'current' ? 'curr' : | |
| columnName === 'correcting' ? 'corr' : 'final'; | |
| document.getElementById(`corr_${prefix}_N`).value = currentProfile.N.toFixed(3); | |
| document.getElementById(`corr_${prefix}_NO3`).value = currentProfile.NO3.toFixed(2); | |
| document.getElementById(`corr_${prefix}_NH4`).value = currentProfile.NH4.toFixed(2); | |
| document.getElementById(`corr_${prefix}_P`).value = currentProfile.P.toFixed(3); | |
| document.getElementById(`corr_${prefix}_K`).value = currentProfile.K.toFixed(3); | |
| document.getElementById(`corr_${prefix}_Ca`).value = currentProfile.Ca.toFixed(3); | |
| document.getElementById(`corr_${prefix}_Mg`).value = currentProfile.Mg.toFixed(3); | |
| document.getElementById(`corr_${prefix}_S`).value = currentProfile.S.toFixed(3); | |
| document.getElementById(`corr_${prefix}_Cl`).value = currentProfile.Cl.toFixed(1); | |
| document.getElementById(`corr_${prefix}_EC`).value = currentProfile.EC.toFixed(3); | |
| } | |
| // ===== ФУНКЦИИ КОРРЕКТОРА ===== | |
| // Текущая активная колонка для корректора | |
| let activeCorrectColumn = null; | |
| // Обработчик изменения полей корректора | |
| function onCorrectorChange(event) { | |
| // Запускаем полный пересчет корректора | |
| korrection(); | |
| } | |
| // Главная функция расчета корректора (аналог korrection из легаси строка 2580) | |
| function korrection() { | |
| // Исходный (0) - колонка init | |
| const V_0 = parseFloat(document.getElementById('corr_init_vol').value) || 10; | |
| const V_2 = parseFloat(document.getElementById('corr_final_vol').value) || 30; | |
| // Текущий (1) - колонка curr | |
| let V_1 = 10; // текущий всегда 10 литров (условно) | |
| // Автоматически устанавливаем V_2 если он меньше V_1 | |
| if (V_0 >= V_2) { | |
| document.getElementById('corr_final_vol').value = (V_0 + 10).toFixed(1); | |
| } | |
| // Пересчет S и EC для исходного | |
| calculateCorrectorS('init'); | |
| calculateCorrectorEC('init'); | |
| const N_0 = parseFloat(document.getElementById('corr_init_NO3').value) + | |
| parseFloat(document.getElementById('corr_init_NH4').value); | |
| document.getElementById('corr_init_N').value = N_0.toFixed(3); | |
| // Текущий = исходный × (EC_1 / EC_0) - учет испарения воды | |
| const EC_0 = parseFloat(document.getElementById('corr_init_EC').value) || 1; | |
| const EC_1 = parseFloat(document.getElementById('corr_curr_EC').value) || EC_0; | |
| const kEC = EC_0 !== 0 ? EC_1 / EC_0 : 1; | |
| const elements = ['NO3', 'NH4', 'P', 'K', 'Ca', 'Mg', 'Cl']; | |
| elements.forEach(elem => { | |
| const val_0 = parseFloat(document.getElementById(`corr_init_${elem}`).value) || 0; | |
| document.getElementById(`corr_curr_${elem}`).value = (val_0 * kEC).toFixed(elem === 'NO3' || elem === 'NH4' ? 2 : 3); | |
| }); | |
| // S и N для текущего | |
| calculateCorrectorS('curr'); | |
| const N_1 = parseFloat(document.getElementById('corr_curr_NO3').value) + | |
| parseFloat(document.getElementById('corr_curr_NH4').value); | |
| document.getElementById('corr_curr_N').value = N_1.toFixed(3); | |
| document.getElementById('corr_curr_EC').value = EC_1.toFixed(3); | |
| // Объем корректирующего | |
| const V_k = V_2 - V_1; | |
| document.getElementById('corr_corr_vol').value = V_k.toFixed(1); | |
| // Корректирующий рассчитывается по формуле баланса масс: | |
| // C_k = (C_2 * V_2 - C_1 * V_1) / V_k | |
| elements.forEach(elem => { | |
| const C_1 = parseFloat(document.getElementById(`corr_curr_${elem}`).value) || 0; | |
| const C_2 = parseFloat(document.getElementById(`corr_final_${elem}`).value) || 0; | |
| const C_k = V_k !== 0 ? (C_2 * V_2 - C_1 * V_1) / V_k : 0; | |
| document.getElementById(`corr_corr_${elem}`).value = C_k.toFixed(elem === 'NO3' || elem === 'NH4' ? 2 : 3); | |
| }); | |
| // S, EC и N для корректирующего | |
| calculateCorrectorS('corr'); | |
| calculateCorrectorEC('corr'); | |
| const N_k = parseFloat(document.getElementById('corr_corr_NO3').value) + | |
| parseFloat(document.getElementById('corr_corr_NH4').value); | |
| document.getElementById('corr_corr_N').value = N_k.toFixed(3); | |
| // S, EC и N для итогового | |
| calculateCorrectorS('final'); | |
| calculateCorrectorEC('final'); | |
| const N_2 = parseFloat(document.getElementById('corr_final_NO3').value) + | |
| parseFloat(document.getElementById('corr_final_NH4').value); | |
| document.getElementById('corr_final_N').value = N_2.toFixed(3); | |
| // Обновляем протокол коррекции | |
| updateCorrectionProtocol(); | |
| } | |
| // Обновление протокола коррекции (аналог mkorr из легаси строки 2667-2689) | |
| function updateCorrectionProtocol() { | |
| const protocol = document.getElementById('corrector_protocol'); | |
| if (!protocol) return; | |
| const V_1 = 10; | |
| const V_2 = parseFloat(document.getElementById('corr_final_vol').value) || 30; | |
| const EC_1 = parseFloat(document.getElementById('corr_curr_EC').value) || 1; | |
| const EC_2 = parseFloat(document.getElementById('corr_final_EC').value) || 1; | |
| const N_1 = parseFloat(document.getElementById('corr_curr_N').value) || 0; | |
| const N_2 = parseFloat(document.getElementById('corr_final_N').value) || 0; | |
| const NO3_1 = parseFloat(document.getElementById('corr_curr_NO3').value) || 1; | |
| const NO3_2 = parseFloat(document.getElementById('corr_final_NO3').value) || 1; | |
| const NH4_1 = parseFloat(document.getElementById('corr_curr_NH4').value) || 1; | |
| const NH4_2 = parseFloat(document.getElementById('corr_final_NH4').value) || 1; | |
| const P_1 = parseFloat(document.getElementById('corr_curr_P').value) || 1; | |
| const P_2 = parseFloat(document.getElementById('corr_final_P').value) || 1; | |
| const K_1 = parseFloat(document.getElementById('corr_curr_K').value) || 1; | |
| const K_2 = parseFloat(document.getElementById('corr_final_K').value) || 1; | |
| const Ca_1 = parseFloat(document.getElementById('corr_curr_Ca').value) || 1; | |
| const Ca_2 = parseFloat(document.getElementById('corr_final_Ca').value) || 1; | |
| const Mg_1 = parseFloat(document.getElementById('corr_curr_Mg').value) || 1; | |
| const Mg_2 = parseFloat(document.getElementById('corr_final_Mg').value) || 1; | |
| const S_1 = parseFloat(document.getElementById('corr_curr_S').value) || 0; | |
| const S_2 = parseFloat(document.getElementById('corr_final_S').value) || 0; | |
| const Cl_1 = parseFloat(document.getElementById('corr_curr_Cl').value) || 0; | |
| const Cl_2 = parseFloat(document.getElementById('corr_final_Cl').value) || 0; | |
| let text = 'ОСНОВНОЕ:\n'; | |
| text += `Изменение объема на: ${Math.round((V_2 - V_1) / V_1 * 100)}%\n`; | |
| text += `Доля старого раствора: ${Math.round(V_1 / V_2 * 100)}%\n`; | |
| text += `Изменение EC на: ${Math.round((EC_2 - EC_1) / EC_1 * 100)}%\n`; | |
| text += `Изменение N общий на: ${Math.round((N_2 - N_1) / N_1 * 100)}%\n\n`; | |
| text += 'ПРОФИЛЬ:\n'; | |
| text += `Коррекция NO3 на: ${Math.round((NO3_2 - NO3_1) / NO3_1 * 100)}%\n`; | |
| text += `Коррекция NH4 на: ${Math.round((NH4_2 - NH4_1) / NH4_1 * 100)}%\n`; | |
| text += `Коррекция P на: ${Math.round((P_2 - P_1) / P_1 * 100)}%\n`; | |
| text += `Коррекция K на: ${Math.round((K_2 - K_1) / K_1 * 100)}%\n`; | |
| text += `Коррекция Ca на: ${Math.round((Ca_2 - Ca_1) / Ca_1 * 100)}%\n`; | |
| text += `Коррекция Mg на: ${Math.round((Mg_2 - Mg_1) / Mg_1 * 100)}%\n`; | |
| text += `Коррекция S на: ${Math.round(S_2 - S_1)} ppm\n`; | |
| text += `Коррекция Cl на: ${Math.round(Cl_2 - Cl_1)} ppm\n\n`; | |
| text += 'СООТНОШЕНИЯ:\n'; | |
| text += `NH4:NO3 до ${(NH4_1/NO3_1).toFixed(3)} после ${(NH4_2/NO3_2).toFixed(3)}\n`; | |
| text += `K:N до ${(K_1/N_1).toFixed(3)} после ${(K_2/N_2).toFixed(3)}\n`; | |
| text += `K:Ca до ${(K_1/Ca_1).toFixed(3)} после ${(K_2/Ca_2).toFixed(3)}\n`; | |
| text += `K:Mg до ${(K_1/Mg_1).toFixed(3)} после ${(K_2/Mg_2).toFixed(3)}`; | |
| protocol.value = text; | |
| } | |
| // Расчет серы для корректора (аналог CalculateS) | |
| function calculateCorrectorS(prefix) { | |
| const molN = 14.0067; | |
| const molP = 30.973762; | |
| const molK = 39.0983; | |
| const molCa = 40.078; | |
| const molMg = 24.305; | |
| const molS = 32.065; | |
| const molCl = 35.453; | |
| const NO3 = parseFloat(document.getElementById(`corr_${prefix}_NO3`).value) || 0; | |
| const NH4 = parseFloat(document.getElementById(`corr_${prefix}_NH4`).value) || 0; | |
| const P = parseFloat(document.getElementById(`corr_${prefix}_P`).value) || 0; | |
| const K = parseFloat(document.getElementById(`corr_${prefix}_K`).value) || 0; | |
| const Ca = parseFloat(document.getElementById(`corr_${prefix}_Ca`).value) || 0; | |
| const Mg = parseFloat(document.getElementById(`corr_${prefix}_Mg`).value) || 0; | |
| const Cl = parseFloat(document.getElementById(`corr_${prefix}_Cl`).value) || 0; | |
| // Формула из легаси (строка 1029) | |
| const S = (-molS * (-NH4*molCa*molMg*molK*molP*molCl - 2*Ca*molN*molMg*molK*molP*molCl - | |
| 2*Mg*molN*molCa*molK*molP*molCl - K*molN*molCa*molMg*molP*molCl + | |
| NO3*molCa*molMg*molK*molP*molCl + P*molN*molCa*molMg*molK*molCl + | |
| Cl*molN*molCa*molMg*molK*molP)) / (2*molN*molCa*molMg*molK*molP*molCl); | |
| const sField = document.getElementById(`corr_${prefix}_S`); | |
| if (sField) { | |
| // Программно обновляем значение даже для readonly полей | |
| sField.value = S.toFixed(3); | |
| } | |
| } | |
| // Расчет EC для корректора (аналог CalcEC) | |
| function calculateCorrectorEC(prefix) { | |
| const molN = 14.0067; | |
| const molK = 39.0983; | |
| const molCa = 40.078; | |
| const molMg = 24.305; | |
| const NH4 = parseFloat(document.getElementById(`corr_${prefix}_NH4`).value) || 0; | |
| const K = parseFloat(document.getElementById(`corr_${prefix}_K`).value) || 0; | |
| const Ca = parseFloat(document.getElementById(`corr_${prefix}_Ca`).value) || 0; | |
| const Mg = parseFloat(document.getElementById(`corr_${prefix}_Mg`).value) || 0; | |
| // Формула из легаси (строка 1017) | |
| const EC = 0.095 * (NH4*molCa*molMg*molK + 2*Ca*molN*molMg*molK + | |
| 2*Mg*molN*molCa*molK + K*molN*molCa*molMg + 2*molN*molCa*molMg*molK) / | |
| (molN*molCa*molMg*molK); | |
| const ecField = document.getElementById(`corr_${prefix}_EC`); | |
| if (ecField) { | |
| // Программно обновляем значение даже для readonly полей | |
| ecField.value = EC.toFixed(3); | |
| } | |
| } | |
| // Кнопка "В расчет" - перенос значений в основной профиль | |
| function calcCorrection(columnName) { | |
| const prefix = columnName === 'initial' ? 'init' : | |
| columnName === 'current' ? 'curr' : | |
| columnName === 'correcting' ? 'corr' : 'final'; | |
| // Переносим значения в основные поля макроэлементов | |
| document.getElementById('N').value = document.getElementById(`corr_${prefix}_N`).value; | |
| document.getElementById('NO3').value = document.getElementById(`corr_${prefix}_NO3`).value; | |
| document.getElementById('NH4').value = document.getElementById(`corr_${prefix}_NH4`).value; | |
| document.getElementById('P').value = document.getElementById(`corr_${prefix}_P`).value; | |
| document.getElementById('K').value = document.getElementById(`corr_${prefix}_K`).value; | |
| document.getElementById('Ca').value = document.getElementById(`corr_${prefix}_Ca`).value; | |
| document.getElementById('Mg').value = document.getElementById(`corr_${prefix}_Mg`).value; | |
| document.getElementById('S').value = document.getElementById(`corr_${prefix}_S`).value; | |
| document.getElementById('Cl').value = document.getElementById(`corr_${prefix}_Cl`).value; | |
| document.getElementById('EC').value = document.getElementById(`corr_${prefix}_EC`).value; | |
| // Обновляем объем | |
| const vol = document.getElementById(`corr_${prefix}_vol`); | |
| if (vol && vol.value) { | |
| document.getElementById('volume').value = vol.value; | |
| } | |
| // Запускаем полный пересчет | |
| calcAll(); | |
| } | |
| // Инициализация корректора при загрузке | |
| function initCorrector() { | |
| // Устанавливаем обработчики на все поля корректора | |
| const correctorInputs = document.querySelectorAll('.corrector-input'); | |
| correctorInputs.forEach(input => { | |
| input.addEventListener('input', onCorrectorChange); | |
| input.addEventListener('focus', function(e) { | |
| const id = e.target.id; | |
| if (id.includes('_init_')) activeCorrectColumn = 'init'; | |
| else if (id.includes('_corr_')) activeCorrectColumn = 'corr'; | |
| else if (id.includes('_final_')) activeCorrectColumn = 'final'; | |
| }); | |
| }); | |
| // Начальный расчет корректора | |
| korrection(); | |
| } | |
| // Сохранение в журнал коррекций (как в легаси tojrnlClick) | |
| function saveCorrectorToJournal() { | |
| // Получаем текущую дату в формате yyyy-mm-dd | |
| const now = new Date(); | |
| const dateStr = now.toISOString().split('T')[0]; | |
| // Получаем итоговый объем | |
| const finalVolume = parseFloat(document.getElementById('corr_final_vol')?.value) || 30; | |
| // Формируем описание (как в легаси: m1.Text) | |
| const desc = `Корректор. Скорректирован раствор до ${finalVolume} литров.`; | |
| // Получаем значения из Итогового профиля (N_2, NO3_2 и т.д. в легаси) | |
| const N = parseFloat(document.getElementById('corr_final_N')?.value) || 0; | |
| const NO3 = parseFloat(document.getElementById('corr_final_NO3')?.value) || 0; | |
| const NH4 = parseFloat(document.getElementById('corr_final_NH4')?.value) || 0; | |
| const P = parseFloat(document.getElementById('corr_final_P')?.value) || 0; | |
| const K = parseFloat(document.getElementById('corr_final_K')?.value) || 0; | |
| const Ca = parseFloat(document.getElementById('corr_final_Ca')?.value) || 0; | |
| const Mg = parseFloat(document.getElementById('corr_final_Mg')?.value) || 0; | |
| const S = parseFloat(document.getElementById('corr_final_S')?.value) || 0; | |
| const Cl = parseFloat(document.getElementById('corr_final_Cl')?.value) || 0; | |
| // Получаем микроэлементы (из основного профиля, как в легаси) | |
| const Fe = (parseFloat(document.getElementById('Fe')?.value) || 0) / 1000; | |
| const Mn = (parseFloat(document.getElementById('Mn')?.value) || 0) / 1000; | |
| const B = (parseFloat(document.getElementById('B')?.value) || 0) / 1000; | |
| const Zn = (parseFloat(document.getElementById('Zn')?.value) || 0) / 1000; | |
| const Cu = (parseFloat(document.getElementById('Cu')?.value) || 0) / 1000; | |
| const Mo = (parseFloat(document.getElementById('Mo')?.value) || 0) / 1000; | |
| const Co = (parseFloat(document.getElementById('Co')?.value) || 0) / 1000; | |
| const Si = (parseFloat(document.getElementById('Si')?.value) || 0) / 1000; | |
| // Формируем строку профиля (как в легаси str) | |
| const profileStr = `N=${N} NO3=${NO3} NH4=${NH4} P=${P} K=${K} Ca=${Ca} Mg=${Mg} S=${S} Cl=${Cl} Fe=${Fe} Mn=${Mn} B=${B} Zn=${Zn} Cu=${Cu} Mo=${Mo} Co=${Co} Si=${Si}`; | |
| // Создаем запись журнала (как в легаси: date=...;...;...) | |
| const journalEntry = { | |
| date: dateStr, | |
| desc: desc, | |
| memo: desc, // Совместимость | |
| profile: profileStr | |
| }; | |
| // Добавляем в журнал | |
| journalEntries.push(journalEntry); | |
| // Сортируем журнал по дате (как в легаси DStr.Sort) | |
| journalEntries.sort((a, b) => { | |
| const dateA = new Date(a.date); | |
| const dateB = new Date(b.date); | |
| return dateB - dateA; // Новые записи сверху | |
| }); | |
| // Обновляем список журнала | |
| updateJournalList(); | |
| // Переключаемся на вкладку "Файл" чтобы показать результат | |
| switchTab('file', document.querySelector('[onclick*="file"]')); | |
| alert('Данные корректора сохранены в журнал'); | |
| } | |
| // Функции для расчета концентратов (аналог CalcConc из легаси) | |
| // В легаси CalcConc вызывается ПОСЛЕ CalcWeight и microToWeght | |
| // и использует уже вычисленные значения gCaNO3, gKNO3, gFe и т.д. | |
| function calcConcentrates() { | |
| const volume = parseFloat(document.getElementById('volume').value) || 10; | |
| // Концентрации растворов (г/л) - читаем из полей или используем дефолтные значения | |
| const glCaNO3 = parseFloat(document.getElementById('conc_glCaNO3')?.value) || 600; | |
| const glKNO3 = parseFloat(document.getElementById('conc_glKNO3')?.value) || 250; | |
| const glNH4NO3 = parseFloat(document.getElementById('conc_glNH4NO3')?.value) || 100; | |
| const glMgNO3 = parseFloat(document.getElementById('conc_glMgNO3')?.value) || 500; | |
| const glCaCl2 = parseFloat(document.getElementById('conc_glCaCl2')?.value) || 100; | |
| const glMgSO4 = parseFloat(document.getElementById('conc_glMgSO4')?.value) || 600; | |
| const glKH2PO4 = parseFloat(document.getElementById('conc_glKH2PO4')?.value) || 150; | |
| const glK2SO4 = parseFloat(document.getElementById('conc_glK2SO4')?.value) || 100; | |
| const glFe = parseFloat(document.getElementById('conc_glFe')?.value) || 10; | |
| const glMn = parseFloat(document.getElementById('conc_glMn')?.value) || 10; | |
| const glB = parseFloat(document.getElementById('conc_glB')?.value) || 10; | |
| const glZn = parseFloat(document.getElementById('conc_glZn')?.value) || 10; | |
| const glCu = parseFloat(document.getElementById('conc_glCu')?.value) || 10; | |
| const glMo = parseFloat(document.getElementById('conc_glMo')?.value) || 10; | |
| const glCo = parseFloat(document.getElementById('conc_glCo')?.value) || 10; | |
| const glSi = parseFloat(document.getElementById('conc_glSi')?.value) || 10; | |
| const glCmplx = parseFloat(document.getElementById('conc_glCmplx')?.value) || 10; | |
| // Плотности растворов (г/мл) - читаем из полей или используем дефолтные значения | |
| const gmlCaNO3 = parseFloat(document.getElementById('conc_gmlCaNO3')?.value) || 1; | |
| const gmlKNO3 = parseFloat(document.getElementById('conc_gmlKNO3')?.value) || 1; | |
| const gmlNH4NO3 = parseFloat(document.getElementById('conc_gmlNH4NO3')?.value) || 1; | |
| const gmlMgNO3 = parseFloat(document.getElementById('conc_gmlMgNO3')?.value) || 1; | |
| const gmlCaCl2 = parseFloat(document.getElementById('conc_gmlCaCl2')?.value) || 1; | |
| const gmlMgSO4 = parseFloat(document.getElementById('conc_gmlMgSO4')?.value) || 1; | |
| const gmlKH2PO4 = parseFloat(document.getElementById('conc_gmlKH2PO4')?.value) || 1; | |
| const gmlK2SO4 = parseFloat(document.getElementById('conc_gmlK2SO4')?.value) || 1; | |
| const gmlFe = parseFloat(document.getElementById('conc_gmlFe')?.value) || 1; | |
| const gmlMn = parseFloat(document.getElementById('conc_gmlMn')?.value) || 1; | |
| const gmlB = parseFloat(document.getElementById('conc_gmlB')?.value) || 1; | |
| const gmlZn = parseFloat(document.getElementById('conc_gmlZn')?.value) || 1; | |
| const gmlCu = parseFloat(document.getElementById('conc_gmlCu')?.value) || 1; | |
| const gmlMo = parseFloat(document.getElementById('conc_gmlMo')?.value) || 1; | |
| const gmlCo = parseFloat(document.getElementById('conc_gmlCo')?.value) || 1; | |
| const gmlSi = parseFloat(document.getElementById('conc_gmlSi')?.value) || 1; | |
| const gmlCmplx = parseFloat(document.getElementById('conc_gmlCmplx')?.value) || 1; | |
| // Читаем граммы солей из полей (уже вычислены в calcWeight) | |
| // В легаси: Kf.mlCaNO3.Value:=Kf.gCaNO3.value/Kf.glCaNO3.value*1000 | |
| const gCaNO3 = parseFloat(document.getElementById('g_CaNO3')?.value) || 0; | |
| const gKNO3 = parseFloat(document.getElementById('g_KNO3')?.value) || 0; | |
| const gNH4NO3 = parseFloat(document.getElementById('g_NH4NO3')?.value) || 0; | |
| const gMgNO3 = parseFloat(document.getElementById('g_MgNO3')?.value) || 0; | |
| const gCaCl2 = parseFloat(document.getElementById('g_CaCl2')?.value) || 0; | |
| const gMgSO4 = parseFloat(document.getElementById('g_MgSO4')?.value) || 0; | |
| const gKH2PO4 = parseFloat(document.getElementById('g_KH2PO4')?.value) || 0; | |
| const gK2SO4 = parseFloat(document.getElementById('g_K2SO4')?.value) || 0; | |
| // Читаем граммы микроэлементов из полей (уже вычислены в microToWeght) | |
| // В легаси: if Kf.dFe.value >0 then Kf.gFe.value:=Kf.Fe.value/Kf.dFe.value*Kf.V.value/10000 | |
| const gFe = parseFloat(document.getElementById('g_Fe')?.value) || 0; | |
| const gMn = parseFloat(document.getElementById('g_Mn')?.value) || 0; | |
| const gB = parseFloat(document.getElementById('g_B')?.value) || 0; | |
| const gZn = parseFloat(document.getElementById('g_Zn')?.value) || 0; | |
| const gCu = parseFloat(document.getElementById('g_Cu')?.value) || 0; | |
| const gMo = parseFloat(document.getElementById('g_Mo')?.value) || 0; | |
| const gCo = parseFloat(document.getElementById('g_Co')?.value) || 0; | |
| const gSi = parseFloat(document.getElementById('g_Si')?.value) || 0; | |
| // Комплекс микроэлементов (сумма всех) | |
| const gCmplx = gFe + gMn + gB + gZn + gCu + gMo + gCo + gSi; | |
| // Получаем значения Fe, Mn и т.д. для расчёта mlFe | |
| const Fe = parseFloat(document.getElementById('Fe')?.value) || 0; | |
| const Mn = parseFloat(document.getElementById('Mn')?.value) || 0; | |
| const B = parseFloat(document.getElementById('B')?.value) || 0; | |
| const Zn = parseFloat(document.getElementById('Zn')?.value) || 0; | |
| const Cu = parseFloat(document.getElementById('Cu')?.value) || 0; | |
| const Mo = parseFloat(document.getElementById('Mo')?.value) || 0; | |
| const Co = parseFloat(document.getElementById('Co')?.value) || 0; | |
| const Si = parseFloat(document.getElementById('Si')?.value) || 0; | |
| const dFe = parseFloat(document.getElementById('micro_Fe_percent').value) || 20.1; | |
| const dMn = parseFloat(document.getElementById('micro_Mn_percent').value) || 36.4; | |
| const dB = parseFloat(document.getElementById('micro_B_percent').value) || 17.5; | |
| const dZn = parseFloat(document.getElementById('micro_Zn_percent').value) || 22.7; | |
| const dCu = parseFloat(document.getElementById('micro_Cu_percent').value) || 25.5; | |
| const dMo = parseFloat(document.getElementById('micro_Mo_percent').value) || 54.3; | |
| const dCo = parseFloat(document.getElementById('micro_Co_percent').value) || 13.0; | |
| const dSi = parseFloat(document.getElementById('micro_Si_percent').value) || 7.0; | |
| // Расчет миллилитров концентрата | |
| // Для макроэлементов: mlCaNO3 = gCaNO3 / glCaNO3 * 1000 | |
| const mlCaNO3 = gCaNO3 / glCaNO3 * 1000; | |
| const mlKNO3 = gKNO3 / glKNO3 * 1000; | |
| const mlNH4NO3 = gNH4NO3 / glNH4NO3 * 1000; | |
| const mlMgNO3 = gMgNO3 / glMgNO3 * 1000; | |
| const mlCaCl2 = gCaCl2 / glCaCl2 * 1000; | |
| const mlMgSO4 = gMgSO4 / glMgSO4 * 1000; | |
| const mlKH2PO4 = gKH2PO4 / glKH2PO4 * 1000; | |
| const mlK2SO4 = gK2SO4 / glK2SO4 * 1000; | |
| // Для микроэлементов: mlFe = (Fe / dFe * V / 10) / glFe (легаси строка 1559) | |
| const mlFe = dFe > 0 ? ((Fe / dFe * volume / 10) / glFe) : 0; | |
| const mlMn = dMn > 0 ? ((Mn / dMn * volume / 10) / glMn) : 0; | |
| const mlB = dB > 0 ? ((B / dB * volume / 10) / glB) : 0; | |
| const mlZn = dZn > 0 ? ((Zn / dZn * volume / 10) / glZn) : 0; | |
| const mlCu = dCu > 0 ? ((Cu / dCu * volume / 10) / glCu) : 0; | |
| const mlMo = dMo > 0 ? ((Mo / dMo * volume / 10) / glMo) : 0; | |
| const mlCo = dCo > 0 ? ((Co / dCo * volume / 10) / glCo) : 0; | |
| const mlSi = dSi > 0 ? ((Si / dSi * volume / 10) / glSi) : 0; | |
| const mlCmplx = dB > 0 ? ((B / dB * volume / 10) / glCmplx) : 0; | |
| // Расчет граммов для изготовления (как в легаси: ggCaNO3 = gmlCaNO3 * mlCaNO3) | |
| const ggCaNO3 = gmlCaNO3 * mlCaNO3; | |
| const ggKNO3 = gmlKNO3 * mlKNO3; | |
| const ggNH4NO3 = gmlNH4NO3 * mlNH4NO3; | |
| const ggMgNO3 = gmlMgNO3 * mlMgNO3; | |
| const ggCaCl2 = gmlCaCl2 * mlCaCl2; | |
| const ggMgSO4 = gmlMgSO4 * mlMgSO4; | |
| const ggKH2PO4 = gmlKH2PO4 * mlKH2PO4; | |
| const ggK2SO4 = gmlK2SO4 * mlK2SO4; | |
| const ggFe = gmlFe * mlFe; | |
| const ggMn = gmlMn * mlMn; | |
| const ggB = gmlB * mlB; | |
| const ggZn = gmlZn * mlZn; | |
| const ggCu = gmlCu * mlCu; | |
| const ggMo = gmlMo * mlMo; | |
| const ggCo = gmlCo * mlCo; | |
| const ggSi = gmlSi * mlSi; | |
| const ggCmplx = gmlCmplx * mlCmplx; | |
| // Расчет сводной информации для растворов A и B (как в легаси CalcConc строки 1672-1712) | |
| const Av = Math.round((mlCaNO3 + mlKNO3 + mlNH4NO3 + mlMgNO3 + mlCaCl2) * 10000) / 10000; | |
| const Am = Math.round((ggCaNO3 + ggKNO3 + ggNH4NO3 + ggMgNO3 + ggCaCl2) * 10000) / 10000; | |
| const Ak = Math.round(Am / Av * 100) / 100; | |
| const useComplex = document.getElementById('use_complex')?.checked; | |
| let Bv, Bm, Bk; | |
| if (useComplex) { | |
| Bv = Math.round((mlMgSO4 + mlKH2PO4 + mlK2SO4 + mlCmplx) * 10000) / 10000; | |
| Bm = Math.round((ggMgSO4 + ggKH2PO4 + ggK2SO4 + ggCmplx) * 10000) / 10000; | |
| Bk = Math.round(Bm / Bv * 1000) / 1000; | |
| } else { | |
| Bv = mlMgSO4 + mlKH2PO4 + mlK2SO4 + mlFe + mlMn + mlB + mlZn + mlMo + mlCu + mlCo + mlSi; | |
| Bm = Math.round((ggMgSO4 + ggKH2PO4 + ggK2SO4 + ggFe + ggMn + ggB + ggZn + ggMo + ggCo + ggSi) * 100) / 100; | |
| Bk = Math.round(Bm / Bv * 100) / 100; | |
| } | |
| // Обновление сводной информации | |
| const sumAElement = document.getElementById('sumA'); | |
| const sumBElement = document.getElementById('sumB'); | |
| if (sumAElement) { | |
| sumAElement.textContent = `Объем: ${Av.toFixed(2)} мл, вес: ${Am.toFixed(2)} гр, плотность: ${Ak.toFixed(2)} г/мл.`; | |
| } | |
| if (sumBElement) { | |
| sumBElement.textContent = `Объем: ${(Math.round(Bv * 10) / 10).toFixed(1)} мл, вес: ${Bm.toFixed(2)} гр, плотность: ${Bk.toFixed(2)} г/мл`; | |
| } | |
| // Расчёт информации о таре (как в легаси строки 1675-1680, 1708-1712) | |
| const tAml = parseFloat(document.getElementById('tank_A')?.value) || 500; | |
| const tBml = parseFloat(document.getElementById('tank_B')?.value) || 500; | |
| const Ac = tAml !== 0 ? Math.round(volume / tAml * 1000) : 0; | |
| const Aw = Math.round(tAml - Av); | |
| const Aml = Math.round(tAml / volume * 1000) / 1000; | |
| const Bc = tBml !== 0 ? Math.round(volume / tBml * 1000) : 0; | |
| const Bw = Math.round(tBml - Bv); | |
| const Bml = Math.round(tBml / volume * 1000) / 1000; | |
| const lVolAElement = document.getElementById('lVolA'); | |
| const lVolBElement = document.getElementById('lVolB'); | |
| if (lVolAElement) { | |
| lVolAElement.textContent = `Концентрат A (${Ac}:1) . Долить воды: ${Aw}мл. По ${Aml} мл на 1л.`; | |
| } | |
| if (lVolBElement) { | |
| lVolBElement.textContent = `Концентрат B (${Bc}:1) . Долить воды: ${Bw}мл. По ${Bml} мл на 1л.`; | |
| } | |
| // Обновление таблицы расчета (передаём вычисленные ml* и gg*) | |
| updateConcentrateTable('A', { | |
| CaNO3: { g_l: glCaNO3, g_ml: gmlCaNO3, ml: mlCaNO3, gr: ggCaNO3 }, | |
| KNO3: { g_l: glKNO3, g_ml: gmlKNO3, ml: mlKNO3, gr: ggKNO3 }, | |
| NH4NO3: { g_l: glNH4NO3, g_ml: gmlNH4NO3, ml: mlNH4NO3, gr: ggNH4NO3 }, | |
| MgNO3: { g_l: glMgNO3, g_ml: gmlMgNO3, ml: mlMgNO3, gr: ggMgNO3 }, | |
| CaCl2: { g_l: glCaCl2, g_ml: gmlCaCl2, ml: mlCaCl2, gr: ggCaCl2 } | |
| }); | |
| updateConcentrateTable('B', { | |
| MgSO4: { g_l: glMgSO4, g_ml: gmlMgSO4, ml: mlMgSO4, gr: ggMgSO4 }, | |
| KH2PO4: { g_l: glKH2PO4, g_ml: gmlKH2PO4, ml: mlKH2PO4, gr: ggKH2PO4 }, | |
| K2SO4: { g_l: glK2SO4, g_ml: gmlK2SO4, ml: mlK2SO4, gr: ggK2SO4 }, | |
| Fe: { g_l: glFe, g_ml: gmlFe, ml: mlFe, gr: ggFe }, | |
| Mn: { g_l: glMn, g_ml: gmlMn, ml: mlMn, gr: ggMn }, | |
| B: { g_l: glB, g_ml: gmlB, ml: mlB, gr: ggB }, | |
| Zn: { g_l: glZn, g_ml: gmlZn, ml: mlZn, gr: ggZn }, | |
| Cu: { g_l: glCu, g_ml: gmlCu, ml: mlCu, gr: ggCu }, | |
| Mo: { g_l: glMo, g_ml: gmlMo, ml: mlMo, gr: ggMo }, | |
| Co: { g_l: glCo, g_ml: gmlCo, ml: mlCo, gr: ggCo }, | |
| Si: { g_l: glSi, g_ml: gmlSi, ml: mlSi, gr: ggSi } | |
| }); | |
| // Обновляем названия солей перед обновлением изготовления | |
| updateSaltNames(); | |
| // Обновление подвкладки изготовления (передаём gg* - граммы для изготовления) | |
| updatePreparationTab({ | |
| CaNO3: ggCaNO3, KNO3: ggKNO3, NH4NO3: ggNH4NO3, MgNO3: ggMgNO3, CaCl2: ggCaCl2, | |
| MgSO4: ggMgSO4, KH2PO4: ggKH2PO4, K2SO4: ggK2SO4, | |
| Fe: ggFe, Mn: ggMn, B: ggB, Zn: ggZn, Cu: ggCu, Mo: ggMo, Co: ggCo, Si: ggSi, | |
| Cmplx: ggCmplx | |
| }); | |
| // Обновление подвкладки цены - не нужно, так как updatePriceResults() вызывается в calcAll() | |
| // и берет данные напрямую из полей g_CaNO3, g_KNO3 и т.д. | |
| } | |
| // Обновление названий микроэлементов во всех местах (аналог legacy строки 1623-1630) | |
| function updateMicroNames() { | |
| // Получаем проценты микроэлементов | |
| const dFe = parseFloat(document.getElementById('micro_Fe_percent')?.value) || 20.1; | |
| const dMn = parseFloat(document.getElementById('micro_Mn_percent')?.value) || 36.4; | |
| const dB = parseFloat(document.getElementById('micro_B_percent')?.value) || 17.5; | |
| const dZn = parseFloat(document.getElementById('micro_Zn_percent')?.value) || 22.7; | |
| const dCu = parseFloat(document.getElementById('micro_Cu_percent')?.value) || 25.5; | |
| const dMo = parseFloat(document.getElementById('micro_Mo_percent')?.value) || 54.3; | |
| const dCo = parseFloat(document.getElementById('micro_Co_percent')?.value) || 13.0; | |
| const dSi = parseFloat(document.getElementById('micro_Si_percent')?.value) || 7.0; | |
| // Формируем названия микроэлементов (как в legacy: 'Железо Fe=20.1%') | |
| const nFe = `Железо Fe=${dFe}%`; | |
| const nMn = `Марганец Mn=${dMn}%`; | |
| const nB = `Бор B=${dB}%`; | |
| const nZn = `Цинк Zn=${dZn}%`; | |
| const nCu = `Медь Cu=${dCu}%`; | |
| const nMo = `Молибден Mo=${dMo}%`; | |
| const nCo = `Кобальт Co=${dCo}%`; | |
| const nSi = `Кремний Si=${dSi}%`; | |
| // Обновляем метки на вкладке "Микро" | |
| const labelFe = document.getElementById('micro_Fe_label'); | |
| const labelMn = document.getElementById('micro_Mn_label'); | |
| const labelB = document.getElementById('micro_B_label'); | |
| const labelZn = document.getElementById('micro_Zn_label'); | |
| const labelCu = document.getElementById('micro_Cu_label'); | |
| const labelMo = document.getElementById('micro_Mo_label'); | |
| const labelCo = document.getElementById('micro_Co_label'); | |
| const labelSi = document.getElementById('micro_Si_label'); | |
| if (labelFe) labelFe.textContent = nFe; | |
| if (labelMn) labelMn.textContent = nMn; | |
| if (labelB) labelB.textContent = nB; | |
| if (labelZn) labelZn.textContent = nZn; | |
| if (labelCu) labelCu.textContent = nCu; | |
| if (labelMo) labelMo.textContent = nMo; | |
| if (labelCo) labelCo.textContent = nCo; | |
| if (labelSi) labelSi.textContent = nSi; | |
| // Возвращаем названия для использования в других функциях | |
| return { | |
| Fe: nFe, | |
| Mn: nMn, | |
| B: nB, | |
| Zn: nZn, | |
| Cu: nCu, | |
| Mo: nMo, | |
| Co: nCo, | |
| Si: nSi | |
| }; | |
| } | |
| // Обновление названий солей во всех местах (аналог SoilName из легаси строки 1990-2073) | |
| function updateSaltNames() { | |
| const round1 = (val) => Math.round(val * 10) / 10; | |
| // Кальций азотнокислый | |
| const CaNO3_Ca = parseFloat(document.getElementById('salt_CaNO3_Ca')?.value) || 16.972; | |
| const CaNO3_NO3 = parseFloat(document.getElementById('salt_CaNO3_NO3')?.value) || 11.863; | |
| const CaNO3_NH4 = parseFloat(document.getElementById('salt_CaNO3_NH4')?.value) || 0; | |
| let nCaNO3; | |
| if (CaNO3_NH4 === 0) { | |
| const ca = round1(CaNO3_Ca); | |
| if (ca === 17) nCaNO3 = 'Кальций азотнокислый Ca(NO3)2*4H2O'; | |
| else if (ca === 20) nCaNO3 = 'Кальций азотнокислый Ca(NO3)2*2H2O'; | |
| else if (ca === 24.4) nCaNO3 = 'Кальций азотнокислый Ca(NO3)2'; | |
| else nCaNO3 = `Селитра кальциевая CaO-${round1(CaNO3_Ca/0.714691)}% N-${round1(CaNO3_NH4+CaNO3_NO3)}%`; | |
| } else { | |
| nCaNO3 = `Селитра кальциевая CaO-${round1(CaNO3_Ca/0.714691)}% N-${round1(CaNO3_NH4+CaNO3_NO3)}%`; | |
| } | |
| // Калий азотнокислый | |
| const KNO3_K = parseFloat(document.getElementById('salt_KNO3_K')?.value) || 38.672; | |
| const KNO3_NO3 = parseFloat(document.getElementById('salt_KNO3_NO3')?.value) || 13.854; | |
| const nKNO3 = round1(KNO3_K) === 38.7 | |
| ? 'Калий азотнокислый KNO3' | |
| : `Селитра калиевая K2O-${round1(KNO3_K/0.830148)}% N-${round1(KNO3_NO3)}%`; | |
| // Аммоний азотнокислый | |
| const NH4NO3_NH4 = parseFloat(document.getElementById('salt_NH4NO3_NH4')?.value) || 17.499; | |
| const NH4NO3_NO3 = parseFloat(document.getElementById('salt_NH4NO3_NO3')?.value) || 17.499; | |
| const nNH4NO3 = round1(NH4NO3_NO3) === 17.5 | |
| ? 'Аммоний азотнокислый NH4NO3' | |
| : `Селитра аммиачная N-${round1(NH4NO3_NH4+NH4NO3_NO3)}%`; | |
| // Магний сернокислый | |
| const MgSO4_Mg = parseFloat(document.getElementById('salt_MgSO4_Mg')?.value) || 9.861; | |
| const MgSO4_S = parseFloat(document.getElementById('salt_MgSO4_S')?.value) || 13.01; | |
| let nMgSO4; | |
| const mg = round1(MgSO4_Mg); | |
| if (mg === 9.9) nMgSO4 = 'Магний сернокислый MgSO4*7H2O'; | |
| else if (mg === 20.2) nMgSO4 = 'Магний сернокислый MgSO4'; | |
| else nMgSO4 = `Сульфат магния MgO-${round1(MgSO4_Mg/0.603036)}% SO3-${round1(MgSO4_S/0.400496)}%`; | |
| // Калий фосфорнокислый | |
| const KH2PO4_K = parseFloat(document.getElementById('salt_KH2PO4_K')?.value) || 28.731; | |
| const KH2PO4_P = parseFloat(document.getElementById('salt_KH2PO4_P')?.value) || 22.761; | |
| const nKH2PO4 = round1(KH2PO4_K) === 28.7 | |
| ? 'Калий фосфорнокислый KH2PO4' | |
| : `Монофосфат калия K2O-${round1(KH2PO4_K/0.830148)}% P2O5-${round1(KH2PO4_P/0.436421)}%`; | |
| // Калий сернокислый | |
| const K2SO4_K = parseFloat(document.getElementById('salt_K2SO4_K')?.value) || 44.874; | |
| const K2SO4_S = parseFloat(document.getElementById('salt_K2SO4_S')?.value) || 18.401; | |
| const nK2SO4 = round1(K2SO4_K) === 44.9 | |
| ? 'Калий сернокислый K2SO4' | |
| : `Сульфат калия K2O-${round1(K2SO4_K/0.830148)}% SO3-${round1(K2SO4_S/0.400496)}%`; | |
| // Магний азотнокислый | |
| const MgNO3_Mg = parseFloat(document.getElementById('salt_MgNO3_Mg')?.value) || 9.479; | |
| const MgNO3_NO3 = parseFloat(document.getElementById('salt_MgNO3_NO3')?.value) || 10.926; | |
| let nMgNO3; | |
| const mgn = round1(MgNO3_Mg); | |
| if (mgn === 9.5) nMgNO3 = 'Магний азотнокислый Mg(NO3)2*6H2O'; | |
| else if (mgn === 16.4) nMgNO3 = 'Магний азотнокислый Mg(NO3)2'; | |
| else nMgNO3 = `Селитра магниевая MgO-${round1(MgNO3_Mg/0.603036)}% N-${round1(MgNO3_NO3)}%`; | |
| // Хлорид кальция | |
| const CaCl2_Ca = parseFloat(document.getElementById('salt_CaCl2_Ca')?.value) || 18.294; | |
| const CaCl2_Cl = parseFloat(document.getElementById('salt_CaCl2_Cl')?.value) || 32.366; | |
| let nCaCl2; | |
| const cacl = round1(CaCl2_Ca); | |
| if (cacl === 18.3) nCaCl2 = 'Хлорид кальция 6-водный CaCl2*6H2O'; | |
| else if (cacl === 36.1) nCaCl2 = 'Хлорид кальция безводный CaCl2'; | |
| else nCaCl2 = `Кальций хлористый CaO-${round1(CaCl2_Ca/0.714691)}% Cl-${round1(CaCl2_Cl)}%`; | |
| // Обновляем метки на вкладке "Макро" | |
| const labelCaNO3 = document.getElementById('label_CaNO3'); | |
| const labelKNO3 = document.getElementById('label_KNO3'); | |
| const labelNH4NO3 = document.getElementById('label_NH4NO3'); | |
| const labelMgSO4 = document.getElementById('label_MgSO4'); | |
| const labelKH2PO4 = document.getElementById('label_KH2PO4'); | |
| const labelK2SO4 = document.getElementById('label_K2SO4'); | |
| const labelMgNO3 = document.getElementById('label_MgNO3'); | |
| const labelCaCl2 = document.getElementById('label_CaCl2'); | |
| if (labelCaNO3) labelCaNO3.textContent = nCaNO3; | |
| if (labelKNO3) labelKNO3.textContent = nKNO3; | |
| if (labelNH4NO3) labelNH4NO3.textContent = nNH4NO3; | |
| if (labelMgSO4) labelMgSO4.textContent = nMgSO4; | |
| if (labelKH2PO4) labelKH2PO4.textContent = nKH2PO4; | |
| if (labelK2SO4) labelK2SO4.textContent = nK2SO4; | |
| if (labelMgNO3) labelMgNO3.textContent = nMgNO3; | |
| if (labelCaCl2) labelCaCl2.textContent = nCaCl2; | |
| // Возвращаем названия для использования в других функциях | |
| return { | |
| CaNO3: nCaNO3, | |
| KNO3: nKNO3, | |
| NH4NO3: nNH4NO3, | |
| MgSO4: nMgSO4, | |
| KH2PO4: nKH2PO4, | |
| K2SO4: nK2SO4, | |
| MgNO3: nMgNO3, | |
| CaCl2: nCaCl2 | |
| }; | |
| } | |
| function updateConcentrateTable(section, data) { | |
| const saltNames = updateSaltNames(); | |
| const tableBodies = document.querySelectorAll('#calc .concentrate-table tbody'); | |
| tableBodies.forEach(tableBody => { | |
| const rows = tableBody.querySelectorAll('tr'); | |
| rows.forEach(row => { | |
| const firstCell = row.cells[0]; | |
| const cellText = firstCell.textContent; | |
| let key = ''; | |
| // Определяем ключ по содержимому (более надёжно чем по тексту) | |
| if (cellText.includes('азотнокислый') && cellText.includes('Ca')) key = 'CaNO3'; | |
| else if (cellText.includes('азотнокислый') && cellText.includes('K')) key = 'KNO3'; | |
| else if (cellText.includes('азотнокислый') && cellText.includes('NH4')) key = 'NH4NO3'; | |
| else if (cellText.includes('азотнокислый') && cellText.includes('Mg')) key = 'MgNO3'; | |
| else if (cellText.includes('хлорид') || cellText.includes('Хлорид') || cellText.includes('хлористый')) key = 'CaCl2'; | |
| else if (cellText.includes('сернокислый') && cellText.includes('Mg')) key = 'MgSO4'; | |
| else if (cellText.includes('фосфорнокислый') || cellText.includes('Монофосфат')) key = 'KH2PO4'; | |
| else if (cellText.includes('сернокислый') && cellText.includes('K')) key = 'K2SO4'; | |
| else if (cellText.includes('Fe=')) key = 'Fe'; | |
| else if (cellText.includes('Mn=')) key = 'Mn'; | |
| else if (cellText.includes('B=')) key = 'B'; | |
| else if (cellText.includes('Zn=')) key = 'Zn'; | |
| else if (cellText.includes('Cu=')) key = 'Cu'; | |
| else if (cellText.includes('Mo=')) key = 'Mo'; | |
| else if (cellText.includes('Co=')) key = 'Co'; | |
| else if (cellText.includes('Si=')) key = 'Si'; | |
| if (key && data[key]) { | |
| // Обновляем название соли | |
| if (saltNames[key]) { | |
| firstCell.textContent = saltNames[key]; | |
| } | |
| // Обновляем значения | |
| row.cells[1].querySelector('input').value = data[key].g_l.toFixed(1); | |
| row.cells[2].querySelector('input').value = data[key].g_ml.toFixed(4); | |
| row.cells[3].querySelector('input').value = data[key].ml.toFixed(2); | |
| row.cells[4].querySelector('input').value = data[key].gr.toFixed(2); | |
| } | |
| }); | |
| }); | |
| } | |
| function updatePreparationTab(weights) { | |
| // Обновляем весовые значения для макроэлементов | |
| document.getElementById('g2gCaNO3').value = weights.CaNO3.toFixed(2); | |
| document.getElementById('g2gKNO3').value = weights.KNO3.toFixed(2); | |
| document.getElementById('g2gNH4NO3').value = weights.NH4NO3.toFixed(2); | |
| document.getElementById('g2gMgNO3').value = weights.MgNO3.toFixed(2); | |
| document.getElementById('g2gCaCl2').value = weights.CaCl2.toFixed(2); | |
| document.getElementById('g2gMgSO4').value = weights.MgSO4.toFixed(2); | |
| document.getElementById('g2gKH2PO4').value = weights.KH2PO4.toFixed(2); | |
| document.getElementById('g2gK2SO4').value = weights.K2SO4.toFixed(2); | |
| // Обновляем весовые значения для микроэлементов | |
| document.getElementById('g2gFe').value = weights.Fe.toFixed(2); | |
| document.getElementById('g2gMn').value = weights.Mn.toFixed(2); | |
| document.getElementById('g2gB').value = weights.B.toFixed(2); | |
| document.getElementById('g2gZn').value = weights.Zn.toFixed(2); | |
| document.getElementById('g2gCu').value = weights.Cu.toFixed(2); | |
| document.getElementById('g2gMo').value = weights.Mo.toFixed(2); | |
| document.getElementById('g2gCo').value = weights.Co.toFixed(2); | |
| document.getElementById('g2gSi').value = weights.Si.toFixed(2); | |
| // Получаем названия солей из единой системы (аналог SoilName из легаси) | |
| const saltNames = updateSaltNames(); | |
| // Получаем названия микроэлементов из единой системы (аналог legacy строки 1623-1630) | |
| const microNames = updateMicroNames(); | |
| // Получаем концентрации для изготовления (аналог CalcConc из легаси) | |
| const glCaNO3 = parseFloat(document.getElementById('conc_glCaNO3')?.value) || 600; | |
| const glKNO3 = parseFloat(document.getElementById('conc_glKNO3')?.value) || 250; | |
| const glNH4NO3 = parseFloat(document.getElementById('conc_glNH4NO3')?.value) || 100; | |
| const glMgNO3 = parseFloat(document.getElementById('conc_glMgNO3')?.value) || 500; | |
| const glCaCl2 = parseFloat(document.getElementById('conc_glCaCl2')?.value) || 100; | |
| const glMgSO4 = parseFloat(document.getElementById('conc_glMgSO4')?.value) || 600; | |
| const glKH2PO4 = parseFloat(document.getElementById('conc_glKH2PO4')?.value) || 150; | |
| const glK2SO4 = parseFloat(document.getElementById('conc_glK2SO4')?.value) || 100; | |
| const gmlCaNO3 = parseFloat(document.getElementById('conc_gmlCaNO3')?.value) || 1.0000; | |
| const gmlKNO3 = parseFloat(document.getElementById('conc_gmlKNO3')?.value) || 1.0000; | |
| const gmlNH4NO3 = parseFloat(document.getElementById('conc_gmlNH4NO3')?.value) || 1.0000; | |
| const gmlMgNO3 = parseFloat(document.getElementById('conc_gmlMgNO3')?.value) || 1.0000; | |
| const gmlCaCl2 = parseFloat(document.getElementById('conc_gmlCaCl2')?.value) || 1.0000; | |
| const gmlMgSO4 = parseFloat(document.getElementById('conc_gmlMgSO4')?.value) || 1.0000; | |
| const gmlKH2PO4 = parseFloat(document.getElementById('conc_gmlKH2PO4')?.value) || 1.0000; | |
| const gmlK2SO4 = parseFloat(document.getElementById('conc_gmlK2SO4')?.value) || 1.0000; | |
| // Обновляем названия с весовыми значениями и концентрациями (как в legacy CalcConc строки 1652-1659) | |
| const k2nCaNO3 = document.querySelector('label[for="k2nCaNO3"]'); | |
| const k2nKNO3 = document.querySelector('label[for="k2nKNO3"]'); | |
| const k2nNH4NO3 = document.querySelector('label[for="k2nNH4NO3"]'); | |
| const k2nMgNO3 = document.querySelector('label[for="k2nMgNO3"]'); | |
| const k2nMgSO4 = document.querySelector('label[for="k2nMgSO4"]'); | |
| const k2nKH2PO4 = document.querySelector('label[for="k2nKH2PO4"]'); | |
| const k2nK2SO4 = document.querySelector('label[for="k2nK2SO4"]'); | |
| const k2nCaCl2 = document.querySelector('label[for="k2nCaCl2"]'); | |
| if (k2nCaNO3) k2nCaNO3.textContent = `${saltNames.CaNO3} ${weights.CaNO3.toFixed(3)} г. (${glCaNO3} г/л, ${gmlCaNO3.toFixed(4)} г/мл)`; | |
| if (k2nKNO3) k2nKNO3.textContent = `${saltNames.KNO3} ${weights.KNO3.toFixed(3)} г. (${glKNO3} г/л, ${gmlKNO3.toFixed(4)} г/мл)`; | |
| if (k2nNH4NO3) k2nNH4NO3.textContent = `${saltNames.NH4NO3} ${weights.NH4NO3.toFixed(3)} г. (${glNH4NO3} г/л, ${gmlNH4NO3.toFixed(4)} г/мл)`; | |
| if (k2nMgNO3) k2nMgNO3.textContent = `${saltNames.MgNO3} ${weights.MgNO3.toFixed(3)} г. (${glMgNO3} г/л, ${gmlMgNO3.toFixed(4)} г/мл)`; | |
| if (k2nMgSO4) k2nMgSO4.textContent = `${saltNames.MgSO4} ${weights.MgSO4.toFixed(3)} г. (${glMgSO4} г/л, ${gmlMgSO4.toFixed(4)} г/мл)`; | |
| if (k2nKH2PO4) k2nKH2PO4.textContent = `${saltNames.KH2PO4} ${weights.KH2PO4.toFixed(3)} г. (${glKH2PO4} г/л, ${gmlKH2PO4.toFixed(4)} г/мл)`; | |
| if (k2nK2SO4) k2nK2SO4.textContent = `${saltNames.K2SO4} ${weights.K2SO4.toFixed(3)} г. (${glK2SO4} г/л, ${gmlK2SO4.toFixed(4)} г/мл)`; | |
| if (k2nCaCl2) k2nCaCl2.textContent = `${saltNames.CaCl2} ${weights.CaCl2.toFixed(3)} г. (${glCaCl2} г/л, ${gmlCaCl2.toFixed(4)} г/мл)`; | |
| // Обновляем названия микроэлементов (аналог CalcConc строки 1662-1669) | |
| const glFe = parseFloat(document.getElementById('conc_glFe')?.value) || 10; | |
| const glMn = parseFloat(document.getElementById('conc_glMn')?.value) || 10; | |
| const glB = parseFloat(document.getElementById('conc_glB')?.value) || 10; | |
| const glZn = parseFloat(document.getElementById('conc_glZn')?.value) || 10; | |
| const glCu = parseFloat(document.getElementById('conc_glCu')?.value) || 10; | |
| const glMo = parseFloat(document.getElementById('conc_glMo')?.value) || 10; | |
| const glCo = parseFloat(document.getElementById('conc_glCo')?.value) || 10; | |
| const glSi = parseFloat(document.getElementById('conc_glSi')?.value) || 10; | |
| const gmlFe = parseFloat(document.getElementById('conc_gmlFe')?.value) || 1.000; | |
| const gmlMn = parseFloat(document.getElementById('conc_gmlMn')?.value) || 1.000; | |
| const gmlB = parseFloat(document.getElementById('conc_gmlB')?.value) || 1.000; | |
| const gmlZn = parseFloat(document.getElementById('conc_gmlZn')?.value) || 1.000; | |
| const gmlCu = parseFloat(document.getElementById('conc_gmlCu')?.value) || 1.000; | |
| const gmlMo = parseFloat(document.getElementById('conc_gmlMo')?.value) || 1.000; | |
| const gmlCo = parseFloat(document.getElementById('conc_gmlCo')?.value) || 1.000; | |
| const gmlSi = parseFloat(document.getElementById('conc_gmlSi')?.value) || 1.000; | |
| const l2Cmplx = document.querySelector('.prep-complex-text'); | |
| const l2Fe = document.querySelector('label[for="l2Fe"]'); | |
| const l2Mn = document.querySelector('label[for="l2Mn"]'); | |
| const l2B = document.querySelector('label[for="l2B"]'); | |
| const l2Zn = document.querySelector('label[for="l2Zn"]'); | |
| const l2Cu = document.querySelector('label[for="l2Cu"]'); | |
| const l2Mo = document.querySelector('label[for="l2Mo"]'); | |
| const l2Co = document.querySelector('label[for="l2Co"]'); | |
| const l2Si = document.querySelector('label[for="l2Si"]'); | |
| if (l2Cmplx) l2Cmplx.textContent = `Комплекс микроэлементов ${weights.Cmplx.toFixed(3)} г. (10 г/л, 1.000 г/мл)`; | |
| if (l2Fe) l2Fe.textContent = `${microNames.Fe} ${weights.Fe.toFixed(3)} г. (${glFe} г/л, ${gmlFe.toFixed(3)} г/мл)`; | |
| if (l2Mn) l2Mn.textContent = `${microNames.Mn} ${weights.Mn.toFixed(3)} г. (${glMn} г/л, ${gmlMn.toFixed(3)} г/мл)`; | |
| if (l2B) l2B.textContent = `${microNames.B} ${weights.B.toFixed(3)} г. (${glB} г/л, ${gmlB.toFixed(3)} г/мл)`; | |
| if (l2Zn) l2Zn.textContent = `${microNames.Zn} ${weights.Zn.toFixed(3)} г. (${glZn} г/л, ${gmlZn.toFixed(3)} г/мл)`; | |
| if (l2Cu) l2Cu.textContent = `${microNames.Cu} ${weights.Cu.toFixed(3)} г. (${glCu} г/л, ${gmlCu.toFixed(3)} г/мл)`; | |
| if (l2Mo) l2Mo.textContent = `${microNames.Mo} ${weights.Mo.toFixed(3)} г. (${glMo} г/л, ${gmlMo.toFixed(3)} г/мл)`; | |
| if (l2Co) l2Co.textContent = `${microNames.Co} ${weights.Co.toFixed(3)} г. (${glCo} г/л, ${gmlCo.toFixed(3)} г/мл)`; | |
| if (l2Si) l2Si.textContent = `${microNames.Si} ${weights.Si.toFixed(3)} г. (${glSi} г/л, ${gmlSi.toFixed(3)} г/мл)`; | |
| } | |
| // Перевод фактических навесок в профиль (аналог btchClick из легаси строки 4269-4301) | |
| function transferToProfile() { | |
| // 1. Пересчитываем граммы из навесок: gSalt = (g2gSalt * glSalt) / (1000 * gmlSalt) | |
| const g2g = (id) => parseFloat(document.getElementById(id)?.value) || 0; | |
| const gl = (id) => parseFloat(document.getElementById(id)?.value) || 0; | |
| const gml = (id) => parseFloat(document.getElementById(id)?.value) || 1; | |
| // Обновляем граммы макроэлементов | |
| document.getElementById('g_CaNO3').value = ((g2g('g2gCaNO3') * gl('conc_glCaNO3')) / (1000 * gml('conc_gmlCaNO3'))).toFixed(5); | |
| document.getElementById('g_KNO3').value = ((g2g('g2gKNO3') * gl('conc_glKNO3')) / (1000 * gml('conc_gmlKNO3'))).toFixed(5); | |
| document.getElementById('g_NH4NO3').value = ((g2g('g2gNH4NO3') * gl('conc_glNH4NO3')) / (1000 * gml('conc_gmlNH4NO3'))).toFixed(5); | |
| document.getElementById('g_MgSO4').value = ((g2g('g2gMgSO4') * gl('conc_glMgSO4')) / (1000 * gml('conc_gmlMgSO4'))).toFixed(5); | |
| document.getElementById('g_KH2PO4').value = ((g2g('g2gKH2PO4') * gl('conc_glKH2PO4')) / (1000 * gml('conc_gmlKH2PO4'))).toFixed(5); | |
| document.getElementById('g_K2SO4').value = ((g2g('g2gK2SO4') * gl('conc_glK2SO4')) / (1000 * gml('conc_gmlK2SO4'))).toFixed(5); | |
| document.getElementById('g_MgNO3').value = ((g2g('g2gMgNO3') * gl('conc_glMgNO3')) / (1000 * gml('conc_gmlMgNO3'))).toFixed(5); | |
| document.getElementById('g_CaCl2').value = ((g2g('g2gCaCl2') * gl('conc_glCaCl2')) / (1000 * gml('conc_gmlCaCl2'))).toFixed(5); | |
| // Обновляем граммы микроэлементов | |
| document.getElementById('g_Fe').value = ((g2g('g2gFe') * gl('conc_glFe')) / (1000 * gml('conc_gmlFe'))).toFixed(5); | |
| document.getElementById('g_Mn').value = ((g2g('g2gMn') * gl('conc_glMn')) / (1000 * gml('conc_gmlMn'))).toFixed(5); | |
| document.getElementById('g_B').value = ((g2g('g2gB') * gl('conc_glB')) / (1000 * gml('conc_gmlB'))).toFixed(5); | |
| document.getElementById('g_Zn').value = ((g2g('g2gZn') * gl('conc_glZn')) / (1000 * gml('conc_gmlZn'))).toFixed(5); | |
| document.getElementById('g_Cu').value = ((g2g('g2gCu') * gl('conc_glCu')) / (1000 * gml('conc_gmlCu'))).toFixed(5); | |
| document.getElementById('g_Mo').value = ((g2g('g2gMo') * gl('conc_glMo')) / (1000 * gml('conc_gmlMo'))).toFixed(5); | |
| document.getElementById('g_Co').value = ((g2g('g2gCo') * gl('conc_glCo')) / (1000 * gml('conc_gmlCo'))).toFixed(5); | |
| document.getElementById('g_Si').value = ((g2g('g2gSi') * gl('conc_glSi')) / (1000 * gml('conc_gmlSi'))).toFixed(5); | |
| // 2. WeghtTomicro - пересчет микроэлементов из граммов | |
| weightToMicro(); | |
| // 3. fromWeight - пересчет макроэлементов (вызываем onSaltWeightChange который это делает) | |
| onSaltWeightChange(); | |
| // 4. CalculateS - расчет серы (вызывается внутри calcAll) | |
| // 5. CalcKoef - расчет коэффициентов (вызывается внутри calcAll) | |
| // 6. CalcEC - расчет EC (вызывается внутри calcAll) | |
| // 7. genProfile - обновление профиля (вызывается внутри calcAll) | |
| } | |
| // Переменные для автоповтора крутилок | |
| let priceAdjustInterval = null; | |
| let priceAdjustTimeout = null; | |
| // Функция для изменения цены с помощью кастомных крутилок | |
| function adjustPrice(inputId, delta) { | |
| const input = document.getElementById(inputId); | |
| if (!input) return; | |
| const currentValue = parseFloat(input.value) || 0; | |
| const newValue = Math.max(0, currentValue + delta); // Не даем уйти в минус | |
| input.value = newValue.toFixed(4); | |
| updatePriceResults(); | |
| } | |
| // Начать автоповтор при удержании кнопки | |
| function startPriceAdjust(inputId, delta) { | |
| // Первое нажатие | |
| adjustPrice(inputId, delta); | |
| // Задержка перед началом автоповтора | |
| priceAdjustTimeout = setTimeout(() => { | |
| // Автоповтор каждые 50ms | |
| priceAdjustInterval = setInterval(() => { | |
| adjustPrice(inputId, delta); | |
| }, 50); | |
| }, 300); // Начинаем автоповтор через 300ms | |
| } | |
| // Остановить автоповтор | |
| function stopPriceAdjust() { | |
| if (priceAdjustTimeout) { | |
| clearTimeout(priceAdjustTimeout); | |
| priceAdjustTimeout = null; | |
| } | |
| if (priceAdjustInterval) { | |
| clearInterval(priceAdjustInterval); | |
| priceAdjustInterval = null; | |
| } | |
| } | |
| // Инициализация обработчиков для кастомных крутилок (делегирование событий) | |
| function initPriceSpinners() { | |
| document.addEventListener('mousedown', (e) => { | |
| const button = e.target.closest('.price-spinner button'); | |
| if (!button) return; | |
| const inputId = button.getAttribute('data-input'); | |
| const delta = parseFloat(button.getAttribute('data-delta')); | |
| if (inputId && !isNaN(delta)) { | |
| startPriceAdjust(inputId, delta); | |
| } | |
| }); | |
| document.addEventListener('mouseup', (e) => { | |
| if (e.target.closest('.price-spinner button')) { | |
| stopPriceAdjust(); | |
| } | |
| }); | |
| document.addEventListener('mouseleave', (e) => { | |
| if (e.target.closest('.price-spinner button')) { | |
| stopPriceAdjust(); | |
| } | |
| }); | |
| } | |
| // Функция обновления результатов цен (аналог price() из легаси строки 1073-1147) | |
| function updatePriceResults() { | |
| // Получаем названия солей и микроэлементов (аналог knCaNO3.caption, lFe.caption из легаси) | |
| const saltNames = updateSaltNames(); | |
| const microNames = updateMicroNames(); | |
| // Получаем веса удобрений из вкладки "Расчет" | |
| const weights = { | |
| CaNO3: parseFloat(document.getElementById('g_CaNO3')?.value) || 0, | |
| KNO3: parseFloat(document.getElementById('g_KNO3')?.value) || 0, | |
| NH4NO3: parseFloat(document.getElementById('g_NH4NO3')?.value) || 0, | |
| MgNO3: parseFloat(document.getElementById('g_MgNO3')?.value) || 0, | |
| CaCl2: parseFloat(document.getElementById('g_CaCl2')?.value) || 0, | |
| MgSO4: parseFloat(document.getElementById('g_MgSO4')?.value) || 0, | |
| KH2PO4: parseFloat(document.getElementById('g_KH2PO4')?.value) || 0, | |
| K2SO4: parseFloat(document.getElementById('g_K2SO4')?.value) || 0, | |
| Fe: parseFloat(document.getElementById('g_Fe')?.value) || 0, | |
| Mn: parseFloat(document.getElementById('g_Mn')?.value) || 0, | |
| B: parseFloat(document.getElementById('g_B')?.value) || 0, | |
| Zn: parseFloat(document.getElementById('g_Zn')?.value) || 0, | |
| Cu: parseFloat(document.getElementById('g_Cu')?.value) || 0, | |
| Mo: parseFloat(document.getElementById('g_Mo')?.value) || 0, | |
| Co: parseFloat(document.getElementById('g_Co')?.value) || 0, | |
| Si: parseFloat(document.getElementById('g_Si')?.value) || 0, | |
| Cmplx: parseFloat(document.getElementById('g_Cmplx')?.value) || 0 | |
| }; | |
| // Получаем цены за грамм | |
| const prices = { | |
| CaNO3: parseFloat(document.getElementById('price_CaNO3')?.value) || 0, | |
| KNO3: parseFloat(document.getElementById('price_KNO3')?.value) || 0, | |
| NH4NO3: parseFloat(document.getElementById('price_NH4NO3')?.value) || 0, | |
| MgNO3: parseFloat(document.getElementById('price_MgNO3')?.value) || 0, | |
| CaCl2: parseFloat(document.getElementById('price_CaCl2')?.value) || 0, | |
| MgSO4: parseFloat(document.getElementById('price_MgSO4')?.value) || 0, | |
| KH2PO4: parseFloat(document.getElementById('price_KH2PO4')?.value) || 0, | |
| K2SO4: parseFloat(document.getElementById('price_K2SO4')?.value) || 0, | |
| Fe: parseFloat(document.getElementById('price_Fe')?.value) || 0, | |
| Mn: parseFloat(document.getElementById('price_Mn')?.value) || 0, | |
| B: parseFloat(document.getElementById('price_B')?.value) || 0, | |
| Zn: parseFloat(document.getElementById('price_Zn')?.value) || 0, | |
| Cu: parseFloat(document.getElementById('price_Cu')?.value) || 0, | |
| Mo: parseFloat(document.getElementById('price_Mo')?.value) || 0, | |
| Co: parseFloat(document.getElementById('price_Co')?.value) || 0, | |
| Si: parseFloat(document.getElementById('price_Si')?.value) || 0, | |
| Cmplx: parseFloat(document.getElementById('price_Cmplx')?.value) || 0 | |
| }; | |
| // Проверяем использование комплекса микроэлементов | |
| const useComplex = document.getElementById('use_complex')?.checked || false; | |
| let totalPrice = 0; | |
| // Рассчитываем цены для макроэлементов (аналог строк 1087-1094) | |
| ['CaNO3', 'KNO3', 'NH4NO3', 'MgNO3', 'CaCl2', 'MgSO4', 'KH2PO4', 'K2SO4'].forEach(key => { | |
| const price = Math.round(prices[key] * weights[key] * 100) / 100; | |
| totalPrice += price; | |
| const resultEl = document.getElementById(`price_result_${key}`); | |
| const nameEl = resultEl?.previousElementSibling; // элемент с классом price-name | |
| if (resultEl) { | |
| // Обновляем название (аналог knCaNO3.caption) | |
| if (nameEl && nameEl.classList.contains('price-name') && saltNames[key]) { | |
| nameEl.textContent = saltNames[key]; | |
| } | |
| // Обновляем результат (вес + цена) | |
| resultEl.textContent = `${(Math.round(weights[key] * 1000) / 1000).toFixed(3)} г. цена: ${price.toFixed(2)}`; | |
| } | |
| }); | |
| // Рассчитываем цены для микроэлементов в зависимости от режима | |
| if (useComplex) { | |
| // Комплекс микроэлементов (аналог строки 1108) | |
| const price = Math.round(prices.Cmplx * weights.Cmplx * 100) / 100; | |
| totalPrice += price; | |
| const resultEl = document.getElementById('price_result_Cmplx'); | |
| const nameEl = resultEl?.previousElementSibling; | |
| if (resultEl) { | |
| // Название комплекса статичное "Комплекс микроэлементов" | |
| if (nameEl && nameEl.classList.contains('price-name')) { | |
| nameEl.textContent = 'Комплекс микроэлементов'; | |
| } | |
| resultEl.textContent = `${(Math.round(weights.Cmplx * 1000) / 1000).toFixed(3)} г. цена: ${price.toFixed(2)}`; | |
| } | |
| } else { | |
| // Отдельные микроэлементы (аналог строк 1133-1140) | |
| ['Fe', 'Mn', 'B', 'Zn', 'Cu', 'Mo', 'Co', 'Si'].forEach(key => { | |
| const price = Math.round(prices[key] * weights[key] * 100) / 100; | |
| totalPrice += price; | |
| const resultEl = document.getElementById(`price_result_${key}`); | |
| const nameEl = resultEl?.previousElementSibling; | |
| if (resultEl) { | |
| // Обновляем название (аналог lFe.caption) | |
| if (nameEl && nameEl.classList.contains('price-name') && microNames[key]) { | |
| nameEl.textContent = microNames[key]; | |
| } | |
| // Обновляем результат (вес + цена) | |
| resultEl.textContent = `${(Math.round(weights[key] * 1000) / 1000).toFixed(3)} г. цена: ${price.toFixed(2)}`; | |
| } | |
| }); | |
| } | |
| // Обновляем общую стоимость и цену за литр | |
| const volume = parseFloat(document.getElementById('volume')?.value) || 1; | |
| const pricePerLiter = volume > 0 ? Math.round(totalPrice / volume * 100) / 100 : 0; | |
| // Обновляем в подвкладке "Цена" | |
| document.getElementById('total_price').textContent = (Math.round(totalPrice * 100) / 100).toFixed(2); | |
| document.getElementById('price_per_liter').textContent = pricePerLiter.toFixed(2); | |
| // Обновляем внизу страницы (аналог lprice из легаси строка 1145) | |
| const bottomTotalPrice = document.getElementById('bottom_total_price'); | |
| const bottomPricePerLiter = document.getElementById('bottom_price_per_liter'); | |
| if (bottomTotalPrice) bottomTotalPrice.textContent = (Math.round(totalPrice * 100) / 100).toFixed(2); | |
| if (bottomPricePerLiter) bottomPricePerLiter.textContent = pricePerLiter.toFixed(2); | |
| } | |
| // Обработчики событий для полей цен и тары | |
| function addPriceEventListeners() { | |
| const priceInputs = document.querySelectorAll('#price input[type="number"]'); | |
| priceInputs.forEach(input => { | |
| input.addEventListener('input', () => { | |
| updatePriceResults(); | |
| }); | |
| // Форматирование до 4 знаков после запятой при потере фокуса | |
| input.addEventListener('blur', () => { | |
| const value = parseFloat(input.value) || 0; | |
| input.value = value.toFixed(4); | |
| }); | |
| }); | |
| // Обработчики для полей тары | |
| const tankA = document.getElementById('tank_A'); | |
| const tankB = document.getElementById('tank_B'); | |
| // Обработчики для полей ввода в подвкладке Изготовление | |
| const preparationInputs = document.querySelectorAll('#preparation input[type="text"]'); | |
| preparationInputs.forEach(input => { | |
| input.addEventListener('input', () => { | |
| // Здесь можно добавить логику для обработки изменений полей ввода | |
| console.log(`Field ${input.id} changed to: ${input.value}`); | |
| }); | |
| }); | |
| // Обработчики для чекбоксов в подвкладке Изготовление | |
| const preparationCheckboxes = document.querySelectorAll('#preparation input[type="checkbox"]'); | |
| preparationCheckboxes.forEach(checkbox => { | |
| checkbox.addEventListener('change', () => { | |
| // Здесь можно добавить логику для обработки изменений чекбоксов | |
| console.log(`Checkbox ${checkbox.id} changed to: ${checkbox.checked}`); | |
| }); | |
| }); | |
| // Обработчик для чекбокса комплекса микроэлементов (как в legacy) | |
| const cbCmplx = document.getElementById('cbCmplx'); | |
| if (cbCmplx) { | |
| cbCmplx.addEventListener('change', () => { | |
| toggleMicroElements(cbCmplx.checked); | |
| }); | |
| } | |
| // Обработчики для полей миксера | |
| const addrMixer = document.getElementById('addrMixer'); | |
| const nmix = document.getElementById('nmix'); | |
| if (addrMixer) { | |
| addrMixer.addEventListener('input', () => { | |
| console.log(`Mixer address changed to: ${addrMixer.value}`); | |
| }); | |
| } | |
| if (nmix) { | |
| nmix.addEventListener('input', () => { | |
| console.log(`Mix number changed to: ${nmix.value}`); | |
| }); | |
| } | |
| // Обработчики для кнопок изготовления | |
| const btnManufacture = document.getElementById('btnManufacture'); | |
| const btnToProfile = document.getElementById('btnToProfile'); | |
| const btnToJournal = document.getElementById('btnToJournal'); | |
| if (btnManufacture) { | |
| btnManufacture.addEventListener('click', () => { | |
| manufactureSolution(); | |
| }); | |
| } | |
| if (btnToProfile) { | |
| btnToProfile.addEventListener('click', () => { | |
| transferToProfile(); | |
| }); | |
| } | |
| if (btnToJournal) { | |
| btnToJournal.addEventListener('click', () => { | |
| addToJournal(); | |
| }); | |
| } | |
| } | |
| // Функции обработки кнопок изготовления | |
| function manufactureSolution() { | |
| // Пересчитываем веса перед отправкой (как в легаси перед Button2Click вызывается CalcAll -> CalcConc) | |
| calcConcentrates(); | |
| const addrMixerInput = document.getElementById('addrMixer'); | |
| const nmixInput = document.getElementById('nmix'); | |
| if (!addrMixerInput || !nmixInput) { | |
| console.warn('Mixer controls not found'); | |
| return; | |
| } | |
| const addrMixer = addrMixerInput.value.trim(); | |
| const nmix = nmixInput.value.trim(); | |
| if (!addrMixer) { | |
| alert('Укажите адрес миксера'); | |
| return; | |
| } | |
| if (!nmix) { | |
| alert('Укажите номер раствора (s)'); | |
| return; | |
| } | |
| let mixlink = `http://${addrMixer}/?`; | |
| mixlink += `s=${encodeURIComponent(nmix)}&`; | |
| const macroPairs = [ | |
| ['mCaNO3', 'g2gCaNO3'], | |
| ['mKNO3', 'g2gKNO3'], | |
| ['mNH4NO3', 'g2gNH4NO3'], | |
| ['mMgNO3', 'g2gMgNO3'], | |
| ['mCaCl2', 'g2gCaCl2'], | |
| ['mMgSO4', 'g2gMgSO4'], | |
| ['mKH2PO4', 'g2gKH2PO4'], | |
| ['mK2SO4', 'g2gK2SO4'] | |
| ]; | |
| macroPairs.forEach(([pumpId, valueId]) => { | |
| const pumpInput = document.getElementById(pumpId); | |
| const valueInput = document.getElementById(valueId); | |
| if (!pumpInput || !valueInput) { | |
| return; | |
| } | |
| const pump = pumpInput.value.trim(); | |
| if (!pump) { | |
| return; | |
| } | |
| const grams = valueInput.value.trim(); | |
| if (grams === '' || grams === '0' || grams === '0.00') { | |
| return; | |
| } | |
| mixlink += `${encodeURIComponent(pump)}=${encodeURIComponent(grams)}&`; | |
| }); | |
| const useComplex = document.getElementById('use_complex')?.checked; | |
| if (useComplex) { | |
| const pumpInput = document.getElementById('mCmplx'); | |
| const valueInput = document.getElementById('g2gCmplx'); | |
| if (pumpInput && valueInput) { | |
| const pump = pumpInput.value.trim(); | |
| const grams = valueInput.value.trim(); | |
| if (pump && grams !== '' && grams !== '0' && grams !== '0.00') { | |
| mixlink += `${encodeURIComponent(pump)}=${encodeURIComponent(grams)}&`; | |
| } | |
| } | |
| } else { | |
| const microPairs = [ | |
| ['mFe', 'g2gFe'], | |
| ['mMn', 'g2gMn'], | |
| ['mB', 'g2gB'], | |
| ['mZn', 'g2gZn'], | |
| ['mCu', 'g2gCu'], | |
| ['mMo', 'g2gMo'], | |
| ['mCo', 'g2gCo'], | |
| ['mSi', 'g2gSi'] | |
| ]; | |
| microPairs.forEach(([pumpId, valueId]) => { | |
| const pumpInput = document.getElementById(pumpId); | |
| const valueInput = document.getElementById(valueId); | |
| if (!pumpInput || !valueInput) { | |
| return; | |
| } | |
| const pump = pumpInput.value.trim(); | |
| if (!pump) { | |
| return; | |
| } | |
| const grams = valueInput.value.trim(); | |
| if (grams === '' || grams === '0' || grams === '0.00') { | |
| return; | |
| } | |
| mixlink += `${encodeURIComponent(pump)}=${encodeURIComponent(grams)}&`; | |
| }); | |
| } | |
| if (mixlink.endsWith('&')) { | |
| mixlink = mixlink.slice(0, -1); | |
| } | |
| window.open(mixlink, '_blank'); | |
| } | |
| // Автозапись в журнал при изготовлении (как в легаси Button9Click) | |
| function addToJournal() { | |
| // Получаем текущую дату в формате yyyy-mm-dd | |
| const currentDate = new Date().toISOString().split('T')[0]; | |
| // Получаем объем | |
| const volume = parseFloat(document.getElementById('volume')?.value) || 10; | |
| // Формируем описание (как в легаси: m1.Text) | |
| const desc = `Автозапись. Изготовлен раствор на ${volume} литров.`; | |
| // Берем строку профиля из поля profile-string (аналог profile.Caption в легаси) | |
| const profileStr = document.getElementById('profile-string')?.value || ''; | |
| // Создаем запись журнала (как в легаси: date=...;...;...) | |
| const journalEntry = { | |
| date: currentDate, | |
| desc: desc, | |
| memo: desc, // Совместимость | |
| profile: profileStr | |
| }; | |
| // Добавляем в журнал | |
| journalEntries.push(journalEntry); | |
| // Сортируем журнал по дате (как в легаси DStr.Sort) | |
| journalEntries.sort((a, b) => { | |
| const dateA = new Date(a.date); | |
| const dateB = new Date(b.date); | |
| return dateB - dateA; // Новые записи сверху | |
| }); | |
| // Обновляем список журнала | |
| updateJournalList(); | |
| // Переключаемся на вкладку "Файл" чтобы показать результат | |
| switchTab('file', document.querySelector('[onclick*="file"]')); | |
| alert('Автозапись добавлена в журнал'); | |
| } | |
| // Переключение между комплексом микроэлементов и отдельными элементами (как в legacy) | |
| function toggleMicroElements(useComplex) { | |
| const complexElements = ['mCmplx', 'cbCmplx', 'l2Cmplx', 'g2gCmplx']; | |
| const individualElements = ['mFe', 'cbFe', 'l2Fe', 'g2gFe', 'mMn', 'cbMn', 'l2Mn', 'g2gMn', | |
| 'mB', 'cbB', 'l2B', 'g2gB', 'mZn', 'cbZn', 'l2Zn', 'g2gZn', | |
| 'mCu', 'cbCu', 'l2Cu', 'g2gCu', 'mMo', 'cbMo', 'l2Mo', 'g2gMo', | |
| 'mCo', 'cbCo', 'l2Co', 'g2gCo', 'mSi', 'cbSi', 'l2Si', 'g2gSi']; | |
| if (useComplex) { | |
| // Показываем комплекс, скрываем отдельные элементы | |
| complexElements.forEach(id => { | |
| const element = document.getElementById(id); | |
| if (element) element.style.display = 'block'; | |
| }); | |
| individualElements.forEach(id => { | |
| const element = document.getElementById(id); | |
| if (element) element.style.display = 'none'; | |
| }); | |
| } else { | |
| // Показываем отдельные элементы, скрываем комплекс | |
| complexElements.forEach(id => { | |
| const element = document.getElementById(id); | |
| if (element) element.style.display = 'none'; | |
| }); | |
| individualElements.forEach(id => { | |
| const element = document.getElementById(id); | |
| if (element) element.style.display = ''; | |
| }); | |
| } | |
| } | |
| function saveData() { | |
| saveProfile(); | |
| } | |
| function resetSalts() { | |
| // Возврат к стандартным составам солей | |
| document.getElementById('salt_CaNO3_Ca').value = 16.972; | |
| document.getElementById('salt_CaNO3_NO3').value = 11.863; | |
| document.getElementById('salt_CaNO3_NH4').value = 0.000; | |
| document.getElementById('salt_KNO3_K').value = 38.672; | |
| document.getElementById('salt_KNO3_NO3').value = 13.854; | |
| document.getElementById('salt_NH4NO3_NH4').value = 17.499; | |
| document.getElementById('salt_NH4NO3_NO3').value = 17.499; | |
| document.getElementById('salt_MgSO4_Mg').value = 9.861; | |
| document.getElementById('salt_MgSO4_S').value = 13.010; | |
| document.getElementById('salt_KH2PO4_K').value = 28.731; | |
| document.getElementById('salt_KH2PO4_P').value = 22.761; | |
| document.getElementById('salt_K2SO4_K').value = 44.874; | |
| document.getElementById('salt_K2SO4_S').value = 18.401; | |
| document.getElementById('salt_MgNO3_Mg').value = 9.479; | |
| document.getElementById('salt_MgNO3_NO3').value = 10.925; | |
| document.getElementById('salt_CaCl2_Ca').value = 18.294; | |
| document.getElementById('salt_CaCl2_Cl').value = 32.366; | |
| onSaltCompositionChange(); | |
| } | |
| function resetProfile() { | |
| currentProfile = { | |
| N: 220, NO3: 200, NH4: 20, P: 40, K: 180, Ca: 200, Mg: 50, S: 73.049, Cl: 0, EC: 2.102, | |
| Fe: 2000, Mn: 550, B: 500, Zn: 330, Cu: 63, Mo: 63, Co: 0, Si: 0, | |
| volume: 10.0 | |
| }; | |
| applyProfile(); | |
| } | |
| function recalculate() { | |
| // Полный сброс к начальным значениям через имитацию загрузки дефолтного файла | |
| // Очистка localStorage | |
| localStorage.removeItem('hpg_profile'); | |
| localStorage.removeItem('hpg_journal'); | |
| // Очистка журнала | |
| journalEntries = []; | |
| updateJournalList(); | |
| // Создаем ПОЛНЫЙ дефолтный файл со ВСЕМИ параметрами | |
| const defaultFileContent = `version=Hydroponic Profile Generator HTML5 | |
| Comment=По умолчанию | |
| N=220 | |
| NO3=200 | |
| NH4=20 | |
| P=40 | |
| K=180 | |
| Ca=200 | |
| Mg=50 | |
| S=73.049 | |
| Cl=0 | |
| Fe=2000 | |
| Mn=550 | |
| B=500 | |
| Zn=330 | |
| Cu=63 | |
| Mo=63 | |
| Co=0 | |
| Si=0 | |
| CaNO3_Ca=16.972 | |
| CaNO3_NO3=11.863 | |
| CaNO3_NH4=0.000 | |
| KNO3_K=38.672 | |
| KNO3_NO3=13.854 | |
| NH4NO3_NH4=17.499 | |
| NH4NO3_NO3=17.499 | |
| MgSO4_Mg=9.861 | |
| MgSO4_S=13.010 | |
| KH2PO4_K=28.731 | |
| KH2PO4_P=22.761 | |
| K2SO4_K=44.874 | |
| K2SO4_S=18.401 | |
| MgNO3_Mg=9.479 | |
| MgNO3_NO3=10.926 | |
| CaCl2_Ca=18.294 | |
| CaCl2_Cl=32.366 | |
| dFe=20.1000 | |
| dMn=36.4000 | |
| dB=17.5000 | |
| dZn=22.7000 | |
| dCu=25.5000 | |
| dMo=54.3000 | |
| dCo=13.0000 | |
| dSi=7.0000 | |
| glCaNO3=600.0 | |
| gmlCaNO3=1.0000 | |
| glKNO3=250.0 | |
| gmlKNO3=1.0000 | |
| glNH4NO3=100.0 | |
| gmlNH4NO3=1.0000 | |
| glMgNO3=500.0 | |
| gmlMgNO3=1.0000 | |
| glMgSO4=600.0 | |
| gmlMgSO4=1.0000 | |
| glK2SO4=100.0 | |
| gmlK2SO4=1.0000 | |
| glKH2PO4=150.0 | |
| gmlKH2PO4=1.0000 | |
| glCaCl2=100.0 | |
| gmlCaCl2=1.0000 | |
| glCmplx=10.00 | |
| gmlCmplx=1.000 | |
| glFe=10.00 | |
| gmlFe=1.000 | |
| glMn=10.00 | |
| gmlMn=1.000 | |
| glB=10.00 | |
| gmlB=1.000 | |
| glZn=10.00 | |
| gmlZn=1.000 | |
| glCu=10.00 | |
| gmlCu=1.000 | |
| glMo=10.00 | |
| gmlMo=1.000 | |
| glCo=10.00 | |
| gmlCo=1.000 | |
| glSi=10.00 | |
| gmlSi=1.000 | |
| chkComplex=False | |
| chK2SO4=False | |
| chMgNO3=False | |
| V=10.0 | |
| mCaNO3=p1 | |
| mKNO3=p2 | |
| mNH4NO3=p3 | |
| mMgNO3= | |
| mMgSO4=p4 | |
| mKH2PO4=p5 | |
| mK2SO4=p6 | |
| mCaCl2= | |
| mCmplx= | |
| mFe= | |
| mMn= | |
| mB= | |
| mZn= | |
| mCu= | |
| mMo= | |
| mCo= | |
| mSi= | |
| addrMixer=mixer.local | |
| nmix=1 | |
| tAml=500 | |
| tBml=500 | |
| cgCaNO3=0.4000 | |
| cgKNO3=0.3500 | |
| cgNH4NO3=0.2500 | |
| cgMgNO3=0.3500 | |
| cgMgSO4=0.1200 | |
| cgK2SO4=0.3200 | |
| cgKH2PO4=0.4000 | |
| cgCaCl2=7.7000 | |
| cgCmplx=0.0500 | |
| cgFe=3.0000 | |
| cgMn=0.3000 | |
| cgB=0.4000 | |
| cgZn=0.1500 | |
| cgCu=0.4000 | |
| cgMo=3.0000 | |
| cgCo=2.3000 | |
| cgSi=0.2700`; | |
| // Парсим дефолтный файл - это установит ВСЕ параметры | |
| parseFullFile(defaultFileContent); | |
| // Сброс полей вкладки "Файл" | |
| document.getElementById('file_filename').value = 'default.hpg'; | |
| document.getElementById('file_comment').value = 'По умолчанию'; | |
| document.getElementById('file_journal_desc').value = ''; | |
| document.getElementById('file_journal_date').value = ''; | |
| document.getElementById('file_journal_memo').value = ''; | |
| document.getElementById('file_profile_info').value = ''; // Используем .value для input | |
| clearFileProfileDetails(); | |
| // Пересчитываем все | |
| calcAll(); | |
| calcConcentrates(); | |
| updatePriceResults(); | |
| } | |
| // Вычисление контрольной суммы файла (SHA-256, последние 3 символа) | |
| async function calculateChecksum() { | |
| try { | |
| const response = await fetch(window.location.href); | |
| const text = await response.text(); | |
| const msgBuffer = new TextEncoder().encode(text); | |
| const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); | |
| const hashArray = Array.from(new Uint8Array(hashBuffer)); | |
| const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); | |
| return hashHex.slice(-3); // Последние 3 символа | |
| } catch (error) { | |
| console.error('Ошибка вычисления контрольной суммы:', error); | |
| return '---'; | |
| } | |
| } | |
| // Определение типа устройства | |
| function detectDevice() { | |
| const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); | |
| const isTablet = /(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(navigator.userAgent); | |
| const screenWidth = window.innerWidth; | |
| let deviceType = 'Desktop'; | |
| if (isMobile && !isTablet) { | |
| deviceType = 'Mobile'; | |
| } else if (isTablet) { | |
| deviceType = 'Tablet'; | |
| } | |
| console.log('=== HPG Device Info ==='); | |
| console.log('Device Type:', deviceType); | |
| console.log('Screen Width:', screenWidth + 'px'); | |
| console.log('User Agent:', navigator.userAgent); | |
| console.log('Touch Support:', 'ontouchstart' in window); | |
| console.log('======================'); | |
| // Добавляем класс к body для дополнительной стилизации | |
| document.body.classList.add(deviceType.toLowerCase()); | |
| return { | |
| type: deviceType, | |
| isMobile: isMobile && !isTablet, | |
| isTablet: isTablet, | |
| isDesktop: !isMobile && !isTablet, | |
| screenWidth: screenWidth | |
| }; | |
| } | |
| // Инициализация при загрузке | |
| window.onload = async function() { | |
| // Определяем тип устройства | |
| const device = detectDevice(); | |
| // Инициализация корректора | |
| initCorrector(); | |
| const k2so4Checkbox = document.getElementById('use_K2SO4'); | |
| const mgno3Checkbox = document.getElementById('use_MgNO3'); | |
| useK2SO4 = k2so4Checkbox.checked; | |
| useMgNO3 = mgno3Checkbox.checked; | |
| syncSaltCompositions(); | |
| updateSaltLabels(); | |
| updateSaltNames(); // Обновляем названия солей при загрузке | |
| updateMicroNames(); // Обновляем названия микроэлементов при загрузке | |
| calcAll(); | |
| onMicroChange(); | |
| // Инициализация микро вкладки | |
| onComplexChange(); | |
| // Инициализация концентратов | |
| calcConcentrates(); | |
| addPriceEventListeners(); | |
| initPriceSpinners(); // Инициализация кастомных крутилок | |
| // Инициализация подвкладки Изготовление (по умолчанию отдельные микроэлементы) | |
| toggleMicroElements(false); | |
| // Восстановить активную вкладку из localStorage | |
| const savedTab = localStorage.getItem('activeTab'); | |
| if (savedTab && document.getElementById(savedTab)) { | |
| switchTab(savedTab); | |
| // Если активная вкладка "концентраты", восстановить внутреннюю вкладку | |
| if (savedTab === 'concentrates') { | |
| const savedInnerTab = localStorage.getItem('activeInnerTab'); | |
| if (savedInnerTab && document.getElementById(savedInnerTab)) { | |
| switchInnerTab(savedInnerTab); | |
| } | |
| } | |
| } | |
| // Вычисляем и добавляем контрольную сумму к версии | |
| const checksum = await calculateChecksum(); | |
| // Обновляем версию на вкладке Справка (добавляем после даты) | |
| const helpTab = document.getElementById('help'); | |
| if (helpTab) { | |
| const paragraphs = helpTab.querySelectorAll('p'); | |
| paragraphs.forEach(p => { | |
| if (p.innerHTML && p.innerHTML.includes('Версия:')) { | |
| // Добавляем контрольную сумму после даты: "10.10.2025)" -> "10.10.2025-XXXXXX)" | |
| p.innerHTML = p.innerHTML.replace('10.10.2025)', `10.10.2025-${checksum})`); | |
| } | |
| }); | |
| } | |
| // Обновляем версию внизу страницы | |
| const versionText = document.querySelector('.version-text'); | |
| if (versionText) { | |
| versionText.textContent = versionText.textContent.replace('0.221', `0.221-${checksum}`); | |
| } | |
| }; | |
| // ========== МОБИЛЬНАЯ ВЕРСИЯ - ФУНКЦИИ ========== | |
| function loadFileMobile() { | |
| const input = document.createElement('input'); | |
| input.type = 'file'; | |
| input.accept = '.hpg,.txt'; | |
| input.onchange = (e) => { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = (event) => { | |
| try { | |
| const content = event.target.result; | |
| parseMobileProfile(content); | |
| autoCalculateMobile(); // Автоматически пересчитываем | |
| alert(`✅ Профиль загружен: ${file.name}`); | |
| } catch (err) { | |
| console.error('Ошибка загрузки файла:', err); | |
| alert('❌ Ошибка загрузки файла'); | |
| } | |
| }; | |
| reader.readAsText(file); | |
| } | |
| }; | |
| input.click(); | |
| } | |
| function parseMobileProfile(content) { | |
| const lines = content.split('\n'); | |
| lines.forEach(line => { | |
| line = line.trim(); | |
| if (!line || !line.includes('=') || line.startsWith('#') || line.startsWith('date=')) return; | |
| const [key, value] = line.split('='); | |
| if (!value) return; | |
| const numValue = parseFloat(value.trim()); | |
| // Макроэлементы | |
| if (key === 'N' && !isNaN(numValue)) document.getElementById('m_N').value = numValue; | |
| else if (key === 'P' && !isNaN(numValue)) document.getElementById('m_P').value = numValue; | |
| else if (key === 'K' && !isNaN(numValue)) document.getElementById('m_K').value = numValue; | |
| else if (key === 'Ca' && !isNaN(numValue)) document.getElementById('m_Ca').value = numValue; | |
| else if (key === 'Mg' && !isNaN(numValue)) document.getElementById('m_Mg').value = numValue; | |
| else if (key === 'S' && !isNaN(numValue)) document.getElementById('m_S').value = numValue; | |
| // Микроэлементы | |
| else if (key === 'Fe' && !isNaN(numValue)) document.getElementById('m_Fe').value = numValue; | |
| else if (key === 'Mn' && !isNaN(numValue)) document.getElementById('m_Mn').value = numValue; | |
| else if (key === 'B' && !isNaN(numValue)) document.getElementById('m_B').value = numValue; | |
| else if (key === 'Zn' && !isNaN(numValue)) document.getElementById('m_Zn').value = numValue; | |
| else if (key === 'Cu' && !isNaN(numValue)) document.getElementById('m_Cu').value = numValue; | |
| else if (key === 'Mo' && !isNaN(numValue)) document.getElementById('m_Mo').value = numValue; | |
| // Объем | |
| else if (key === 'V' && !isNaN(numValue)) document.getElementById('m_volume').value = numValue; | |
| }); | |
| } | |
| // Переключение аккордеонов | |
| function toggleAccordion(id) { | |
| const content = document.getElementById('content-' + id); | |
| const icon = document.getElementById('icon-' + id); | |
| if (content.classList.contains('open')) { | |
| content.classList.remove('open'); | |
| icon.classList.remove('open'); | |
| } else { | |
| content.classList.add('open'); | |
| icon.classList.add('open'); | |
| } | |
| } | |
| // Автоматический расчет при изменении любого поля | |
| function autoCalculateMobile() { | |
| calculateMobile(); | |
| updateMobileRatios(); | |
| } | |
| // Обновление соотношений элементов | |
| function updateMobileRatios() { | |
| const N = parseFloat(document.getElementById('m_N').value) || 0; | |
| const P = parseFloat(document.getElementById('m_P').value) || 0; | |
| const K = parseFloat(document.getElementById('m_K').value) || 0; | |
| const Ca = parseFloat(document.getElementById('m_Ca').value) || 0; | |
| const Mg = parseFloat(document.getElementById('m_Mg').value) || 0; | |
| const S = parseFloat(document.getElementById('m_S').value) || 0; | |
| if (P === 0) return; // Избегаем деления на ноль | |
| const ratios = [ | |
| { label: 'N:P', value: (N/P).toFixed(2), optimal: N/P >= 5.0 && N/P <= 6.0 }, | |
| { label: 'N:K', value: (N/K).toFixed(2), optimal: N/K >= 1.1 && N/K <= 1.3 }, | |
| { label: 'K:P', value: (K/P).toFixed(2), optimal: K/P >= 4.0 && K/P <= 5.0 }, | |
| { label: 'K:Ca', value: (K/Ca).toFixed(2), optimal: K/Ca >= 0.8 && K/Ca <= 1.2 }, | |
| { label: 'K:Mg', value: (K/Mg).toFixed(2), optimal: K/Mg >= 3.0 && K/Mg <= 4.0 }, | |
| { label: 'Ca:P', value: (Ca/P).toFixed(2), optimal: Ca/P >= 4.5 && Ca/P <= 5.5 }, | |
| { label: 'Ca:Mg', value: (Ca/Mg).toFixed(2), optimal: Ca/Mg >= 3.5 && Ca/Mg <= 4.5 }, | |
| { label: 'Ca:K', value: (Ca/K).toFixed(2), optimal: Ca/K >= 0.8 && Ca/K <= 1.2 }, | |
| { label: 'Mg:P', value: (Mg/P).toFixed(2), optimal: Mg/P >= 1.0 && Mg/P <= 1.5 }, | |
| { label: 'Mg:S', value: (Mg/S).toFixed(2), optimal: Mg/S >= 0.6 && Mg/S <= 0.8 }, | |
| { label: 'S:P', value: (S/P).toFixed(2), optimal: S/P >= 1.5 && S/P <= 2.0 }, | |
| { label: 'S:K', value: (S/K).toFixed(2), optimal: S/K >= 0.3 && S/K <= 0.5 } | |
| ]; | |
| let html = '<div class="ratio-grid">'; | |
| ratios.forEach(r => { | |
| const className = r.optimal ? 'ratio-item optimal' : 'ratio-item'; | |
| html += `<div class="${className}"> | |
| <div class="ratio-label">${r.label}</div> | |
| <div class="ratio-value">${r.value}</div> | |
| </div>`; | |
| }); | |
| html += '</div>'; | |
| document.getElementById('mobile-ratios-content').innerHTML = html; | |
| } | |
| function calculateMobile() { | |
| // Получаем значения из полей | |
| const N = parseFloat(document.getElementById('m_N').value) || 0; | |
| const P = parseFloat(document.getElementById('m_P').value) || 0; | |
| const K = parseFloat(document.getElementById('m_K').value) || 0; | |
| const Ca = parseFloat(document.getElementById('m_Ca').value) || 0; | |
| const Mg = parseFloat(document.getElementById('m_Mg').value) || 0; | |
| const S = parseFloat(document.getElementById('m_S').value) || 0; | |
| const Fe = parseFloat(document.getElementById('m_Fe').value) || 0; | |
| const Mn = parseFloat(document.getElementById('m_Mn').value) || 0; | |
| const B = parseFloat(document.getElementById('m_B').value) || 0; | |
| const Zn = parseFloat(document.getElementById('m_Zn').value) || 0; | |
| const Cu = parseFloat(document.getElementById('m_Cu').value) || 0; | |
| const Mo = parseFloat(document.getElementById('m_Mo').value) || 0; | |
| const volume = parseFloat(document.getElementById('m_volume').value) || 10; | |
| // Используем те же функции расчета что и в десктопной версии | |
| // Устанавливаем значения в основные поля | |
| document.getElementById('N').value = N; | |
| document.getElementById('P').value = P; | |
| document.getElementById('K').value = K; | |
| document.getElementById('Ca').value = Ca; | |
| document.getElementById('Mg').value = Mg; | |
| document.getElementById('S').value = S; | |
| document.getElementById('Fe').value = Fe; | |
| document.getElementById('Mn').value = Mn; | |
| document.getElementById('B').value = B; | |
| document.getElementById('Zn').value = Zn; | |
| document.getElementById('Cu').value = Cu; | |
| document.getElementById('Mo').value = Mo; | |
| document.getElementById('volume').value = volume; | |
| // Вызываем расчет | |
| calcAll(); | |
| onMicroChange(); | |
| // Получаем результаты | |
| const results = []; | |
| // Макросоли (с подчеркиванием!) | |
| const gCaNO3 = parseFloat(document.getElementById('g_CaNO3')?.value) || 0; | |
| const gKNO3 = parseFloat(document.getElementById('g_KNO3')?.value) || 0; | |
| const gNH4NO3 = parseFloat(document.getElementById('g_NH4NO3')?.value) || 0; | |
| const gMgSO4 = parseFloat(document.getElementById('g_MgSO4')?.value) || 0; | |
| const gKH2PO4 = parseFloat(document.getElementById('g_KH2PO4')?.value) || 0; | |
| const gK2SO4 = parseFloat(document.getElementById('g_K2SO4')?.value) || 0; | |
| const gMgNO3 = parseFloat(document.getElementById('g_MgNO3')?.value) || 0; | |
| const gCaCl2 = parseFloat(document.getElementById('g_CaCl2')?.value) || 0; | |
| // Микроэлементы (с подчеркиванием!) | |
| const gFe = parseFloat(document.getElementById('g_Fe')?.value) || 0; | |
| const gMn = parseFloat(document.getElementById('g_Mn')?.value) || 0; | |
| const gB = parseFloat(document.getElementById('g_B')?.value) || 0; | |
| const gZn = parseFloat(document.getElementById('g_Zn')?.value) || 0; | |
| const gCu = parseFloat(document.getElementById('g_Cu')?.value) || 0; | |
| const gMo = parseFloat(document.getElementById('g_Mo')?.value) || 0; | |
| let html = '<div style="font-size: 14px;">'; | |
| html += '<h4 style="margin: 10px 0; color: #2e7d32;">Макросоли:</h4>'; | |
| if (gCaNO3 > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>Ca(NO3)2·4H2O:</strong> ${gCaNO3.toFixed(2)} г</div>`; | |
| if (gKNO3 > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>KNO3:</strong> ${gKNO3.toFixed(2)} г</div>`; | |
| if (gNH4NO3 > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>NH4NO3:</strong> ${gNH4NO3.toFixed(2)} г</div>`; | |
| if (gMgSO4 > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>MgSO4·7H2O:</strong> ${gMgSO4.toFixed(2)} г</div>`; | |
| if (gKH2PO4 > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>KH2PO4:</strong> ${gKH2PO4.toFixed(2)} г</div>`; | |
| if (gK2SO4 > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>K2SO4:</strong> ${gK2SO4.toFixed(2)} г</div>`; | |
| if (gMgNO3 > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>Mg(NO3)2·6H2O:</strong> ${gMgNO3.toFixed(2)} г</div>`; | |
| if (gCaCl2 > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>CaCl2·6H2O:</strong> ${gCaCl2.toFixed(2)} г</div>`; | |
| html += '<h4 style="margin: 15px 0 10px 0; color: #2e7d32;">Микроэлементы:</h4>'; | |
| if (gFe > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>Fe-DTPA (13%):</strong> ${gFe.toFixed(3)} г</div>`; | |
| if (gMn > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>MnSO4·5H2O:</strong> ${gMn.toFixed(3)} г</div>`; | |
| if (gB > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>H3BO3:</strong> ${gB.toFixed(3)} г</div>`; | |
| if (gZn > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>ZnSO4·7H2O:</strong> ${gZn.toFixed(3)} г</div>`; | |
| if (gCu > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>CuSO4·5H2O:</strong> ${gCu.toFixed(3)} г</div>`; | |
| if (gMo > 0) html += `<div style="padding: 5px 0; border-bottom: 1px solid #ddd;"><strong>(NH4)6Mo7O24·4H2O:</strong> ${gMo.toFixed(3)} г</div>`; | |
| const totalSalts = parseFloat(document.getElementById('total-salts')?.value) || 0; | |
| html += `<div style="margin-top: 15px; padding: 10px; background: #fff; border-radius: 5px; text-align: center;">`; | |
| html += `<strong style="font-size: 16px;">Всего солей: ${totalSalts.toFixed(2)} г</strong>`; | |
| html += `</div>`; | |
| html += '</div>'; | |
| document.getElementById('mobile-results-content').innerHTML = html; | |
| } | |
| </script> | |
| <!-- МОБИЛЬНАЯ ВЕРСИЯ (показывается только на экранах ≤768px) --> | |
| <div class="mobile-version"> | |
| <div class="mobile-header"> | |
| 🌱 HPG - Hydroponic Profile Generator | |
| </div> | |
| <!-- Информационный баннер --> | |
| <div style="background: #fff3cd; border-left: 4px solid #ffc107; padding: 12px; margin: 10px; border-radius: 5px; font-size: 13px; line-height: 1.5;"> | |
| <strong>📱 Упрощенная мобильная версия</strong><br> | |
| Базовый расчет навесок по профилю. Для полного функционала (настройка составов солей, концентраты, корректор, журнал) откройте на компьютере или планшете в горизонтальной ориентации (≥768px). | |
| </div> | |
| <!-- Кнопка загрузки файла --> | |
| <div style="background: white; padding: 15px; border-radius: 8px; margin: 10px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> | |
| <button onclick="loadFileMobile()" style="width: 100%; padding: 12px; background: #6c757d; color: white; border: none; border-radius: 5px; font-size: 15px; font-weight: bold;"> | |
| 📂 Загрузить профиль из файла | |
| </button> | |
| <p style="margin: 8px 0 0 0; font-size: 12px; color: #666;">Поддерживаются файлы .hpg и .txt</p> | |
| </div> | |
| <!-- Объем раствора --> | |
| <div style="background: white; padding: 15px; border-radius: 8px; margin: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"> | |
| <div class="mobile-input-group"> | |
| <label>Объем раствора (литры):</label> | |
| <input type="number" id="m_volume" value="10" step="0.1" oninput="autoCalculateMobile()"> | |
| </div> | |
| </div> | |
| <!-- Аккордеон: Макроэлементы --> | |
| <div class="mobile-accordion"> | |
| <div class="mobile-accordion-header" onclick="toggleAccordion('macro')"> | |
| <span>🌿 Макроэлементы (мг/л)</span> | |
| <span class="mobile-accordion-icon" id="icon-macro">▼</span> | |
| </div> | |
| <div class="mobile-accordion-content" id="content-macro"> | |
| <div class="mobile-input-group"> | |
| <label>N (Азот):</label> | |
| <input type="number" id="m_N" value="220" step="1" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>P (Фосфор):</label> | |
| <input type="number" id="m_P" value="40" step="1" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>K (Калий):</label> | |
| <input type="number" id="m_K" value="180" step="1" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>Ca (Кальций):</label> | |
| <input type="number" id="m_Ca" value="200" step="1" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>Mg (Магний):</label> | |
| <input type="number" id="m_Mg" value="50" step="1" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>S (Сера):</label> | |
| <input type="number" id="m_S" value="73" step="1" oninput="autoCalculateMobile()"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Аккордеон: Микроэлементы --> | |
| <div class="mobile-accordion"> | |
| <div class="mobile-accordion-header" onclick="toggleAccordion('micro')"> | |
| <span>🔬 Микроэлементы (мкг/л)</span> | |
| <span class="mobile-accordion-icon" id="icon-micro">▼</span> | |
| </div> | |
| <div class="mobile-accordion-content" id="content-micro"> | |
| <div class="mobile-input-group"> | |
| <label>Fe (Железо):</label> | |
| <input type="number" id="m_Fe" value="5385" step="10" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>Mn (Марганец):</label> | |
| <input type="number" id="m_Mn" value="2000" step="10" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>B (Бор):</label> | |
| <input type="number" id="m_B" value="500" step="10" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>Zn (Цинк):</label> | |
| <input type="number" id="m_Zn" value="462" step="10" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>Cu (Медь):</label> | |
| <input type="number" id="m_Cu" value="208" step="10" oninput="autoCalculateMobile()"> | |
| </div> | |
| <div class="mobile-input-group"> | |
| <label>Mo (Молибден):</label> | |
| <input type="number" id="m_Mo" value="50" step="10" oninput="autoCalculateMobile()"> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Аккордеон: Соотношения элементов --> | |
| <div class="mobile-accordion"> | |
| <div class="mobile-accordion-header" onclick="toggleAccordion('ratios')"> | |
| <span>📊 Соотношения элементов</span> | |
| <span class="mobile-accordion-icon" id="icon-ratios">▼</span> | |
| </div> | |
| <div class="mobile-accordion-content" id="content-ratios"> | |
| <div id="mobile-ratios-content"></div> | |
| </div> | |
| </div> | |
| <!-- Результат: Навески --> | |
| <div class="mobile-accordion"> | |
| <div class="mobile-accordion-header" onclick="toggleAccordion('results')"> | |
| <span>⚖️ Навески солей</span> | |
| <span class="mobile-accordion-icon open" id="icon-results">▼</span> | |
| </div> | |
| <div class="mobile-accordion-content open" id="content-results"> | |
| <div id="mobile-results-content"></div> | |
| </div> | |
| </div> | |
| <!-- Информация --> | |
| <div id="mobile-info" style="display: none;"> | |
| <div class="mobile-notice"> | |
| <h3>⚠️ Упрощенная версия</h3> | |
| <p>Это упрощенный калькулятор для быстрого расчета навесок.</p> | |
| <p>Для полного функционала откройте на десктопе.</p> | |
| <a href="https://wega-project.github.io/wega-hpg/hpg.html" class="mobile-link"> | |
| Открыть полную версию | |
| </a> | |
| </div> | |
| <div class="mobile-features"> | |
| <h4>✨ Полная версия включает:</h4> | |
| <ul> | |
| <li>Настройка составов солей</li> | |
| <li>Матрица соотношений элементов</li> | |
| <li>Расчет концентратов</li> | |
| <li>Корректор растворов</li> | |
| <li>Журнал действий</li> | |
| <li>Сохранение и загрузка профилей</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div style="text-align: center; margin-top: 20px; color: #666; font-size: 12px;"> | |
| <p>Версия: 0.221 (HTML5 порт)</p> | |
| <p>© WEGA Automation</p> | |
| </div> | |
| </div> | |
| </body> | |
| </html> |