1308 lines
55 KiB
HTML
1308 lines
55 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="description" content="Roll Diameter Calculator - Internal Naue Tool">
|
||
<title>Roll Diameter Calculator</title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body { font-family: 'Segoe UI', Arial, sans-serif; background: #f4f6f9; color: #333; min-height: 100vh; }
|
||
.header { background: linear-gradient(135deg, #1F5438 0%, #2BAC70 100%); color: #fff; padding: 20px 32px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); }
|
||
.header h1 { font-size: 24px; margin-bottom: 5px; }
|
||
.header p { font-size: 13px; opacity: 0.9; }
|
||
.container { max-width: 900px; margin: 30px auto; padding: 0 16px 60px; }
|
||
.card { background: #fff; border-radius: 10px; box-shadow: 0 2px 12px rgba(0,0,0,0.07); margin-bottom: 24px; overflow: hidden; }
|
||
.card-header { background: #f0f4fa; padding: 16px 22px; border-bottom: 1px solid #e0e8f0; }
|
||
.card-header h2 { font-size: 16px; color: #1F5438; font-weight: 700; }
|
||
.card-body { padding: 22px; }
|
||
.tabs { display: flex; border-bottom: 2px solid #e0e8f0; margin-bottom: 20px; }
|
||
.tab-btn { padding: 10px 20px; border: none; background: none; cursor: pointer; font-size: 14px; color: #666; font-weight: 500; border-bottom: 3px solid transparent; transition: all 0.2s; }
|
||
.tab-btn.active { color: #2BAC70; border-bottom-color: #2BAC70; font-weight: 700; }
|
||
.tab-pane { display: none; }
|
||
.tab-pane.active { display: block; }
|
||
.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: 6px; text-transform: uppercase; }
|
||
.form-group input, .form-group select { padding: 9px 11px; border: 1px solid #ccc; border-radius: 5px; font-size: 14px; }
|
||
.form-group input:focus, .form-group select:focus { outline: none; border-color: #2BAC70; box-shadow: 0 0 0 3px rgba(43,172,112,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; }
|
||
.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; }
|
||
.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 #2BAC70; background: #fff; color: #2BAC70; border-radius: 4px; cursor: pointer; transition: all 0.15s; }
|
||
.core-preset-btn:hover, .core-preset-btn.active { background: #2BAC70; color: #fff; }
|
||
.mode-selector { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
|
||
.mode-btn { padding: 7px 16px; border: 2px solid #2BAC70; background: #fff; color: #2BAC70; border-radius: 5px; cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.2s; }
|
||
.mode-btn.active { background: #2BAC70; color: #fff; }
|
||
.btn-calc { background: #2BAC70; color: #fff; border: none; border-radius: 6px; padding: 11px 28px; font-size: 14px; font-weight: 700; cursor: pointer; transition: background 0.2s; }
|
||
.btn-calc:hover { background: #1F5438; }
|
||
.btn-reset { background: #e0e0e0; color: #444; border: none; border-radius: 6px; padding: 11px 20px; font-size: 14px; cursor: pointer; margin-left: 10px; }
|
||
.btn-reset:hover { background: #d0d0d0; }
|
||
.result-box { background: linear-gradient(135deg, #D6EBE2 0%, #8CC9AB 100%); border: 2px solid #2BAC70; border-radius: 8px; padding: 16px 20px; margin-bottom: 18px; display: none; }
|
||
.result-box.show { display: block; }
|
||
.result-label { font-size: 12px; color: #1F5438; font-weight: 600; text-transform: uppercase; }
|
||
.result-value { font-size: 28px; font-weight: 700; color: #1F5438; line-height: 1.2; }
|
||
.result-range { font-size: 12px; color: #1F5438; margin-top: 4px; font-weight: 500; }
|
||
.forklift-check { border-radius: 7px; padding: 14px 16px; margin-top: 14px; font-size: 13px; line-height: 1.6; display: none; }
|
||
.forklift-check.show { display: block; }
|
||
.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; margin-bottom: 8px; }
|
||
.forklift-check ul { margin: 6px 0 0 18px; }
|
||
.forklift-check li { margin-bottom: 3px; }
|
||
.fc-req-ok { color: #28a745; }
|
||
.fc-req-nok { color: #dc3545; font-weight: 600; }
|
||
.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; }
|
||
.footer { text-align: center; font-size: 11px; color: #aaa; padding: 18px; margin-top: 30px; }
|
||
|
||
/* ============================================
|
||
LOAD OPTIMIZER STYLES
|
||
============================================ */
|
||
.load-optimizer-section {
|
||
margin-top: 30px;
|
||
border: 2px solid #2BAC70;
|
||
border-radius: 8px;
|
||
background-color: #f0f7ff;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(43,172,112,0.1);
|
||
}
|
||
.lo-accordion-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px 20px;
|
||
background: linear-gradient(135deg, #2BAC70 0%, #1F5438 100%);
|
||
color: white;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
transition: background 0.3s ease;
|
||
}
|
||
.lo-accordion-header:hover {
|
||
background: linear-gradient(135deg, #1F5438 0%, #1F5438 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;
|
||
display: none;
|
||
}
|
||
.lo-accordion-content.active {
|
||
display: block;
|
||
animation: loSlideDown 0.3s ease;
|
||
}
|
||
@keyframes loSlideDown {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
.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 #2BAC70;
|
||
background: white;
|
||
color: #2BAC70;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
transition: all 0.2s;
|
||
}
|
||
.lo-preset-btn:hover { background: #2BAC70; color: white; }
|
||
.lo-preset-btn.active {
|
||
background: #2BAC70;
|
||
color: white;
|
||
box-shadow: 0 0 0 3px rgba(43,172,112,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: #2BAC70;
|
||
box-shadow: 0 0 0 2px rgba(43,172,112,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: #2BAC70; color: white; }
|
||
.lo-btn-primary:hover { background: #1F5438; box-shadow: 0 2px 6px rgba(43,172,112,0.3); }
|
||
.lo-btn-secondary { background: #e0e0e0; color: #333; }
|
||
.lo-btn-secondary:hover { background: #d0d0d0; }
|
||
.lo-results { display: grid; gap: 20px; display: none; }
|
||
.lo-results.show { display: grid; }
|
||
.lo-results-summary {
|
||
background: white;
|
||
padding: 16px;
|
||
border-radius: 6px;
|
||
border-left: 4px solid #2BAC70;
|
||
}
|
||
.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: #2BAC70; font-weight: bold; font-size: 16px; }
|
||
.lo-value-number { color: #1F5438; 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 #2BAC70;
|
||
}
|
||
.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;
|
||
}
|
||
.lo-settings-section h4 {
|
||
margin-top: 0;
|
||
color: #333;
|
||
font-size: 14px;
|
||
margin-bottom: 4px;
|
||
}
|
||
.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;
|
||
}
|
||
.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: #2BAC70;
|
||
box-shadow: 0 0 0 2px rgba(43,172,112,0.1);
|
||
}
|
||
.lo-settings-input-wrap .lo-unit {
|
||
font-size: 12px;
|
||
color: #888;
|
||
}
|
||
.lo-settings-effective {
|
||
font-size: 11px;
|
||
color: #2BAC70;
|
||
margin-top: 5px;
|
||
min-height: 16px;
|
||
}
|
||
|
||
/* ===== SIDE VIEW ===== */
|
||
.lo-sideview-section {
|
||
background: white; padding: 16px; border-radius: 6px; border: 1px solid #ddd; display: none;
|
||
}
|
||
.lo-sideview-section.show { display: block; }
|
||
.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:#2BAC70; }
|
||
.container-color { background:#e8e8e8; border-color:#999; }
|
||
.margin-color { background: repeating-linear-gradient(45deg,#fff7e0,#fff7e0 3px,#ffe8a0 3px,#ffe8a0 6px); border-color:#f0b429; }
|
||
|
||
@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; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="header">
|
||
<h1>Naue Roll Diameter Calculator</h1>
|
||
<p>Roll Diameter • Product Length • Roll Weight • Load Optimizer</p>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h2>📝 Roll Diameter Calculator</h2>
|
||
</div>
|
||
<div class="card-body">
|
||
|
||
<div class="tabs">
|
||
<button class="tab-btn active" data-tab="direct">Direct Calculation</button>
|
||
<button class="tab-btn" data-tab="extrap">Extrapolation</button>
|
||
</div>
|
||
|
||
<!-- DIRECT CALCULATION TAB -->
|
||
<div class="tab-pane active" id="tab-direct">
|
||
|
||
<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>
|
||
</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>
|
||
|
||
<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>
|
||
</div>
|
||
</div>
|
||
|
||
<h3 style="font-size: 13px; color: #555; margin: 18px 0 14px;">Input Parameters</h3>
|
||
|
||
<div class="form-row">
|
||
<div class="form-group">
|
||
<label>Core 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>
|
||
</div>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Article Number (optional)</label>
|
||
<input type="text" id="d-art" list="d-art-list" placeholder="Search article..." autocomplete="off">
|
||
<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">
|
||
<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>
|
||
|
||
<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="d-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>
|
||
|
||
<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>
|
||
|
||
<div class="result-box" id="d-result">
|
||
<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 class="formula-box">
|
||
Geometry: π/4 × (D² − d²) = L × t | Weight: G [kg] = Area Weight [g/m²] × L [m] × Width [m] / 1000
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- EXTRAPOLATION TAB -->
|
||
<div class="tab-pane" id="tab-extrap">
|
||
|
||
<h3 style="font-size: 13px; color: #555; margin-bottom: 14px;">Known Reference Roll</h3>
|
||
|
||
<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..." autocomplete="off">
|
||
<datalist id="e-art-list"></datalist>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Core 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>
|
||
</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>
|
||
|
||
<h3 style="font-size: 13px; color: #555; margin: 18px 0 14px;">New Roll Length</h3>
|
||
|
||
<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">
|
||
<span class="unit-label">mm</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
|
||
<div class="result-box" id="e-result">
|
||
<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 class="formula-box">
|
||
Geometry: D₁ = √(d² + L₁/L₀ × (D₀² − d²)) | Weight: G [kg] = Area Weight × L₁ × Width / 1000
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</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">
|
||
|
||
<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 (one side)</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 (top only)</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">
|
||
|
||
<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">
|
||
<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>
|
||
|
||
<div class="footer">
|
||
Naue GmbH & Co. KG · Roll Diameter Calculator · Internal Application
|
||
</div>
|
||
|
||
<script>
|
||
// =====================================================
|
||
// GLOBAL STATE
|
||
// =====================================================
|
||
window.ARTICLE_DATA = [];
|
||
window.APP_CONFIG = {"forklift_rules":{"applicable_categories":["bentofix"],"rules":[{"weight_threshold_kg":1700,"max_weight_kg":2750,"requirements":{"min_core_outer_diameter_mm":170},"messages":{"en":{"warning_title":"Heavy Roll – Special Equipment Required","core_ok":"Core diameter OK (≥ 170 mm)","core_nok":"Core diameter too small"}}}],"hard_limit":{"max_weight_kg":2750,"messages":{"en":{"block_title":"Weight Limit Exceeded","block_text":"Roll weight exceeds 2750 kg maximum"}}}}};
|
||
|
||
// =====================================================
|
||
// LOAD ARTICLE DATA
|
||
// =====================================================
|
||
function populateArticleDropdowns() {
|
||
const data = window.ARTICLE_DATA || [];
|
||
|
||
['d-art', 'e-art'].forEach(function(inputId) {
|
||
const dl = document.getElementById(inputId + '-list');
|
||
if (!dl) return;
|
||
data.forEach(function(a) {
|
||
const opt = document.createElement('option');
|
||
opt.value = a.nr + ' – ' + a.name;
|
||
dl.appendChild(opt);
|
||
});
|
||
});
|
||
|
||
const dArt = document.getElementById('d-art');
|
||
if (dArt) {
|
||
dArt.addEventListener('input', function() {
|
||
const val = dArt.value;
|
||
const match = data.find(a => val.startsWith(a.nr + ' –') || val === a.nr);
|
||
if (match) {
|
||
if (match.thickness) document.getElementById('d-th').value = match.thickness;
|
||
if (match.thickness_stddev && match.thickness_stddev > 0) document.getElementById('d-tol').value = match.thickness_stddev;
|
||
if (match.area_weight && match.area_weight > 0) document.getElementById('d-aw').value = match.area_weight;
|
||
if (match.core_type && match.core_type > 0) document.getElementById('d-co').value = match.core_type;
|
||
}
|
||
});
|
||
}
|
||
|
||
const eArt = document.getElementById('e-art');
|
||
if (eArt) {
|
||
eArt.addEventListener('input', function() {
|
||
const val = eArt.value;
|
||
const match = data.find(a => val.startsWith(a.nr + ' –') || val === a.nr);
|
||
if (match) {
|
||
if (match.thickness) document.getElementById('e-th').value = match.thickness;
|
||
if (match.thickness_stddev && match.thickness_stddev > 0) document.getElementById('e-tol').value = match.thickness_stddev;
|
||
if (match.area_weight && match.area_weight > 0) document.getElementById('e-aw').value = match.area_weight;
|
||
if (match.core_type && match.core_type > 0) document.getElementById('e-co').value = match.core_type;
|
||
}
|
||
});
|
||
}
|
||
|
||
document.querySelectorAll('.core-preset-btn').forEach(btn => {
|
||
btn.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
const val = btn.dataset.core;
|
||
const pane = btn.closest('.tab-pane');
|
||
if (pane) {
|
||
const inp = pane.querySelector('input[id$="-co"]');
|
||
if (inp) {
|
||
inp.value = val;
|
||
pane.querySelectorAll('.core-preset-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
}
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
(async function() {
|
||
try {
|
||
const resp = await fetch('/static/article-data.json');
|
||
if (resp.ok) {
|
||
window.ARTICLE_DATA = await resp.json();
|
||
console.log('✅ Article data loaded:', window.ARTICLE_DATA.length, 'items');
|
||
populateArticleDropdowns();
|
||
} else {
|
||
console.error('❌ Failed to load article data:', resp.status);
|
||
}
|
||
} catch (e) {
|
||
console.error('❌ Error loading data:', e);
|
||
}
|
||
})();
|
||
|
||
// =====================================================
|
||
// 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
|
||
// =====================================================
|
||
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 (WITH 2σ)
|
||
// =====================================================
|
||
document.getElementById('d-calc').addEventListener('click', () => {
|
||
const mode = document.querySelector('.mode-btn.active')?.dataset.mode || 'diameter';
|
||
const d = parseFloat(document.getElementById('d-co').value) || 0;
|
||
const t = parseFloat(document.getElementById('d-th').value) || 0;
|
||
const tol = parseFloat(document.getElementById('d-tol').value) || 0;
|
||
const L = parseFloat(document.getElementById('d-le').value) || 0;
|
||
const D = parseFloat(document.getElementById('d-di').value) || 0;
|
||
const category = document.getElementById('d-cat')?.value || '';
|
||
|
||
const resultBox = 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');
|
||
|
||
if (mode === 'diameter') {
|
||
if (!d || !t || !L) { alert('Please enter d, t, and L.'); return; }
|
||
const D_nom = Math.sqrt(d*d + (4*L*1000*t)/Math.PI);
|
||
resLabel.textContent = 'Roll Diameter (D)';
|
||
resVal.textContent = D_nom.toFixed(1) + ' mm';
|
||
if (tol) {
|
||
const D_minus2sigma = Math.sqrt(d*d + (4*L*1000*(t-2*tol))/Math.PI);
|
||
const D_plus2sigma = Math.sqrt(d*d + (4*L*1000*(t+2*tol))/Math.PI);
|
||
resRange.textContent = '−2σ: ' + D_minus2sigma.toFixed(1) + ' mm | +2σ: ' + D_plus2sigma.toFixed(1) + ' mm';
|
||
} else {
|
||
resRange.textContent = '';
|
||
}
|
||
document.getElementById('d-di').value = D_nom.toFixed(1);
|
||
} else if (mode === 'length') {
|
||
if (!d || !t || !D) { alert('Please enter d, t, and D.'); return; }
|
||
const L_calc = (Math.PI / (4 * t)) * (D*D - d*d) / 1000;
|
||
resLabel.textContent = 'Product Length (L)';
|
||
resVal.textContent = L_calc.toFixed(2) + ' m';
|
||
resRange.textContent = '';
|
||
document.getElementById('d-le').value = L_calc.toFixed(2);
|
||
} else if (mode === 'target') {
|
||
if (!d || !t) { alert('Please enter d and t.'); return; }
|
||
const D_target = D || 800;
|
||
const L_calc = (Math.PI / (4 * t)) * (D_target*D_target - d*d) / 1000;
|
||
resLabel.textContent = 'Length for Target Diameter ' + D_target + ' mm';
|
||
resVal.textContent = L_calc.toFixed(2) + ' m';
|
||
resRange.textContent = '';
|
||
document.getElementById('d-le').value = L_calc.toFixed(2);
|
||
}
|
||
|
||
const wi = parseFloat(document.getElementById('d-wi').value) || 0;
|
||
const aw = parseFloat(document.getElementById('d-aw').value) || 0;
|
||
const L_final = parseFloat(document.getElementById('d-le').value) || 0;
|
||
if (wi > 0 && aw > 0 && L_final > 0) {
|
||
const weight = (aw * L_final * wi / 1000).toFixed(1);
|
||
document.getElementById('d-wo').value = weight;
|
||
checkForklift(weight, d, category);
|
||
} else {
|
||
document.getElementById('forkliftCheck').classList.remove('show');
|
||
}
|
||
|
||
resultBox.style.display = 'block';
|
||
});
|
||
|
||
document.getElementById('d-reset').addEventListener('click', () => {
|
||
['d-co', 'd-th', 'd-tol', 'd-le', 'd-di', 'd-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';
|
||
document.getElementById('forkliftCheck').classList.remove('show');
|
||
});
|
||
|
||
// =====================================================
|
||
// EXTRAPOLATION
|
||
// =====================================================
|
||
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 all values.'); 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) {
|
||
document.getElementById('e-res-range').textContent = 'Min: ' + (D1 - tol).toFixed(1) + ' mm | Max: ' + (D1 + tol).toFixed(1) + ' mm';
|
||
} else {
|
||
document.getElementById('e-res-range').textContent = '';
|
||
}
|
||
|
||
document.getElementById('e-result').style.display = 'block';
|
||
});
|
||
|
||
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').value = '';
|
||
document.getElementById('e-result').style.display = 'none';
|
||
});
|
||
|
||
// =====================================================
|
||
// FORKLIFT CHECK
|
||
// =====================================================
|
||
function checkForklift(weightKg, coreMm, category) {
|
||
const box = document.getElementById('forkliftCheck');
|
||
const titleEl = document.getElementById('forkliftCheckTitle');
|
||
const bodyEl = document.getElementById('forkliftCheckBody');
|
||
|
||
const cfg = window.APP_CONFIG?.forklift_rules;
|
||
if (!cfg || !cfg.applicable_categories.includes(category.toLowerCase())) {
|
||
box.classList.remove('show');
|
||
return;
|
||
}
|
||
|
||
const rule = cfg.rules[0];
|
||
const limit = cfg.hard_limit;
|
||
|
||
if (weightKg > limit.max_weight_kg) {
|
||
box.className = 'forklift-check fc-block show';
|
||
titleEl.textContent = '⚠️ ' + limit.messages.en.block_title;
|
||
bodyEl.innerHTML = '<p>' + limit.messages.en.block_text.replace('{weight}', weightKg) + '</p>';
|
||
return;
|
||
}
|
||
|
||
if (weightKg >= rule.weight_threshold_kg) {
|
||
const coreOk = coreMm >= rule.requirements.min_core_outer_diameter_mm;
|
||
box.className = 'forklift-check fc-warn show';
|
||
titleEl.textContent = '⚠️ ' + rule.messages.en.warning_title;
|
||
const statusMsg = coreOk ? '<li class="fc-req-ok">✓ Core diameter OK</li>' : '<li class="fc-req-nok">✗ Core diameter ' + coreMm + ' mm (min 170 mm required)</li>';
|
||
bodyEl.innerHTML = '<p>Special equipment required for rolls ≥ ' + rule.weight_threshold_kg + ' kg:</p><ul>' + statusMsg + '</ul>';
|
||
return;
|
||
}
|
||
|
||
box.className = 'forklift-check fc-ok show';
|
||
titleEl.textContent = '✓ Forklift Check OK';
|
||
bodyEl.innerHTML = '<p>Roll weight ' + weightKg.toFixed(1) + ' kg is within standard limits. No special equipment required.</p>';
|
||
}
|
||
|
||
// =====================================================
|
||
// LOAD OPTIMIZER CORE
|
||
// =====================================================
|
||
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
|
||
};
|
||
}
|
||
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;
|
||
|
||
const effWidth = dim.width - marginSide;
|
||
const effHeight = dim.height - marginCeiling;
|
||
|
||
const rollsAlongLength = Math.floor(dim.length / rollWidth);
|
||
const rollsAlongWidth = Math.floor(effWidth / D);
|
||
const rollsPerLayer = rollsAlongLength * rollsAlongWidth;
|
||
const maxStackHeight = Math.floor(effHeight / D);
|
||
const maxByGeometry = rollsPerLayer * maxStackHeight;
|
||
const maxByWeight = rollWeight > 0 ? Math.floor(maxLoadWeight / rollWeight) : Infinity;
|
||
|
||
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) : '–'; }
|
||
}
|
||
|
||
// =====================================================
|
||
// LOAD OPTIMIZER UI
|
||
// =====================================================
|
||
class LoadOptimizerUI {
|
||
constructor() {
|
||
this.optimizer = new 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());
|
||
|
||
const updateEffective = () => this.updateEffectiveHints();
|
||
['loMarginSide','loMarginCeiling','customWidth','customHeight'].forEach(id => {
|
||
const el = document.getElementById(id);
|
||
if (el) el.addEventListener('input', updateEffective);
|
||
});
|
||
}
|
||
|
||
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 = '→ effective width: ' + eff + ' m';
|
||
} else {
|
||
sideEl.textContent = W > 0 ? '→ effective width: ' + W.toFixed(2) + ' m' : '';
|
||
}
|
||
|
||
if (H > 0 && ceilM > 0) {
|
||
const eff = (H - ceilM / 100).toFixed(2);
|
||
ceilEl.textContent = '→ effective height: ' + eff + ' m';
|
||
} else {
|
||
ceilEl.textContent = H > 0 ? '→ effective height: ' + H.toFixed(2) + ' m' : '';
|
||
}
|
||
}
|
||
|
||
toggleAccordion() {
|
||
const header = document.getElementById('loAccordionToggle');
|
||
const content = document.getElementById('loAccordionContent');
|
||
const isOpen = content.classList.contains('active');
|
||
content.classList.toggle('active');
|
||
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; }
|
||
|
||
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('d-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 × ' + 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) : '–';
|
||
const scM2 = isFinite(sc.rolls) && m2PerRoll > 0 ? Math.round(sc.rolls * m2PerRoll) : '–';
|
||
tr.innerHTML =
|
||
'<td>'+ sc.name +(lim?' ★':'')+'</td>'+
|
||
'<td>'+ (sc.rolls === Infinity ? '–' : sc.rolls) +'</td>'+
|
||
'<td>'+ (isFinite(sc.weight) ? Math.round(sc.weight) : '–') +'</td>'+
|
||
'<td>'+ scM2 +'</td>'+
|
||
'<td>'+ util +'%</td>';
|
||
tbody.appendChild(tr);
|
||
});
|
||
document.getElementById('resultsContainer').classList.add('show');
|
||
}
|
||
|
||
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;
|
||
const marginCeil = res.marginCeiling || 0;
|
||
|
||
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;
|
||
|
||
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>';
|
||
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"/>';
|
||
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"/>';
|
||
|
||
if (mSidePx > 0.5) {
|
||
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"/>';
|
||
}
|
||
if (mCeilPx > 0.5) {
|
||
s += '<rect x="'+cX.toFixed(1)+'" y="'+cY.toFixed(1)+'"'
|
||
+ ' width="'+cW.toFixed(1)+'" height="'+mCeilPx.toFixed(1)+'"'
|
||
+ ' fill="url(#hatchMargin)" opacity="0.9"/>';
|
||
}
|
||
|
||
let drawn = 0;
|
||
for (let layer = 0; layer < layers && drawn < total; layer++) {
|
||
for (let col = 0; col < aW && drawn < total; col++) {
|
||
const cx = cX + col * rD + rD / 2;
|
||
const cy = cY + cH - layer * rD - rD / 2 - 5;
|
||
const r = Math.max(1, rD / 2 - 1.0);
|
||
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)"/>';
|
||
s += '<circle cx="'+cx.toFixed(1)+'" cy="'+cy.toFixed(1)+'" r="'+r.toFixed(1)+'" fill="#a8d4f5" stroke="#2BAC70" stroke-width="1.2"/>';
|
||
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="#1F5438" stroke-width="1" opacity="0.8"/>';
|
||
}
|
||
drawn++;
|
||
}
|
||
}
|
||
|
||
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 × '+layers+' high × '+aL+' deep · '+total+' rolls</text>';
|
||
|
||
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="#2BAC70" 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="#2BAC70">⌀ '+(D_m*1000).toFixed(0)+' mm</text>';
|
||
}
|
||
|
||
svg.setAttribute('viewBox', '0 0 800 300');
|
||
svg.innerHTML = s;
|
||
section.classList.add('show');
|
||
}
|
||
|
||
resetForm() {
|
||
['customLength','customWidth','customHeight','customMaxWeight'].forEach(id =>
|
||
document.getElementById(id).value = ''
|
||
);
|
||
document.getElementById('loMarginSide').value = '0';
|
||
document.getElementById('loMarginCeiling').value = '20';
|
||
document.getElementById('loMarginSideEffective').textContent = '';
|
||
document.getElementById('loMarginCeilingEffective').textContent = '';
|
||
document.getElementById('resultsContainer').classList.remove('show');
|
||
document.getElementById('sideViewSection').classList.remove('show');
|
||
this.clearPresets();
|
||
}
|
||
}
|
||
|
||
// =====================================================
|
||
// INIT
|
||
// =====================================================
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
window.loadOptimizerUI = new LoadOptimizerUI();
|
||
});
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|