1934 lines
74 KiB
Plaintext
1934 lines
74 KiB
Plaintext
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="theme-color" content="#0066cc">
|
||
<meta name="description" content="Roll Diameter Calculator - Internal Naue Tool">
|
||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Roll Diameter Calculator</title>
|
||
<style>
|
||
/* ---- NAUE BASE STYLES ---- */
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: 'Segoe UI', Arial, sans-serif;
|
||
background: #f4f6f9;
|
||
color: #333;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* ---- DISCLAIMER MODAL ---- */
|
||
#disclaimerOverlay {
|
||
position: fixed; inset: 0;
|
||
background: rgba(0,0,0,0.7);
|
||
z-index: 9999;
|
||
display: flex; align-items: center; justify-content: center;
|
||
padding: 20px;
|
||
}
|
||
#disclaimerBox {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
max-width: 680px;
|
||
width: 100%;
|
||
padding: 32px;
|
||
box-shadow: 0 8px 40px rgba(0,0,0,0.3);
|
||
}
|
||
.disclaimer-badge {
|
||
display: inline-block;
|
||
background: #ff6600;
|
||
color: #fff;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
padding: 3px 10px;
|
||
border-radius: 4px;
|
||
letter-spacing: 1px;
|
||
margin-bottom: 12px;
|
||
}
|
||
#disclaimerBox h2 {
|
||
font-size: 18px;
|
||
color: #003366;
|
||
margin-bottom: 20px;
|
||
}
|
||
.disclaimer-section {
|
||
background: #f8f9fa;
|
||
border-left: 4px solid #0066cc;
|
||
border-radius: 4px;
|
||
padding: 14px 16px;
|
||
margin-bottom: 14px;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
}
|
||
.disclaimer-section h4 {
|
||
font-size: 13px;
|
||
margin-bottom: 6px;
|
||
color: #333;
|
||
}
|
||
.disclaimer-confirm {
|
||
margin: 18px 0 20px;
|
||
font-size: 13px;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
}
|
||
.disclaimer-confirm input[type=checkbox] {
|
||
margin-top: 2px;
|
||
width: 16px; height: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
.disclaimer-buttons {
|
||
display: flex;
|
||
gap: 12px;
|
||
justify-content: flex-end;
|
||
}
|
||
#btnCancel {
|
||
padding: 10px 22px;
|
||
border: 2px solid #ccc;
|
||
background: #fff;
|
||
color: #666;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
}
|
||
#btnAccept {
|
||
padding: 10px 22px;
|
||
border: none;
|
||
background: #0066cc;
|
||
color: #fff;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
}
|
||
#btnAccept:disabled {
|
||
background: #aaa;
|
||
cursor: not-allowed;
|
||
}
|
||
#btnAccept:not(:disabled):hover { background: #0052a3; }
|
||
|
||
/* ---- APP HEADER ---- */
|
||
.app-header {
|
||
background: linear-gradient(135deg, #003366 0%, #0066cc 100%);
|
||
color: #fff;
|
||
padding: 18px 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||
}
|
||
.app-header .logo-text {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
letter-spacing: 1px;
|
||
}
|
||
.app-header .subtitle {
|
||
font-size: 13px;
|
||
opacity: 0.85;
|
||
}
|
||
.beta-pill {
|
||
background: #ff6600;
|
||
color: #fff;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
padding: 2px 8px;
|
||
border-radius: 20px;
|
||
letter-spacing: 1px;
|
||
margin-left: 8px;
|
||
}
|
||
.internal-pill {
|
||
background: rgba(255,255,255,0.2);
|
||
color: #fff;
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
padding: 2px 8px;
|
||
border-radius: 20px;
|
||
letter-spacing: 0.5px;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
/* ---- MAIN LAYOUT ---- */
|
||
.app-main {
|
||
max-width: 920px;
|
||
margin: 30px auto;
|
||
padding: 0 16px 60px;
|
||
}
|
||
|
||
/* ---- CARD ---- */
|
||
.card {
|
||
background: #fff;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 12px rgba(0,0,0,0.07);
|
||
overflow: hidden;
|
||
margin-bottom: 24px;
|
||
}
|
||
.card-header {
|
||
background: #f0f4fa;
|
||
padding: 14px 22px;
|
||
border-bottom: 1px solid #e0e8f0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
.card-header h2 {
|
||
font-size: 15px;
|
||
color: #003366;
|
||
font-weight: 700;
|
||
}
|
||
.card-body {
|
||
padding: 22px;
|
||
}
|
||
|
||
/* ---- TABS ---- */
|
||
.tab-bar {
|
||
display: flex;
|
||
border-bottom: 2px solid #e0e8f0;
|
||
margin-bottom: 24px;
|
||
}
|
||
.tab-btn {
|
||
padding: 10px 20px;
|
||
border: none;
|
||
background: none;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
color: #666;
|
||
border-bottom: 3px solid transparent;
|
||
margin-bottom: -2px;
|
||
transition: all 0.2s;
|
||
font-weight: 500;
|
||
}
|
||
.tab-btn.active {
|
||
color: #0066cc;
|
||
border-bottom-color: #0066cc;
|
||
font-weight: 700;
|
||
}
|
||
.tab-btn:hover:not(.active) { color: #333; }
|
||
.tab-pane { display: none; }
|
||
.tab-pane.active { display: block; }
|
||
|
||
/* ---- FORM ELEMENTS ---- */
|
||
.form-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 16px;
|
||
margin-bottom: 16px;
|
||
}
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.form-group label {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #555;
|
||
margin-bottom: 5px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.4px;
|
||
}
|
||
.form-group input,
|
||
.form-group select {
|
||
padding: 9px 11px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 5px;
|
||
font-size: 14px;
|
||
transition: border 0.2s;
|
||
}
|
||
.form-group input:focus,
|
||
.form-group select:focus {
|
||
outline: none;
|
||
border-color: #0066cc;
|
||
box-shadow: 0 0 0 3px rgba(0,102,204,0.1);
|
||
}
|
||
.input-with-unit {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.input-with-unit input { flex: 1; }
|
||
.unit-label {
|
||
font-size: 12px;
|
||
color: #888;
|
||
white-space: nowrap;
|
||
}
|
||
.tol-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.tol-row input { width: 80px; }
|
||
|
||
/* ---- MODE SELECTOR ---- */
|
||
.mode-selector {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.mode-btn {
|
||
padding: 7px 16px;
|
||
border: 2px solid #0066cc;
|
||
background: #fff;
|
||
color: #0066cc;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
transition: all 0.2s;
|
||
}
|
||
.mode-btn.active {
|
||
background: #0066cc;
|
||
color: #fff;
|
||
}
|
||
.mode-btn:hover:not(.active) {
|
||
background: #e8f0fb;
|
||
}
|
||
|
||
/* ---- FORMULA BOX ---- */
|
||
.formula-box {
|
||
background: #f8f9fa;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 6px;
|
||
padding: 10px 14px;
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 18px;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
/* ---- RESULT DISPLAY ---- */
|
||
.result-box {
|
||
background: linear-gradient(135deg, #e8f4fd 0%, #d0e8fa 100%);
|
||
border: 2px solid #0066cc;
|
||
border-radius: 8px;
|
||
padding: 16px 20px;
|
||
margin-bottom: 18px;
|
||
}
|
||
.result-box .result-label {
|
||
font-size: 12px;
|
||
color: #555;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.4px;
|
||
}
|
||
.result-box .result-value {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
color: #003366;
|
||
line-height: 1.2;
|
||
}
|
||
.result-box .result-range {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* ---- DIAGRAM ---- */
|
||
.diagram-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 24px;
|
||
margin: 18px 0;
|
||
}
|
||
.diagram-svg-wrap {
|
||
flex-shrink: 0;
|
||
}
|
||
.diagram-legend {
|
||
font-size: 12px;
|
||
color: #555;
|
||
line-height: 1.8;
|
||
}
|
||
|
||
/* ---- BUTTONS ---- */
|
||
.btn-calc {
|
||
background: #0066cc;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 11px 28px;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
transition: background 0.2s, box-shadow 0.2s;
|
||
}
|
||
.btn-calc:hover { background: #0052a3; box-shadow: 0 2px 8px rgba(0,102,204,0.3); }
|
||
.btn-reset {
|
||
background: #e0e0e0;
|
||
color: #444;
|
||
border: none;
|
||
border-radius: 6px;
|
||
padding: 11px 20px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: background 0.2s;
|
||
margin-left: 10px;
|
||
}
|
||
.btn-reset:hover { background: #d0d0d0; }
|
||
|
||
/* ---- INFO NOTE ---- */
|
||
.info-note {
|
||
background: #fffbea;
|
||
border-left: 4px solid #f0b429;
|
||
padding: 12px 16px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
color: #666;
|
||
line-height: 1.6;
|
||
margin-top: 18px;
|
||
}
|
||
|
||
/* ---- SECTION SEPARATOR ---- */
|
||
.section-sep {
|
||
border: none;
|
||
border-top: 1px solid #e8ecf0;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
/* ---- WEIGHT SECTION ---- */
|
||
.weight-section {
|
||
background: #f8fafa;
|
||
border: 1px dashed #aac;
|
||
border-radius: 6px;
|
||
padding: 14px 18px;
|
||
margin-top: 18px;
|
||
}
|
||
.weight-section h4 {
|
||
font-size: 13px;
|
||
color: #555;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
/* ---- APP FOOTER ---- */
|
||
.app-footer {
|
||
text-align: center;
|
||
font-size: 11px;
|
||
color: #aaa;
|
||
padding: 18px;
|
||
border-top: 1px solid #e8ecf0;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
/* ============================================
|
||
LOAD OPTIMIZER STYLES
|
||
============================================ */
|
||
.load-optimizer-section {
|
||
margin-top: 30px;
|
||
border: 2px solid #0066cc;
|
||
border-radius: 8px;
|
||
background-color: #f0f7ff;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(0,102,204,0.1);
|
||
}
|
||
.lo-accordion-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px 20px;
|
||
background: linear-gradient(135deg, #0066cc 0%, #004499 100%);
|
||
color: white;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
transition: background 0.3s ease;
|
||
}
|
||
.lo-accordion-header:hover {
|
||
background: linear-gradient(135deg, #0052a3 0%, #003d7a 100%);
|
||
}
|
||
.lo-accordion-header h3 {
|
||
margin: 0;
|
||
flex: 1;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
}
|
||
.lo-accordion-icon {
|
||
font-size: 14px;
|
||
transition: transform 0.3s ease;
|
||
display: inline-block;
|
||
}
|
||
.lo-accordion-header.active .lo-accordion-icon {
|
||
transform: rotate(180deg);
|
||
}
|
||
.badge-beta-lo {
|
||
display: inline-block;
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-size: 11px;
|
||
font-weight: bold;
|
||
background-color: rgba(255,255,255,0.3);
|
||
color: white;
|
||
}
|
||
.lo-accordion-content {
|
||
padding: 20px;
|
||
}
|
||
@keyframes loSlideDown {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
.lo-accordion-content { animation: loSlideDown 0.3s ease; }
|
||
.lo-inputs {
|
||
display: grid;
|
||
gap: 20px;
|
||
margin-bottom: 30px;
|
||
}
|
||
.lo-presets-section, .lo-custom-section {
|
||
background: white;
|
||
padding: 16px;
|
||
border-radius: 6px;
|
||
border: 1px solid #ddd;
|
||
}
|
||
.lo-presets-section h4, .lo-custom-section h4 {
|
||
margin-top: 0;
|
||
color: #333;
|
||
font-size: 14px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.lo-preset-buttons {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
.lo-preset-btn {
|
||
padding: 8px 16px;
|
||
border: 2px solid #0066cc;
|
||
background: white;
|
||
color: #0066cc;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
transition: all 0.2s;
|
||
}
|
||
.lo-preset-btn:hover { background: #0066cc; color: white; }
|
||
.lo-preset-btn.active {
|
||
background: #0066cc;
|
||
color: white;
|
||
box-shadow: 0 0 0 3px rgba(0,102,204,0.2);
|
||
}
|
||
.lo-input-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
.lo-input-field { display: flex; flex-direction: column; }
|
||
.lo-input-field label {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #666;
|
||
margin-bottom: 4px;
|
||
}
|
||
.lo-input-field input {
|
||
padding: 8px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
}
|
||
.lo-input-field input:focus {
|
||
outline: none;
|
||
border-color: #0066cc;
|
||
box-shadow: 0 0 0 2px rgba(0,102,204,0.1);
|
||
}
|
||
.lo-button-group { display: flex; gap: 12px; }
|
||
.lo-btn-primary, .lo-btn-secondary {
|
||
flex: 1;
|
||
padding: 12px 16px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.lo-btn-primary { background: #0066cc; color: white; }
|
||
.lo-btn-primary:hover { background: #0052a3; box-shadow: 0 2px 6px rgba(0,102,204,0.3); }
|
||
.lo-btn-secondary { background: #e0e0e0; color: #333; }
|
||
.lo-btn-secondary:hover { background: #d0d0d0; }
|
||
.lo-results { display: grid; gap: 20px; }
|
||
.lo-results-summary {
|
||
background: white;
|
||
padding: 16px;
|
||
border-radius: 6px;
|
||
border-left: 4px solid #28a745;
|
||
}
|
||
.lo-results-summary h4 { margin-top: 0; color: #333; font-size: 14px; margin-bottom: 12px; }
|
||
.lo-summary-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 12px;
|
||
font-size: 13px;
|
||
}
|
||
.lo-summary-item:last-child { margin-bottom: 0; }
|
||
.lo-value-highlight { color: #0066cc; font-weight: bold; font-size: 16px; }
|
||
.lo-value-number { color: #28a745; font-weight: bold; }
|
||
.lo-util-group { display: flex; flex-direction: column; gap: 4px; text-align: right; }
|
||
.lo-results-table {
|
||
background: white;
|
||
padding: 16px;
|
||
border-radius: 6px;
|
||
overflow-x: auto;
|
||
}
|
||
.lo-results-table h4 { margin-top: 0; margin-bottom: 12px; }
|
||
.lo-results-table table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
||
.lo-results-table thead { background: #f5f5f5; }
|
||
.lo-results-table th {
|
||
padding: 8px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: #333;
|
||
border-bottom: 2px solid #0066cc;
|
||
}
|
||
.lo-results-table td { padding: 8px; border-bottom: 1px solid #e0e0e0; }
|
||
.lo-results-table tbody tr:hover { background: #f9f9f9; }
|
||
.lo-results-table tbody tr.lo-recommended { background: #fffacd; font-weight: bold; }
|
||
.lo-warning {
|
||
background: #fff8e1;
|
||
border: 1px solid #f0b429;
|
||
border-radius: 5px;
|
||
padding: 10px 14px;
|
||
font-size: 12px;
|
||
color: #7a5c00;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
/* ---- SETTINGS SECTION ---- */
|
||
.lo-settings-section {
|
||
background: white;
|
||
padding: 16px;
|
||
border-radius: 6px;
|
||
border: 1px solid #ddd;
|
||
position: relative;
|
||
}
|
||
.lo-settings-section h4 {
|
||
margin-top: 0;
|
||
color: #333;
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
.lo-settings-section .lo-settings-subtitle {
|
||
font-size: 11px;
|
||
color: #888;
|
||
margin-bottom: 12px;
|
||
}
|
||
.lo-settings-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
.lo-settings-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.lo-settings-field label {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #555;
|
||
margin-bottom: 4px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
.lo-settings-field label .lo-settings-hint {
|
||
font-weight: 400;
|
||
color: #999;
|
||
font-size: 11px;
|
||
}
|
||
.lo-settings-input-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.lo-settings-input-wrap input {
|
||
padding: 8px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
width: 90px;
|
||
}
|
||
.lo-settings-input-wrap input:focus {
|
||
outline: none;
|
||
border-color: #0066cc;
|
||
box-shadow: 0 0 0 2px rgba(0,102,204,0.1);
|
||
}
|
||
.lo-settings-input-wrap .lo-unit {
|
||
font-size: 12px;
|
||
color: #888;
|
||
}
|
||
.lo-settings-effective {
|
||
font-size: 11px;
|
||
color: #0066cc;
|
||
margin-top: 5px;
|
||
min-height: 16px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.lo-accordion-header h3 { font-size: 14px; }
|
||
.lo-preset-buttons { flex-direction: column; }
|
||
.lo-preset-btn { width: 100%; }
|
||
.lo-input-grid { grid-template-columns: 1fr; }
|
||
.lo-button-group { flex-direction: column; }
|
||
.form-row { grid-template-columns: 1fr; }
|
||
.lo-settings-grid { grid-template-columns: 1fr; }
|
||
}
|
||
/* ===== SIDE VIEW ===== */
|
||
.lo-sideview-section {
|
||
background: white; padding: 16px; border-radius: 6px; border: 1px solid #ddd;
|
||
}
|
||
.lo-sideview-section h4 { margin: 0 0 12px 0; font-size: 14px; color: #333; }
|
||
.lo-sideview-wrap {
|
||
background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 4px; overflow: hidden;
|
||
}
|
||
.lo-sideview-legend { display:flex; gap:20px; margin-top:10px; font-size:12px; color:#555; flex-wrap:wrap; }
|
||
.lo-legend-item { display:flex; align-items:center; gap:6px; }
|
||
.lo-legend-box { width:14px; height:14px; border-radius:2px; display:inline-block; border:1px solid #aaa; }
|
||
.roll-color { background:#a8d4f5; border-color:#0066cc; }
|
||
.container-color { background:#e8e8e8; border-color:#999; }
|
||
.margin-color { background: repeating-linear-gradient(45deg,#fff7e0,#fff7e0 3px,#ffe8a0 3px,#ffe8a0 6px); border-color:#f0b429; }
|
||
.core-presets { display:flex; gap:5px; margin-top:6px; flex-wrap:wrap; }
|
||
.core-preset-btn { padding:3px 9px; font-size:11px; font-weight:600; border:1px solid #0066cc; background:#fff; color:#0066cc; border-radius:4px; cursor:pointer; transition:all 0.15s; }
|
||
.core-preset-btn:hover, .core-preset-btn.active { background:#0066cc; color:#fff; }
|
||
|
||
/* ---- FORKLIFT CHECK BOX ---- */
|
||
.forklift-check{border-radius:7px;padding:14px 16px;margin-top:14px;font-size:13px;line-height:1.6;display:none;}
|
||
.forklift-check.fc-ok{background:#eafaf1;border:1.5px solid #28a745;}
|
||
.forklift-check.fc-warn{background:#fff8e1;border:1.5px solid #f0b429;}
|
||
.forklift-check.fc-block{background:#fff0f0;border:2px solid #dc3545;}
|
||
.forklift-check .fc-title{font-weight:700;font-size:13px;margin-bottom:8px;display:flex;align-items:center;gap:7px;}
|
||
.forklift-check ul{margin:6px 0 0 18px;padding:0;}
|
||
.forklift-check ul li{margin-bottom:3px;}
|
||
.fc-req-ok{color:#28a745;}
|
||
.fc-req-nok{color:#dc3545;font-weight:600;}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ============================================================
|
||
DISCLAIMER MODAL
|
||
============================================================ -->
|
||
<div id="disclaimerOverlay">
|
||
<div id="disclaimerBox">
|
||
<div class="disclaimer-badge">⚠ BETA VERSION · INTERNAL USE ONLY</div>
|
||
<h2>Roll Diameter Calculator – Naue GmbH & Co. KG</h2>
|
||
|
||
<div class="disclaimer-section">
|
||
<h4>🔒 Authorized Departments Only / Nur autorisierte Abteilungen</h4>
|
||
This tool is exclusively intended for internal use by:<br>
|
||
<strong>Production Planning · Logistics · Product Management</strong><br><br>
|
||
Dieses Tool ist ausschließlich für den internen Gebrauch in:<br>
|
||
<strong>Produktionsplanung · Logistik · Produktmanagement</strong>
|
||
</div>
|
||
|
||
<div class="disclaimer-section">
|
||
<h4>🧪 Beta-Status</h4>
|
||
This application is in Beta. Results should be verified before operational use.<br>
|
||
Diese Anwendung befindet sich im Beta-Stadium. Ergebnisse sind vor dem produktiven Einsatz zu überprüfen.
|
||
</div>
|
||
|
||
<div class="disclaimer-section">
|
||
<h4>📊 Product Thickness Values</h4>
|
||
Pre-filled product thickness values are derived from an automated data evaluation and serve as a starting point only.
|
||
They must be independently verified before use in any calculation.<br>
|
||
Die vorgeschlagenen Produktdicken stammen aus einer automatischen Datenauswertung und müssen vor Verwendung in Berechnungen eigenständig überprüft werden.
|
||
</div>
|
||
|
||
<div class="disclaimer-confirm">
|
||
<input type="checkbox" id="confirmCheck">
|
||
<label for="confirmCheck">
|
||
Ich bestätige, dass ich zur Nutzung dieses Tools berechtigt bin und die obigen Hinweise gelesen und verstanden habe.<br>
|
||
<em>I confirm that I am authorised to use this tool and have read and understood the notices above.</em>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="disclaimer-buttons">
|
||
<button id="btnCancel">✕ Abbrechen / Cancel</button>
|
||
<button id="btnAccept" disabled>✓ Akzeptieren & Weiter / Accept & Continue</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================
|
||
APP HEADER
|
||
============================================================ -->
|
||
<div class="app-header">
|
||
<div>
|
||
<div class="logo-text">
|
||
Naue
|
||
<span class="beta-pill">BETA</span>
|
||
<span class="internal-pill">Internal</span>
|
||
</div>
|
||
<div class="subtitle">Roll Diameter Calculator · Roll Diameter · Product Length · Roll Weight</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============================================================
|
||
MAIN
|
||
============================================================ -->
|
||
<div class="app-main">
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h2>✎ Roll Diameter Calculator</h2>
|
||
</div>
|
||
<div class="card-body">
|
||
|
||
<!-- TABS -->
|
||
<div class="tab-bar">
|
||
<button class="tab-btn active" data-tab="direct">✎ Direct Calculation</button>
|
||
<button class="tab-btn" data-tab="extrap">🔍 Extrapolation</button>
|
||
</div>
|
||
|
||
<!-- ===================== TAB: DIRECT CALCULATION ===================== -->
|
||
<div class="tab-pane active" id="tab-direct">
|
||
|
||
<!-- Product Category + Site -->
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Product Category</label>
|
||
<select id="d-cat">
|
||
<option value="">– Select –</option>
|
||
<option value="bentofix">Bentofix</option>
|
||
<option value="secutex">Secutex</option>
|
||
<option value="carbofol">Carbofol</option>
|
||
<option value="secumat">Secumat</option>
|
||
<option value="secudrain">Secudrain</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Production Site</label>
|
||
<select id="d-site">
|
||
<option value="">– Select –</option>
|
||
<option value="espelkamp">Espelkamp</option>
|
||
<option value="malaysia">Malaysia</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Calculation Mode -->
|
||
<div class="form-group" style="margin-bottom:14px;">
|
||
<label>Calculation Mode</label>
|
||
<div class="mode-selector">
|
||
<button class="mode-btn active" data-mode="diameter">→ Roll Diameter</button>
|
||
<button class="mode-btn" data-mode="length">→ Product Length</button>
|
||
<button class="mode-btn" data-mode="target">🎯 Target Diameter</button>
|
||
<button class="mode-btn" data-mode="thickness">→ Product Thickness</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Diagram -->
|
||
<div class="diagram-row">
|
||
<div class="diagram-svg-wrap">
|
||
<svg width="120" height="120" viewBox="0 0 120 120">
|
||
<circle cx="60" cy="60" r="50" fill="#d0e8fa" stroke="#0066cc" stroke-width="2"/>
|
||
<circle cx="60" cy="60" r="20" fill="#fff" stroke="#666" stroke-width="2" stroke-dasharray="4,3"/>
|
||
<line x1="60" y1="60" x2="110" y2="60" stroke="#0066cc" stroke-width="1.5"/>
|
||
<line x1="60" y1="60" x2="80" y2="60" stroke="#666" stroke-width="1.5"/>
|
||
<text x="82" y="56" font-size="9" fill="#0066cc">D</text>
|
||
<text x="68" y="56" font-size="9" fill="#666">d</text>
|
||
</svg>
|
||
</div>
|
||
<div class="diagram-legend">
|
||
<div><strong>D</strong> = Roll outer diameter</div>
|
||
<div><strong>d</strong> = Core outer diameter</div>
|
||
<div><strong>t</strong> = Product thickness</div>
|
||
<div><strong>L</strong> = Product length</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="section-sep">
|
||
|
||
<!-- Input Parameters -->
|
||
<h4 style="font-size:13px;color:#555;margin-bottom:14px;">Input Parameters</h4>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Core Outer Diameter (d)</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="d-co" value="150" min="0" step="1">
|
||
<span class="unit-label">mm</span>
|
||
</div>
|
||
<div class="core-presets">
|
||
<button class="core-preset-btn" data-core="133">133mm Stahl</button>
|
||
<button class="core-preset-btn" data-core="150">150mm PVC</button>
|
||
<button class="core-preset-btn" data-core="168">168mm PVC</button>
|
||
<button class="core-preset-btn" data-core="194">194mm Stahl</button>
|
||
<button class="core-preset-btn" data-core="300">300</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Article Number (optional)</label>
|
||
<input type="text" id="d-art" list="d-art-list" placeholder="Search article no. or name..." autocomplete="off" style="width:100%;">
|
||
<datalist id="d-art-list"></datalist>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Product Thickness (t)</label>
|
||
<div class="tol-row">
|
||
<input type="number" id="d-th" placeholder="mm" min="0" step="0.01">
|
||
<span class="unit-label">±</span>
|
||
<input type="number" id="d-tol" placeholder="mm" min="0" step="0.01" style="width:80px;">
|
||
<span class="unit-label">mm</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Product Length (L)</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="d-le" placeholder="m" min="0" step="0.1">
|
||
<span class="unit-label">m</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Roll Diameter (D)</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="d-di" placeholder="mm" min="0" step="1">
|
||
<span class="unit-label">mm</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Weight (optional) -->
|
||
<div class="weight-section">
|
||
<h4>Roll Weight (optional)</h4>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Roll Width</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="de-wi" placeholder="m" min="0" step="0.01">
|
||
<span class="unit-label">m</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Area Weight</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="d-aw" placeholder="g/m²" min="0" step="1">
|
||
<span class="unit-label">g/m²</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Calculated Weight</label>
|
||
<div class="input-with-unit">
|
||
<input type="text" id="d-wo" readonly placeholder="–" style="background:#f0f0f0;">
|
||
<span class="unit-label">kg</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- Forklift / Weight Check -->
|
||
<div class="forklift-check" id="forkliftCheck">
|
||
<div class="fc-title" id="forkliftCheckTitle"></div>
|
||
<div id="forkliftCheckBody"></div>
|
||
</div>
|
||
<div style="margin-top:18px;">
|
||
<button class="btn-calc" id="d-calc">▶ Calculate</button>
|
||
<button class="btn-reset" id="d-reset">Reset</button>
|
||
</div>
|
||
|
||
<!-- Result -->
|
||
<div id="d-result" style="display:none; margin-top:20px;">
|
||
<div class="result-box">
|
||
<div class="result-label" id="d-res-label">Result</div>
|
||
<div class="result-value" id="d-res-val">–</div>
|
||
<div class="result-range" id="d-res-range"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="formula-box">
|
||
Geometry: π/4 × (D² − d²) = L × t | Weight: G [kg] = Area Weight [g/m²] × L [m] × Width [m] / 1000
|
||
</div>
|
||
|
||
</div>
|
||
<!-- END TAB DIRECT -->
|
||
|
||
<!-- ===================== TAB: EXTRAPOLATION ===================== -->
|
||
<div class="tab-pane" id="tab-extrap">
|
||
|
||
<h4 style="font-size:13px;color:#555;margin-bottom:14px;">Known Reference Roll</h4>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Article Number (optional)</label>
|
||
<input type="text" id="e-art" list="e-art-list" placeholder="Search article no. or name..." autocomplete="off" style="width:100%;">
|
||
<datalist id="e-art-list"></datalist>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Core Outer Diameter (d)</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="e-co" value="150" min="0" step="1">
|
||
<span class="unit-label">mm</span>
|
||
</div>
|
||
<div class="core-presets">
|
||
<button class="core-preset-btn" data-core="133">133mm Stahl</button>
|
||
<button class="core-preset-btn" data-core="150">150mm PVC</button>
|
||
<button class="core-preset-btn" data-core="168">168mm PVC</button>
|
||
<button class="core-preset-btn" data-core="194">194mm Stahl</button>
|
||
<button class="core-preset-btn" data-core="300">300</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Known Roll Diameter (D₀)</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="e-d0" placeholder="mm" min="0" step="1">
|
||
<span class="unit-label">mm</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Known Roll Length (L₀)</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="e-l0" placeholder="m" min="0" step="0.1">
|
||
<span class="unit-label">m</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="section-sep">
|
||
<h4 style="font-size:13px;color:#555;margin-bottom:14px;">New Roll Length</h4>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>New Roll Length (L₁)</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="e-l1" placeholder="m" min="0" step="0.1">
|
||
<span class="unit-label">m</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Tolerance (optional)</label>
|
||
<div class="tol-row">
|
||
<span class="unit-label">±</span>
|
||
<input type="number" id="e-tol" placeholder="mm" min="0" step="0.1" style="width:100px;">
|
||
<span class="unit-label">mm</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Weight (optional) -->
|
||
<div class="weight-section">
|
||
<h4>Roll Weight (optional)</h4>
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Roll Width</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="e-wi" placeholder="m" min="0" step="0.01">
|
||
<span class="unit-label">m</span>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Area Weight</label>
|
||
<div class="input-with-unit">
|
||
<input type="number" id="e-aw" placeholder="g/m²" min="0" step="1">
|
||
<span class="unit-label">g/m²</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top:18px;">
|
||
<button class="btn-calc" id="e-calc">▶ Calculate Roll Diameter</button>
|
||
<button class="btn-reset" id="e-reset">Reset</button>
|
||
</div>
|
||
|
||
<!-- Result -->
|
||
<div id="e-result" style="display:none; margin-top:20px;">
|
||
<div class="result-box">
|
||
<div class="result-label">Extrapolated Roll Diameter (D₁)</div>
|
||
<div class="result-value" id="e-res-val">–</div>
|
||
<div class="result-range" id="e-res-range"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="formula-box">
|
||
Geometry: D₁ = √(d² + L₁/L₀ × (D₀² − d²)) | Weight: G [kg] = Area Weight × L₁ × Width / 1000
|
||
</div>
|
||
|
||
</div>
|
||
<!-- END TAB EXTRAP -->
|
||
|
||
</div><!-- card-body -->
|
||
</div><!-- card -->
|
||
|
||
<!-- INFO NOTE -->
|
||
<div class="info-note">
|
||
<strong>ⓘ Calculation Notes</strong><br>
|
||
The calculated roll diameters assume a perfectly cylindrical roll with uniform, gap-free winding.<br><br>
|
||
The product thickness can vary during production within the permitted manufacturing tolerance.
|
||
The actual roll diameter may therefore differ from the calculated values.<br><br>
|
||
Use the <em>± Tolerance</em> field next to the product thickness to estimate this effect – the result will then show the minimum, nominal and maximum roll diameter.
|
||
</div>
|
||
|
||
<!-- ============================================================
|
||
LOAD OPTIMIZER SECTION
|
||
============================================================ -->
|
||
<div id="loadOptimizerContainer" class="load-optimizer-section">
|
||
|
||
<div class="lo-accordion-header" id="loAccordionToggle">
|
||
<span class="lo-accordion-icon">▼</span>
|
||
<h3>🎯 Load Optimizer – Transport Capacity Calculator</h3>
|
||
<span class="badge-beta-lo">BETA</span>
|
||
</div>
|
||
|
||
<div class="lo-accordion-content" id="loAccordionContent" style="display:none;">
|
||
|
||
<div class="lo-warning">
|
||
⚠ The Load Optimizer uses roll data from the <strong>Direct Calculation</strong> tab above.
|
||
Please complete a roll calculation first (Roll Diameter + Roll Width + Roll Weight must be filled).
|
||
</div>
|
||
|
||
<div class="lo-inputs">
|
||
|
||
<!-- ===== SETTINGS SECTION ===== -->
|
||
<div class="lo-settings-section">
|
||
<h4>⚙️ Loading Clearances</h4>
|
||
<div class="lo-settings-subtitle">Safety margins applied to the effective loading space before calculation.</div>
|
||
<div class="lo-settings-grid">
|
||
<div class="lo-settings-field">
|
||
<label>
|
||
Side Wall Clearance
|
||
<span class="lo-settings-hint">(one side)</span>
|
||
</label>
|
||
<div class="lo-settings-input-wrap">
|
||
<input type="number" id="loMarginSide" value="0" min="0" max="100" step="1">
|
||
<span class="lo-unit">cm</span>
|
||
</div>
|
||
<div class="lo-settings-effective" id="loMarginSideEffective"></div>
|
||
</div>
|
||
<div class="lo-settings-field">
|
||
<label>
|
||
Ceiling Clearance
|
||
<span class="lo-settings-hint">(top only)</span>
|
||
</label>
|
||
<div class="lo-settings-input-wrap">
|
||
<input type="number" id="loMarginCeiling" value="20" min="0" max="100" step="1">
|
||
<span class="lo-unit">cm</span>
|
||
</div>
|
||
<div class="lo-settings-effective" id="loMarginCeilingEffective"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- ===== END SETTINGS ===== -->
|
||
|
||
<div class="lo-presets-section">
|
||
<h4>🔘 Preset Transport Type:</h4>
|
||
<div class="lo-preset-buttons">
|
||
<button class="lo-preset-btn" data-preset="container_20ft">20ft Container</button>
|
||
<button class="lo-preset-btn" data-preset="container_40ft">40ft Container</button>
|
||
<button class="lo-preset-btn" data-preset="container_40ft_hc">40ft High Cube</button>
|
||
<button class="lo-preset-btn" data-preset="lkw_sattelzug">LKW Sattelzug</button>
|
||
<button class="lo-preset-btn" data-preset="lkw_tandem">LKW Tandem</button>
|
||
<button class="lo-preset-btn" data-preset="custom">Custom</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lo-custom-section">
|
||
<h4>📏 Dimensions & Weight Limit:</h4>
|
||
<div class="lo-input-grid">
|
||
<div class="lo-input-field">
|
||
<label>Length (m)</label>
|
||
<input type="number" id="customLength" placeholder="e.g. 12.19" step="0.01" min="0">
|
||
</div>
|
||
<div class="lo-input-field">
|
||
<label>Width (m)</label>
|
||
<input type="number" id="customWidth" placeholder="e.g. 2.35" step="0.01" min="0">
|
||
</div>
|
||
<div class="lo-input-field">
|
||
<label>Height (m)</label>
|
||
<input type="number" id="customHeight" placeholder="e.g. 2.39" step="0.01" min="0">
|
||
</div>
|
||
<div class="lo-input-field">
|
||
<label>Max. Load (kg)</label>
|
||
<input type="number" id="customMaxWeight" placeholder="e.g. 30480" step="100" min="0">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lo-button-group">
|
||
<button class="lo-btn-primary" id="calculateLoad">▶ Calculate</button>
|
||
<button class="lo-btn-secondary" id="resetLoad">↺ Reset</button>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="lo-results" id="resultsContainer" style="display:none;">
|
||
|
||
<div class="lo-results-summary">
|
||
<h4>Results:</h4>
|
||
<div class="lo-summary-item">
|
||
<span><strong>Limiting Factor:</strong></span>
|
||
<span id="limitingFactorValue" class="lo-value-highlight">–</span>
|
||
</div>
|
||
<div class="lo-summary-item">
|
||
<span><strong>Total Rolls:</strong></span>
|
||
<div style="text-align:right;">
|
||
<span id="totalRollsValue" class="lo-value-highlight">–</span>
|
||
<small id="rollsDetailValue" style="display:block;color:#888;"></small>
|
||
</div>
|
||
</div>
|
||
<div class="lo-summary-item">
|
||
<span><strong>Utilization:</strong></span>
|
||
<div class="lo-util-group">
|
||
<div>Weight: <span id="weightUtilValue" class="lo-value-number">–</span> %</div>
|
||
<div>Volume: <span id="volumeUtilValue" class="lo-value-number">–</span> %</div>
|
||
<div>Product: <span id="productM2Value" class="lo-value-number">–</span> m²</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="lo-results-table">
|
||
<h4>Comparison Table:</h4>
|
||
<table id="comparisonTable">
|
||
<thead>
|
||
<tr>
|
||
<th>Scenario</th>
|
||
<th>Rolls</th>
|
||
<th>kg</th>
|
||
<th>m²</th>
|
||
<th>Util. %</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="lo-sideview-section" id="sideViewSection" style="display:none;">
|
||
<h4>🔍 Front View – Cross-Section</h4>
|
||
<div class="lo-sideview-wrap">
|
||
<svg id="sideViewSVG" width="100%" viewBox="0 0 800 300" xmlns="http://www.w3.org/2000/svg">
|
||
<text x="400" y="130" text-anchor="middle" fill="#aaa" font-size="14">Run a calculation to see the side view.</text>
|
||
</svg>
|
||
</div>
|
||
<div class="lo-sideview-legend">
|
||
<span class="lo-legend-item"><span class="lo-legend-box roll-color"></span> Roll (cross-section, Ø = diameter)</span>
|
||
<span class="lo-legend-item"><span class="lo-legend-box container-color"></span> Container / Vehicle</span>
|
||
<span class="lo-legend-item"><span class="lo-legend-box margin-color"></span> Clearance zone</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
<!-- END LOAD OPTIMIZER -->
|
||
|
||
</div><!-- app-main -->
|
||
|
||
<div class="app-footer">
|
||
■ Naue GmbH & Co. KG · Roll Diameter Calculator · Internal Application
|
||
</div>
|
||
|
||
|
||
<!-- ============================================================
|
||
ARTICLE DATA (JSON)
|
||
============================================================ -->
|
||
<script>
|
||
window.ARTICLE_DATA = [];
|
||
// Inline config fallback – always available, even without server
|
||
window.APP_CONFIG = {"_comment":"Naue Roll Calculator - General Configuration. Article data stays in article-data.json.","forklift_rules":{"_comment":"Weight-based forklift and core diameter rules. Applies per product category.","applicable_categories":["bentofix"],"applicable_sites":["espelkamp","malaysia"],"rules":[{"id":"heavy_roll","condition":"weight_kg >= 1700","weight_threshold_kg":1700,"max_weight_kg":2750,"requirements":{"min_core_outer_diameter_mm":170,"forklift_mandrel_diameter_mm":140,"forklift_capacity_t":7.5},"messages":{"en":{"warning_title":"Heavy Roll – Special Equipment Required","requirements_label":"The following requirements apply for rolls ≥ 1700 kg (Bentofix):","req_core":"Core outer diameter ≥ 170 mm required (current: {core} mm)","req_mandrel":"Forklift mandrel diameter: 140 mm required","req_forklift":"Forklift capacity: 7.5 t required","core_ok":"Core diameter OK (≥ 170 mm)","core_nok":"Core diameter too small – minimum 170 mm required (current: {core} mm)"},"de":{"warning_title":"Schwere Rolle – Sonderausrüstung erforderlich","requirements_label":"Für Rollen ≥ 1700 kg (Bentofix) gelten folgende Anforderungen:","req_core":"Kernaußendurchmesser ≥ 170 mm erforderlich (aktuell: {core} mm)","req_mandrel":"Gabelstapler-Dorn: 140 mm Durchmesser erforderlich","req_forklift":"Gabelstapler: 7,5 t Tragkraft erforderlich","core_ok":"Kerndurchmesser OK (≥ 170 mm)","core_nok":"Kerndurchmesser zu klein – Minimum 170 mm erforderlich (aktuell: {core} mm)"}}}],"hard_limit":{"max_weight_kg":2750,"messages":{"en":{"block_title":"Weight Limit Exceeded – Calculation Blocked","block_text":"The calculated roll weight of {weight} kg exceeds the absolute maximum of 2750 kg for Bentofix. Please adjust the product length or roll dimensions."},"de":{"block_title":"Gewichtslimit überschritten – Berechnung gesperrt","block_text":"Das berechnete Rollengewicht von {weight} kg überschreitet das absolute Maximum von 2750 kg für Bentofix. Bitte Produktlänge oder Rollenmaße anpassen."}}}}};
|
||
|
||
(async function() {
|
||
try {
|
||
const [artResp, cfgResp] = await Promise.all([
|
||
fetch('article-data.json'), fetch('config.json')
|
||
]);
|
||
if (artResp.ok) {
|
||
window.ARTICLE_DATA = await artResp.json();
|
||
console.log('Article data loaded:', window.ARTICLE_DATA.length, 'items');
|
||
populateArticleDropdowns();
|
||
} else { console.error('Failed to load article data:', artResp.status); }
|
||
if (cfgResp.ok) { window.APP_CONFIG = await cfgResp.json(); console.log('Config updated from file'); }
|
||
else { console.info('config.json not found – using inline config.'); }
|
||
} catch (e) { console.error('Error loading data:', e); }
|
||
})();
|
||
</script>
|
||
|
||
<!-- ============================================================
|
||
LOAD OPTIMIZER CORE
|
||
============================================================ -->
|
||
<script>
|
||
class LoadOptimizer {
|
||
constructor() {
|
||
this.state = {
|
||
rollDiameter: 0, coreDiameter: 150, rollWidth: 0,
|
||
rollWeight: 0, m2PerRoll: 0, productLength: 0
|
||
};
|
||
this.presets = {
|
||
container_20ft: { name:"20ft Container", length:5.90, width:2.35, height:2.39, maxWeight:21600 },
|
||
container_40ft: { name:"40ft Container", length:12.19, width:2.35, height:2.39, maxWeight:30480 },
|
||
container_40ft_hc:{ name:"40ft High Cube", length:12.19, width:2.35, height:2.69, maxWeight:30480 },
|
||
lkw_sattelzug: { name:"LKW Sattelzug", length:13.50, width:2.50, height:2.70, maxWeight:24000 },
|
||
lkw_tandem: { name:"LKW Tandem", length:13.50, width:2.50, height:2.70, maxWeight:25000 }
|
||
};
|
||
}
|
||
setRollData(data) {
|
||
this.state = {
|
||
rollDiameter: data.rollDiameter || 0,
|
||
coreDiameter: data.coreDiameter || 150,
|
||
rollWidth: data.rollWidth || 0,
|
||
rollWeight: data.rollWeight || 0,
|
||
m2PerRoll: data.m2PerRoll || 0,
|
||
productLength: data.productLength || 0
|
||
};
|
||
}
|
||
// marginSide (m) = one side wall clearance
|
||
// marginCeiling (m) = top clearance
|
||
calculateLoadUtilization(dim, maxLoadWeight, marginSide, marginCeiling) {
|
||
marginSide = marginSide || 0;
|
||
marginCeiling = marginCeiling || 0;
|
||
|
||
const { rollDiameter, coreDiameter, rollWidth, rollWeight, m2PerRoll } = this.state;
|
||
const D = rollDiameter / 1000, d = coreDiameter / 1000;
|
||
|
||
// Apply clearances to effective loading space
|
||
const effWidth = dim.width - marginSide;
|
||
const effHeight = dim.height - marginCeiling;
|
||
|
||
// Rolls liegen auf der Seite: Laenge der Rolle = rollWidth, Durchmesser = D
|
||
const rollsAlongLength = Math.floor(dim.length / rollWidth); // entlang Containerlaenge (kein Margin)
|
||
const rollsAlongWidth = Math.floor(effWidth / D); // quer (Durchmesser, mit Seitenabstand)
|
||
const rollsPerLayer = rollsAlongLength * rollsAlongWidth;
|
||
const maxStackHeight = Math.floor(effHeight / D); // Stapellagen (mit Deckenabstand)
|
||
const maxByGeometry = rollsPerLayer * maxStackHeight;
|
||
const maxByWeight = rollWeight > 0 ? Math.floor(maxLoadWeight / rollWeight) : Infinity;
|
||
|
||
// Volume: use original dim for container volume, effective for rolls
|
||
const volPerRoll = (Math.PI / 4) * (D*D - d*d) * rollWidth;
|
||
const containerVol = dim.length * dim.width * dim.height;
|
||
const effVol = dim.length * effWidth * effHeight;
|
||
const maxByVolume = volPerRoll > 0 ? Math.floor(effVol / volPerRoll) : Infinity;
|
||
|
||
const scenarios = [
|
||
{ name:'Weight', rolls: maxByWeight, weight: maxByWeight * rollWeight, type:'weight' },
|
||
{ name:'Volume', rolls: maxByVolume, weight: maxByVolume * rollWeight, type:'volume' },
|
||
{ name:'Geometry', rolls: maxByGeometry, weight: maxByGeometry * rollWeight, type:'geometry' }
|
||
];
|
||
const limiting = scenarios.reduce((mn, c) => c.rolls < mn.rolls ? c : mn);
|
||
const finalRolls = limiting.rolls === Infinity ? 0 : limiting.rolls;
|
||
const finalWeight = finalRolls * rollWeight;
|
||
const finalM2 = finalRolls * m2PerRoll;
|
||
|
||
return {
|
||
scenarios, limiting, finalRolls, finalWeight, finalM2,
|
||
utilizationWeight: maxLoadWeight > 0 ? (finalWeight / maxLoadWeight) * 100 : 0,
|
||
utilizationVolume: containerVol > 0 ? (finalRolls * volPerRoll / containerVol) * 100 : 0,
|
||
rollsPerLayer, rollsAlongLength, rollsAlongWidth, maxStackHeight,
|
||
containerVol, volPerRoll, effWidth, effHeight,
|
||
marginSide, marginCeiling
|
||
};
|
||
}
|
||
fmt(n, dec=1) { return isFinite(n) ? n.toFixed(dec) : '\u2013'; }
|
||
}
|
||
window.LoadOptimizer = LoadOptimizer;
|
||
</script>
|
||
|
||
<!-- ============================================================
|
||
LOAD OPTIMIZER UI
|
||
============================================================ -->
|
||
<script>
|
||
class LoadOptimizerUI {
|
||
constructor() {
|
||
this.optimizer = new window.LoadOptimizer();
|
||
this.currentPreset = null;
|
||
this.init();
|
||
}
|
||
init() {
|
||
document.getElementById('loAccordionToggle').addEventListener('click', () => this.toggleAccordion());
|
||
document.querySelectorAll('.lo-preset-btn').forEach(btn =>
|
||
btn.addEventListener('click', e => this.loadPreset(e.target.dataset.preset))
|
||
);
|
||
document.getElementById('calculateLoad').addEventListener('click', () => this.calculate());
|
||
document.getElementById('resetLoad').addEventListener('click', () => this.resetForm());
|
||
|
||
// Live-preview of effective dimensions when margins or dimensions change
|
||
const updateEffective = () => this.updateEffectiveHints();
|
||
['loMarginSide','loMarginCeiling','customWidth','customHeight'].forEach(id => {
|
||
const el = document.getElementById(id);
|
||
if (el) el.addEventListener('input', updateEffective);
|
||
});
|
||
}
|
||
|
||
// Show effective dimension hints below margin inputs
|
||
updateEffectiveHints() {
|
||
const sideM = parseFloat(document.getElementById('loMarginSide').value) || 0;
|
||
const ceilM = parseFloat(document.getElementById('loMarginCeiling').value) || 0;
|
||
const W = parseFloat(document.getElementById('customWidth').value) || 0;
|
||
const H = parseFloat(document.getElementById('customHeight').value) || 0;
|
||
const sideEl = document.getElementById('loMarginSideEffective');
|
||
const ceilEl = document.getElementById('loMarginCeilingEffective');
|
||
|
||
if (W > 0 && sideM > 0) {
|
||
const eff = (W - sideM / 100).toFixed(2);
|
||
sideEl.textContent = '\u2192 effective width: ' + eff + ' m';
|
||
} else {
|
||
sideEl.textContent = W > 0 ? '\u2192 effective width: ' + W.toFixed(2) + ' m' : '';
|
||
}
|
||
|
||
if (H > 0 && ceilM > 0) {
|
||
const eff = (H - ceilM / 100).toFixed(2);
|
||
ceilEl.textContent = '\u2192 effective height: ' + eff + ' m';
|
||
} else {
|
||
ceilEl.textContent = H > 0 ? '\u2192 effective height: ' + H.toFixed(2) + ' m' : '';
|
||
}
|
||
}
|
||
|
||
toggleAccordion() {
|
||
const header = document.getElementById('loAccordionToggle');
|
||
const content = document.getElementById('loAccordionContent');
|
||
const isOpen = content.style.display !== 'none';
|
||
content.style.display = isOpen ? 'none' : 'block';
|
||
header.classList.toggle('active');
|
||
}
|
||
loadPreset(id) {
|
||
if (id === 'custom') { this.clearPresets(); return; }
|
||
const p = this.optimizer.presets[id];
|
||
if (!p) return;
|
||
document.getElementById('customLength').value = p.length;
|
||
document.getElementById('customWidth').value = p.width;
|
||
document.getElementById('customHeight').value = p.height;
|
||
document.getElementById('customMaxWeight').value = p.maxWeight;
|
||
this.updatePresetBtns(id);
|
||
this.currentPreset = id;
|
||
this.updateEffectiveHints();
|
||
}
|
||
updatePresetBtns(activeId) {
|
||
document.querySelectorAll('.lo-preset-btn').forEach(b => b.classList.remove('active'));
|
||
if (activeId) document.querySelector('[data-preset="'+activeId+'"]')?.classList.add('active');
|
||
}
|
||
clearPresets() { this.updatePresetBtns(null); this.currentPreset = null; }
|
||
|
||
// Read margin values (cm -> m)
|
||
getMargins() {
|
||
const sideM = (parseFloat(document.getElementById('loMarginSide').value) || 0) / 100;
|
||
const ceilM = (parseFloat(document.getElementById('loMarginCeiling').value) || 0) / 100;
|
||
return { sideM, ceilM };
|
||
}
|
||
|
||
calculate() {
|
||
const L = parseFloat(document.getElementById('customLength').value);
|
||
const W = parseFloat(document.getElementById('customWidth').value);
|
||
const H = parseFloat(document.getElementById('customHeight').value);
|
||
const MW = parseFloat(document.getElementById('customMaxWeight').value);
|
||
if ([L,W,H,MW].some(v => isNaN(v) || v <= 0)) {
|
||
alert('Please fill in all transport dimensions!');
|
||
return;
|
||
}
|
||
const rollData = this.getRollData();
|
||
if (!rollData) {
|
||
alert('Please complete a roll calculation first (Direct Calculation tab).\nRoll Diameter, Roll Width and Roll Weight are required.');
|
||
return;
|
||
}
|
||
const { sideM, ceilM } = this.getMargins();
|
||
this.optimizer.setRollData(rollData);
|
||
const res = this.optimizer.calculateLoadUtilization({ length:L, width:W, height:H }, MW, sideM, ceilM);
|
||
this.showResults(res, MW);
|
||
this.drawSideView(res, { length:L, width:W, height:H });
|
||
}
|
||
|
||
getRollData() {
|
||
const D = parseFloat(document.getElementById('d-di')?.value || 0);
|
||
const W = parseFloat(document.getElementById('de-wi')?.value || 0);
|
||
const GW = parseFloat(document.getElementById('d-wo')?.value || 0);
|
||
const L = parseFloat(document.getElementById('d-le')?.value || 0);
|
||
const d = parseFloat(document.getElementById('d-co')?.value || 150);
|
||
if (!D || !W || !GW) return null;
|
||
return { rollDiameter:D, coreDiameter:d, rollWidth:W, rollWeight:GW, m2PerRoll:L*W, productLength:L };
|
||
}
|
||
|
||
showResults(res, maxW) {
|
||
const m2PerRoll = this.optimizer.state.m2PerRoll || 0;
|
||
document.getElementById('limitingFactorValue').textContent = res.limiting.name;
|
||
document.getElementById('totalRollsValue').textContent = res.finalRolls;
|
||
document.getElementById('rollsDetailValue').textContent =
|
||
'(' + res.rollsAlongLength + ' along length \u00d7 ' + res.rollsAlongWidth + ' across, ' + res.maxStackHeight + ' layers)';
|
||
document.getElementById('weightUtilValue').textContent = this.optimizer.fmt(res.utilizationWeight);
|
||
document.getElementById('volumeUtilValue').textContent = this.optimizer.fmt(res.utilizationVolume);
|
||
document.getElementById('productM2Value').textContent = this.optimizer.fmt(res.finalM2, 0);
|
||
const tbody = document.querySelector('#comparisonTable tbody');
|
||
tbody.innerHTML = '';
|
||
res.scenarios.forEach(sc => {
|
||
const tr = document.createElement('tr');
|
||
const lim = (sc.type === res.limiting.type);
|
||
if (lim) tr.classList.add('lo-recommended');
|
||
const util = maxW > 0 ? (sc.weight / maxW * 100).toFixed(1) : '\u2013';
|
||
const scM2 = isFinite(sc.rolls) && m2PerRoll > 0 ? Math.round(sc.rolls * m2PerRoll) : '\u2013';
|
||
tr.innerHTML =
|
||
'<td>'+ sc.name +(lim?' \u2605':'')+'</td>'+
|
||
'<td>'+ (sc.rolls === Infinity ? '\u2013' : sc.rolls) +'</td>'+
|
||
'<td>'+ (isFinite(sc.weight) ? Math.round(sc.weight) : '\u2013') +'</td>'+
|
||
'<td>'+ scM2 +'</td>'+
|
||
'<td>'+ util +'%</td>';
|
||
tbody.appendChild(tr);
|
||
});
|
||
document.getElementById('resultsContainer').style.display = 'block';
|
||
}
|
||
|
||
drawSideView(res, dim) {
|
||
const svg = document.getElementById('sideViewSVG');
|
||
const section = document.getElementById('sideViewSection');
|
||
if (!svg || !res || res.finalRolls === 0) return;
|
||
|
||
const D_m = this.optimizer.state.rollDiameter / 1000;
|
||
const aW = res.rollsAlongWidth;
|
||
const aL = res.rollsAlongLength;
|
||
const layers = res.maxStackHeight;
|
||
const total = res.finalRolls;
|
||
const marginSide = res.marginSide || 0; // m
|
||
const marginCeil = res.marginCeiling || 0; // m
|
||
|
||
const PAD = 52, W_SVG = 800, H_SVG = 300;
|
||
const scaleX = (W_SVG - 2*PAD) / dim.width;
|
||
const scaleY = (H_SVG - 2*PAD) / dim.height;
|
||
const scale = Math.min(scaleX, scaleY);
|
||
|
||
const cW = dim.width * scale;
|
||
const cH = dim.height * scale;
|
||
const cX = PAD + ((W_SVG - 2*PAD) - cW) / 2;
|
||
const cY = PAD + ((H_SVG - 2*PAD) - cH) / 2;
|
||
const rD = D_m * scale;
|
||
|
||
// Margin in SVG pixels
|
||
const mSidePx = marginSide * scale;
|
||
const mCeilPx = marginCeil * scale;
|
||
|
||
let s = '';
|
||
s += '<defs>';
|
||
s += '<marker id="arr" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="#666"/></marker>';
|
||
s += '<marker id="arr2" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto-start-reverse"><path d="M0,0 L0,6 L6,3 z" fill="#666"/></marker>';
|
||
// Hatch pattern for clearance zones
|
||
s += '<pattern id="hatchMargin" patternUnits="userSpaceOnUse" width="8" height="8" patternTransform="rotate(45)">';
|
||
s += '<rect width="8" height="8" fill="#fff7e0"/>';
|
||
s += '<line x1="0" y1="0" x2="0" y2="8" stroke="#f0b429" stroke-width="2.5"/>';
|
||
s += '</pattern>';
|
||
s += '</defs>';
|
||
|
||
s += '<rect x="0" y="0" width="800" height="300" fill="#f8f9fa"/>';
|
||
// Container outline
|
||
s += '<rect x="'+cX.toFixed(1)+'" y="'+cY.toFixed(1)+'" width="'+cW.toFixed(1)+'" height="'+cH.toFixed(1)+'" fill="#e8e8e8" stroke="#888" stroke-width="2.5" rx="3"/>';
|
||
s += '<rect x="'+cX.toFixed(1)+'" y="'+(cY+cH-5).toFixed(1)+'" width="'+cW.toFixed(1)+'" height="5" fill="#bbb"/>';
|
||
|
||
// Clearance zones (drawn before rolls)
|
||
if (mSidePx > 0.5) {
|
||
// Right side wall clearance zone
|
||
s += '<rect x="'+(cX+cW-mSidePx).toFixed(1)+'" y="'+cY.toFixed(1)+'"'
|
||
+ ' width="'+mSidePx.toFixed(1)+'" height="'+cH.toFixed(1)+'"'
|
||
+ ' fill="url(#hatchMargin)" opacity="0.9"/>';
|
||
// Label if wide enough
|
||
if (mSidePx > 18) {
|
||
s += '<text x="'+(cX+cW-mSidePx/2).toFixed(1)+'" y="'+(cY+cH/2).toFixed(1)+'"'
|
||
+ ' text-anchor="middle" font-size="9" fill="#b07800" transform="rotate(-90,'
|
||
+ (cX+cW-mSidePx/2).toFixed(1)+','+(cY+cH/2).toFixed(1)+')">'
|
||
+ (marginSide*100).toFixed(0)+' cm</text>';
|
||
}
|
||
}
|
||
if (mCeilPx > 0.5) {
|
||
// Ceiling clearance zone
|
||
s += '<rect x="'+cX.toFixed(1)+'" y="'+cY.toFixed(1)+'"'
|
||
+ ' width="'+cW.toFixed(1)+'" height="'+mCeilPx.toFixed(1)+'"'
|
||
+ ' fill="url(#hatchMargin)" opacity="0.9"/>';
|
||
if (mCeilPx > 12) {
|
||
s += '<text x="'+(cX+cW/2).toFixed(1)+'" y="'+(cY+mCeilPx/2+4).toFixed(1)+'"'
|
||
+ ' text-anchor="middle" font-size="9" fill="#b07800">'
|
||
+ (marginCeil*100).toFixed(0)+' cm</text>';
|
||
}
|
||
}
|
||
|
||
// Draw rolls — position within effective area
|
||
// Rolls start from bottom of container (above floor bar), stack upward within effHeight
|
||
// Side: rolls start from left wall, fill effWidth
|
||
let drawn = 0;
|
||
for (let layer = 0; layer < layers && drawn < total; layer++) {
|
||
for (let col = 0; col < aW && drawn < total; col++) {
|
||
// cx: from left wall (no side margin on left side)
|
||
const cx = cX + col * rD + rD / 2;
|
||
// cy: from bottom, stacking up, ceiling margin at top
|
||
const cy = cY + cH - layer * rD - rD / 2 - 5;
|
||
const r = Math.max(1, rD / 2 - 1.0);
|
||
// Shadow
|
||
s += '<circle cx="'+(cx+1.5).toFixed(1)+'" cy="'+(cy+1.5).toFixed(1)+'" r="'+r.toFixed(1)+'" fill="rgba(0,0,0,0.08)"/>';
|
||
// Roll body
|
||
s += '<circle cx="'+cx.toFixed(1)+'" cy="'+cy.toFixed(1)+'" r="'+r.toFixed(1)+'" fill="#a8d4f5" stroke="#0066cc" stroke-width="1.2"/>';
|
||
// Core
|
||
if (rD > 16) {
|
||
const kr = Math.max(1.5, rD * 0.13);
|
||
s += '<circle cx="'+cx.toFixed(1)+'" cy="'+cy.toFixed(1)+'" r="'+kr.toFixed(1)+'" fill="white" stroke="#0052a3" stroke-width="1" opacity="0.8"/>';
|
||
}
|
||
drawn++;
|
||
}
|
||
}
|
||
|
||
// Dimension arrows
|
||
const dimY = cY + cH + 20;
|
||
s += '<line x1="'+cX.toFixed(1)+'" y1="'+dimY.toFixed(1)+'" x2="'+(cX+cW).toFixed(1)+'" y2="'+dimY.toFixed(1)+'" stroke="#666" stroke-width="1" marker-start="url(#arr2)" marker-end="url(#arr)"/>';
|
||
s += '<text x="'+(cX+cW/2).toFixed(1)+'" y="'+(dimY+13).toFixed(1)+'" text-anchor="middle" font-size="11" fill="#555">'+dim.width+' m</text>';
|
||
|
||
const dimX = cX - 22;
|
||
const midY = (cY + cH/2).toFixed(1);
|
||
s += '<line x1="'+dimX.toFixed(1)+'" y1="'+cY.toFixed(1)+'" x2="'+dimX.toFixed(1)+'" y2="'+(cY+cH).toFixed(1)+'" stroke="#666" stroke-width="1" marker-start="url(#arr2)" marker-end="url(#arr)"/>';
|
||
s += '<text x="'+(dimX-5).toFixed(1)+'" y="'+midY+'" text-anchor="middle" font-size="11" fill="#555" transform="rotate(-90,'+(dimX-5).toFixed(1)+','+midY+')">'+dim.height+' m</text>';
|
||
|
||
s += '<text x="'+(cX+cW/2).toFixed(1)+'" y="'+(cY-8).toFixed(1)+'" text-anchor="middle" font-size="11" fill="#666" font-style="italic">'+aL+' rows deep (into page)</text>';
|
||
s += '<text x="'+(cX+cW-4).toFixed(1)+'" y="'+(cY+15).toFixed(1)+'" text-anchor="end" font-size="10" fill="#555" font-style="italic">'+aW+' wide \u00d7 '+layers+' high \u00d7 '+aL+' deep \u00b7 '+total+' rolls</text>';
|
||
|
||
// Roll diameter annotation
|
||
if (rD > 30 && aW > 0) {
|
||
const rx1 = cX, rx2 = cX + rD, ry = cY + cH - rD/2 - 5;
|
||
s += '<line x1="'+rx1.toFixed(1)+'" y1="'+(ry+rD/2+10).toFixed(1)+'" x2="'+rx2.toFixed(1)+'" y2="'+(ry+rD/2+10).toFixed(1)+'" stroke="#0066cc" stroke-width="0.8" stroke-dasharray="3,2" marker-start="url(#arr2)" marker-end="url(#arr)"/>';
|
||
s += '<text x="'+(cX+rD/2).toFixed(1)+'" y="'+(ry+rD/2+22).toFixed(1)+'" text-anchor="middle" font-size="10" fill="#0066cc">\u00d8 '+(D_m*1000).toFixed(0)+' mm</text>';
|
||
}
|
||
|
||
svg.setAttribute('viewBox', '0 0 800 300');
|
||
svg.innerHTML = s;
|
||
section.style.display = 'block';
|
||
}
|
||
|
||
resetForm() {
|
||
['customLength','customWidth','customHeight','customMaxWeight'].forEach(id =>
|
||
document.getElementById(id).value = ''
|
||
);
|
||
// Reset margins to defaults
|
||
document.getElementById('loMarginSide').value = '0';
|
||
document.getElementById('loMarginCeiling').value = '20';
|
||
document.getElementById('loMarginSideEffective').textContent = '';
|
||
document.getElementById('loMarginCeilingEffective').textContent = '';
|
||
document.getElementById('resultsContainer').style.display = 'none';
|
||
document.getElementById('sideViewSection').style.display = 'none';
|
||
this.clearPresets();
|
||
}
|
||
}
|
||
</script>
|
||
|
||
|
||
<!-- ============================================================
|
||
FORKLIFT / WEIGHT CHECK LOGIC
|
||
============================================================ -->
|
||
<script>
|
||
const ForkliftChecker = {
|
||
getRules() { return (window.APP_CONFIG && window.APP_CONFIG.forklift_rules) || null; },
|
||
isApplicable(cat) {
|
||
const r = this.getRules();
|
||
return r ? (r.applicable_categories||[]).includes((cat||'').toLowerCase()) : false;
|
||
},
|
||
evaluate(weightKg, coreMm, category) {
|
||
const box = document.getElementById('forkliftCheck');
|
||
const titleEl = document.getElementById('forkliftCheckTitle');
|
||
const bodyEl = document.getElementById('forkliftCheckBody');
|
||
if (!this.isApplicable(category) || !weightKg || weightKg <= 0) {
|
||
box.style.display = 'none'; box.className = 'forklift-check'; return true;
|
||
}
|
||
const rules = this.getRules(), rule = rules.rules[0], limit = rules.hard_limit;
|
||
const msgs = rule.messages.en, limMsg = limit.messages.en, req = rule.requirements;
|
||
box.style.display = 'block';
|
||
if (weightKg > limit.max_weight_kg) {
|
||
box.className = 'forklift-check fc-block';
|
||
titleEl.innerHTML = '🚫 ' + limMsg.block_title;
|
||
bodyEl.innerHTML = '<p>' + limMsg.block_text
|
||
.replace('{weight}', weightKg.toFixed(1))
|
||
.replace('2750', limit.max_weight_kg) + '</p>';
|
||
return false;
|
||
}
|
||
if (weightKg >= rule.weight_threshold_kg) {
|
||
const coreOk = coreMm >= req.min_core_outer_diameter_mm;
|
||
box.className = 'forklift-check fc-warn';
|
||
titleEl.innerHTML = '⚠️ ' + msgs.warning_title;
|
||
const ci = coreOk
|
||
? '<li class="fc-req-ok">✅ ' + msgs.core_ok + '</li>'
|
||
: '<li class="fc-req-nok">❌ ' + msgs.core_nok.replace('{core}', coreMm) + '</li>';
|
||
bodyEl.innerHTML = '<div>' + msgs.requirements_label + '</div><ul>' + ci +
|
||
'<li>🔧 ' + msgs.req_mandrel + '</li>' +
|
||
'<li>🚜 ' + msgs.req_forklift + '</li></ul>';
|
||
return true;
|
||
}
|
||
box.className = 'forklift-check fc-ok';
|
||
titleEl.innerHTML = '✅ Forklift Check – OK';
|
||
bodyEl.innerHTML = 'Roll weight <strong>' + weightKg.toFixed(1) +
|
||
' kg</strong> is within the standard limit (<' + rule.weight_threshold_kg +
|
||
' kg). No special equipment required.';
|
||
return true;
|
||
},
|
||
hide() {
|
||
const b = document.getElementById('forkliftCheck');
|
||
if (b) { b.style.display = 'none'; b.className = 'forklift-check'; }
|
||
}
|
||
};
|
||
window.ForkliftChecker = ForkliftChecker;
|
||
</script>
|
||
<!-- ============================================================
|
||
ROLL CALCULATOR LOGIC
|
||
============================================================ -->
|
||
<script>
|
||
// --- Article dropdown population ---
|
||
function populateArticleDropdowns() {
|
||
const data = window.ARTICLE_DATA || [];
|
||
|
||
// Datalists befuellen
|
||
['d-art','e-art'].forEach(function(inputId) {
|
||
var dl = document.getElementById(inputId + '-list');
|
||
if (!dl) return;
|
||
data.forEach(function(a) {
|
||
var opt = document.createElement('option');
|
||
opt.value = a.nr + ' \u2013 ' + a.name;
|
||
dl.appendChild(opt);
|
||
});
|
||
});
|
||
|
||
// Auto-fill Dicke bei Artikelauswahl (d-art)
|
||
var dArt = document.getElementById('d-art');
|
||
if (dArt) {
|
||
dArt.addEventListener('input', function() {
|
||
var val = dArt.value;
|
||
var match = data.find(function(a) { return val.startsWith(a.nr + ' \u2013') || val === a.nr; });
|
||
if (match) {
|
||
document.getElementById('d-th').value = match.thickness;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Kern-Preset Buttons
|
||
document.querySelectorAll('.core-preset-btn').forEach(function(btn) {
|
||
btn.addEventListener('click', function() {
|
||
var val = btn.dataset.core;
|
||
var pane = btn.closest('.tab-pane');
|
||
if (pane) {
|
||
var inp = pane.querySelector('input[id$="-co"]');
|
||
if (inp) {
|
||
inp.value = val;
|
||
pane.querySelectorAll('.core-preset-btn').forEach(function(b) { b.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// --- Tab switching ---
|
||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||
document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
|
||
});
|
||
});
|
||
|
||
// --- Mode switching (Direct Calc) ---
|
||
document.querySelectorAll('.mode-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
});
|
||
});
|
||
|
||
// --- DIRECT CALCULATION ---
|
||
document.getElementById('d-calc').addEventListener('click', () => {
|
||
const mode = document.querySelector('.mode-btn.active')?.dataset.mode || 'diameter';
|
||
const d_mm = parseFloat(document.getElementById('d-co').value) || 0;
|
||
const t_mm = parseFloat(document.getElementById('d-th').value) || 0;
|
||
const tol = parseFloat(document.getElementById('d-tol').value) || 0;
|
||
const L_m = parseFloat(document.getElementById('d-le').value) || 0;
|
||
const D_mm = parseFloat(document.getElementById('d-di').value) || 0;
|
||
const resBox = document.getElementById('d-result');
|
||
const resLabel = document.getElementById('d-res-label');
|
||
const resVal = document.getElementById('d-res-val');
|
||
const resRange = document.getElementById('d-res-range');
|
||
const category = (document.getElementById('d-cat')?.value || '').toLowerCase();
|
||
|
||
function calcWeight(length) {
|
||
const wi = parseFloat(document.getElementById('de-wi').value) || 0;
|
||
const aw = parseFloat(document.getElementById('d-aw').value) || 0;
|
||
if (wi > 0 && aw > 0 && length > 0) {
|
||
const w = (aw * length * wi / 1000).toFixed(1);
|
||
document.getElementById('d-wo').value = w;
|
||
return parseFloat(w);
|
||
}
|
||
document.getElementById('d-wo').value = '';
|
||
return null;
|
||
}
|
||
|
||
function runForkliftCheck(weight) {
|
||
const core = parseFloat(document.getElementById('d-co').value) || 0;
|
||
if (window.ForkliftChecker) {
|
||
console.log('[Forklift] Evaluating:', {weight, core, category, configReady: window.CONFIG_READY, configExists: !!window.APP_CONFIG});
|
||
return window.ForkliftChecker.evaluate(weight || 0, core, category);
|
||
}
|
||
console.warn('[Forklift] ForkliftChecker not available');
|
||
return true;
|
||
}
|
||
|
||
let calculatedLength = null;
|
||
|
||
if (mode === 'diameter') {
|
||
if (!d_mm || !t_mm || !L_m) { alert('Please enter d, t and L.'); return; }
|
||
const D_nom = Math.sqrt(d_mm*d_mm + (4*L_m*1000*t_mm)/Math.PI);
|
||
resLabel.textContent = 'Roll Diameter (D)';
|
||
resVal.textContent = D_nom.toFixed(1) + ' mm';
|
||
if (tol) {
|
||
const D_min = Math.sqrt(d_mm*d_mm + (4*L_m*1000*(t_mm-tol))/Math.PI);
|
||
const D_max = Math.sqrt(d_mm*d_mm + (4*L_m*1000*(t_mm+tol))/Math.PI);
|
||
resRange.textContent = 'Min: ' + D_min.toFixed(1) + ' mm | Max: ' + D_max.toFixed(1) + ' mm';
|
||
} else { resRange.textContent = ''; }
|
||
document.getElementById('d-di').value = D_nom.toFixed(1);
|
||
calculatedLength = L_m;
|
||
|
||
} else if (mode === 'length') {
|
||
if (!d_mm || !t_mm || !D_mm) { alert('Please enter d, t and D.'); return; }
|
||
const L = (Math.PI / (4 * t_mm)) * (D_mm*D_mm - d_mm*d_mm) / 1000;
|
||
resLabel.textContent = 'Product Length (L)';
|
||
resVal.textContent = L.toFixed(2) + ' m';
|
||
if (tol) {
|
||
const L_min = (Math.PI / (4*(t_mm+tol))) * (D_mm*D_mm - d_mm*d_mm) / 1000;
|
||
const L_max = (Math.PI / (4*(t_mm-tol))) * (D_mm*D_mm - d_mm*d_mm) / 1000;
|
||
resRange.textContent = 'Min: ' + L_min.toFixed(2) + ' m | Max: ' + L_max.toFixed(2) + ' m';
|
||
} else { resRange.textContent = ''; }
|
||
document.getElementById('d-le').value = L.toFixed(2);
|
||
calculatedLength = L;
|
||
|
||
} else if (mode === 'target') {
|
||
if (!d_mm || !t_mm) { alert('Please enter d and t.'); return; }
|
||
const D_target = D_mm || 800;
|
||
const L = (Math.PI / (4 * t_mm)) * (D_target*D_target - d_mm*d_mm) / 1000;
|
||
resLabel.textContent = 'Length for Target Diameter ' + D_target + ' mm';
|
||
resVal.textContent = L.toFixed(2) + ' m';
|
||
resRange.textContent = '';
|
||
document.getElementById('d-le').value = L.toFixed(2);
|
||
calculatedLength = L;
|
||
|
||
} else if (mode === 'thickness') {
|
||
if (!d_mm || !D_mm || !L_m) { alert('Please enter d, D and L.'); return; }
|
||
const t = (Math.PI / (4 * L_m * 1000)) * (D_mm*D_mm - d_mm*d_mm);
|
||
resLabel.textContent = 'Product Thickness (t)';
|
||
resVal.textContent = t.toFixed(3) + ' mm';
|
||
resRange.textContent = '';
|
||
document.getElementById('d-th').value = t.toFixed(3);
|
||
calculatedLength = L_m;
|
||
}
|
||
|
||
const weight = calcWeight(calculatedLength);
|
||
const allowed = runForkliftCheck(weight);
|
||
resBox.style.display = allowed ? 'block' : 'none';
|
||
});
|
||
|
||
// --- DIRECT RESET ---
|
||
document.getElementById('d-reset').addEventListener('click', () => {
|
||
['d-co','d-th','d-tol','d-le','d-di','de-wi','d-aw','d-wo'].forEach(id => {
|
||
const el = document.getElementById(id);
|
||
if (el) el.value = id === 'd-co' ? '150' : '';
|
||
});
|
||
document.getElementById('d-art').value = '';
|
||
document.getElementById('d-result').style.display = 'none';
|
||
if (window.ForkliftChecker) window.ForkliftChecker.hide();
|
||
});
|
||
|
||
// --- EXTRAPOLATION CALCULATION ---
|
||
document.getElementById('e-calc').addEventListener('click', () => {
|
||
const d = parseFloat(document.getElementById('e-co').value) || 0;
|
||
const D0 = parseFloat(document.getElementById('e-d0').value) || 0;
|
||
const L0 = parseFloat(document.getElementById('e-l0').value) || 0;
|
||
const L1 = parseFloat(document.getElementById('e-l1').value) || 0;
|
||
const tol= parseFloat(document.getElementById('e-tol').value)|| 0;
|
||
|
||
if (!d || !D0 || !L0 || !L1) { alert('Please enter d, D\u2080, L\u2080 and L\u2081.'); return; }
|
||
|
||
const D1 = Math.sqrt(d*d + (L1/L0) * (D0*D0 - d*d));
|
||
document.getElementById('e-res-val').textContent = D1.toFixed(1) + ' mm';
|
||
|
||
if (tol) {
|
||
const D1_min = D1 - tol, D1_max = D1 + tol;
|
||
document.getElementById('e-res-range').textContent =
|
||
'Min: ' + D1_min.toFixed(1) + ' mm | Max: ' + D1_max.toFixed(1) + ' mm';
|
||
} else {
|
||
document.getElementById('e-res-range').textContent = '';
|
||
}
|
||
|
||
document.getElementById('e-result').style.display = 'block';
|
||
});
|
||
|
||
// --- EXTRAPOLATION RESET ---
|
||
document.getElementById('e-reset').addEventListener('click', () => {
|
||
['e-co','e-d0','e-l0','e-l1','e-tol','e-wi','e-aw'].forEach(id => {
|
||
const el = document.getElementById(id);
|
||
if (el) el.value = id === 'e-co' ? '150' : '';
|
||
});
|
||
document.getElementById('e-art').selectedIndex = 0;
|
||
document.getElementById('e-result').style.display = 'none';
|
||
});
|
||
|
||
// --- DISCLAIMER ---
|
||
document.getElementById('confirmCheck').addEventListener('change', function() {
|
||
document.getElementById('btnAccept').disabled = !this.checked;
|
||
});
|
||
document.getElementById('btnAccept').addEventListener('click', () => {
|
||
document.getElementById('disclaimerOverlay').style.display = 'none';
|
||
});
|
||
document.getElementById('btnCancel').addEventListener('click', () => {
|
||
window.location.href = 'about:blank';
|
||
});
|
||
|
||
// --- INIT ---
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
if (window.ARTICLE_DATA && window.ARTICLE_DATA.length > 0) {
|
||
populateArticleDropdowns();
|
||
}
|
||
window.loadOptimizerUI = new LoadOptimizerUI();
|
||
});
|
||
</script>
|
||
|
||
<!-- ============================================================
|
||
SERVICE WORKER REGISTRATION
|
||
============================================================ -->
|
||
|
||
<!-- Offline Indicator -->
|
||
<div id="offlineIndicator" style="display:none; position:fixed; top:0; left:0; right:0; background:#ff6600; color:#fff; padding:10px; text-align:center; font-weight:bold; z-index:10000;">
|
||
📡 You are offline - using cached data
|
||
</div>
|
||
<script>
|
||
window.addEventListener('offline', () => {
|
||
document.getElementById('offlineIndicator').style.display = 'block';
|
||
});
|
||
window.addEventListener('online', () => {
|
||
document.getElementById('offlineIndicator').style.display = 'none';
|
||
});
|
||
if (!navigator.onLine) {
|
||
document.getElementById('offlineIndicator').style.display = 'block';
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
if ('serviceWorker' in navigator) {
|
||
window.addEventListener('load', () => {
|
||
navigator.serviceWorker.register('service-worker.js')
|
||
.then(reg => { console.log('[App] ServiceWorker registered:', reg.scope); })
|
||
.catch(err => { console.warn('[App] ServiceWorker registration failed:', err); });
|
||
});
|
||
}
|
||
|
||
// Auto-calculate weight when article is selected with area_weight
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
setTimeout(function() {
|
||
const origD_art = document.getElementById('d-art');
|
||
if (origD_art) {
|
||
origD_art.addEventListener('input', function() {
|
||
// Berechne Gewicht automatisch wenn alle Felder gefüllt sind
|
||
setTimeout(function() {
|
||
var wi = parseFloat(document.getElementById('de-wi').value) || 0;
|
||
var aw = parseFloat(document.getElementById('d-aw').value) || 0;
|
||
var L = parseFloat(document.getElementById('d-le').value) || 0;
|
||
if (wi > 0 && aw > 0 && L > 0) {
|
||
var w = (aw * L * wi / 1000).toFixed(1);
|
||
document.getElementById('d-wo').value = w;
|
||
console.log('[AutoCalc] Gewicht: ' + w + ' kg');
|
||
}
|
||
}, 100);
|
||
});
|
||
}
|
||
}, 500);
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|