Files
RollCalculator/single html/roll_calculator_v13.html

1906 lines
70 KiB
HTML

<!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">&#9888; BETA VERSION &middot; INTERNAL USE ONLY</div>
<h2>Roll Diameter Calculator &ndash; Naue GmbH &amp; Co. KG</h2>
<div class="disclaimer-section">
<h4>&#128274; Authorized Departments Only / Nur autorisierte Abteilungen</h4>
This tool is exclusively intended for internal use by:<br>
<strong>Production Planning &middot; Logistics &middot; Product Management</strong><br><br>
Dieses Tool ist ausschlie&szlig;lich f&uuml;r den internen Gebrauch in:<br>
<strong>Produktionsplanung &middot; Logistik &middot; Produktmanagement</strong>
</div>
<div class="disclaimer-section">
<h4>&#129514; 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 &uuml;berpr&uuml;fen.
</div>
<div class="disclaimer-section">
<h4>&#128202; 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&uuml;ssen vor Verwendung in Berechnungen eigenst&auml;ndig &uuml;berpr&uuml;ft werden.
</div>
<div class="disclaimer-confirm">
<input type="checkbox" id="confirmCheck">
<label for="confirmCheck">
Ich best&auml;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">&#10005; Abbrechen / Cancel</button>
<button id="btnAccept" disabled>&#10003; Akzeptieren &amp; Weiter / Accept &amp; 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 &middot; Roll Diameter &middot; Product Length &middot; Roll Weight</div>
</div>
</div>
<!-- ============================================================
MAIN
============================================================ -->
<div class="app-main">
<div class="card">
<div class="card-header">
<h2>&#9998; Roll Diameter Calculator</h2>
</div>
<div class="card-body">
<!-- TABS -->
<div class="tab-bar">
<button class="tab-btn active" data-tab="direct">&#9998; Direct Calculation</button>
<button class="tab-btn" data-tab="extrap">&#128269; 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="">&ndash; Select &ndash;</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="">&ndash; Select &ndash;</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">&rarr; Roll Diameter</button>
<button class="mode-btn" data-mode="length">&rarr; Product Length</button>
<button class="mode-btn" data-mode="target">&#127919; Target Diameter</button>
<button class="mode-btn" data-mode="thickness">&rarr; 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="76">76</button>
<button class="core-preset-btn" data-core="150">150</button>
<button class="core-preset-btn" data-core="168">168</button>
<button class="core-preset-btn" data-core="200">200</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">&plusmn;</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&sup2;" min="0" step="1">
<span class="unit-label">g/m&sup2;</span>
</div>
</div>
<div class="form-group">
<label>Calculated Weight</label>
<div class="input-with-unit">
<input type="text" id="d-wo" readonly placeholder="&ndash;" 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">&#9654; 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">&ndash;</div>
<div class="result-range" id="d-res-range"></div>
</div>
</div>
<div class="formula-box">
Geometry: &pi;/4 &times; (D&sup2; &minus; d&sup2;) = L &times; t &nbsp;|&nbsp; Weight: G [kg] = Area Weight [g/m&sup2;] &times; L [m] &times; 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="76">76</button>
<button class="core-preset-btn" data-core="150">150</button>
<button class="core-preset-btn" data-core="168">168</button>
<button class="core-preset-btn" data-core="200">200</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&#8320;)</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&#8320;)</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&#8321;)</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">&plusmn;</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&sup2;" min="0" step="1">
<span class="unit-label">g/m&sup2;</span>
</div>
</div>
</div>
</div>
<div style="margin-top:18px;">
<button class="btn-calc" id="e-calc">&#9654; 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&#8321;)</div>
<div class="result-value" id="e-res-val">&ndash;</div>
<div class="result-range" id="e-res-range"></div>
</div>
</div>
<div class="formula-box">
Geometry: D&#8321; = &radic;(d&sup2; + L&#8321;/L&#8320; &times; (D&#8320;&sup2; &minus; d&sup2;)) &nbsp;|&nbsp; Weight: G [kg] = Area Weight &times; L&#8321; &times; Width / 1000
</div>
</div>
<!-- END TAB EXTRAP -->
</div><!-- card-body -->
</div><!-- card -->
<!-- INFO NOTE -->
<div class="info-note">
<strong>&#9432; 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>&plusmn; Tolerance</em> field next to the product thickness to estimate this effect &ndash; 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">&#9660;</span>
<h3>&#127919; Load Optimizer &ndash; 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">
&#9888; 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>&#9881;&#65039; 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>&#128280; 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>&#128207; Dimensions &amp; 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">&#9654; Calculate</button>
<button class="lo-btn-secondary" id="resetLoad">&#8634; 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">&ndash;</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">&ndash;</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">&ndash;</span> %</div>
<div>Volume: <span id="volumeUtilValue" class="lo-value-number">&ndash;</span> %</div>
<div>Product: <span id="productM2Value" class="lo-value-number">&ndash;</span> m&sup2;</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&sup2;</th>
<th>Util. %</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="lo-sideview-section" id="sideViewSection" style="display:none;">
<h4>&#128269; Front View &ndash; 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, &Oslash; = 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">
&#9632; Naue GmbH &amp; Co. KG &nbsp;&middot;&nbsp; Roll Diameter Calculator &nbsp;&middot;&nbsp; Internal Application
</div>
<!-- ============================================================
ARTICLE DATA (JSON)
============================================================ -->
<script>
window.ARTICLE_DATA = [];
window.APP_CONFIG = {};
(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 loaded'); }
else { console.warn('config.json not found - forklift checks disabled.'); }
} 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 = '&#128683; ' + 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 = '&#9888;&#65039; ' + msgs.warning_title;
const ci = coreOk
? '<li class="fc-req-ok">&#9989; ' + msgs.core_ok + '</li>'
: '<li class="fc-req-nok">&#10060; ' + msgs.core_nok.replace('{core}', coreMm) + '</li>';
bodyEl.innerHTML = '<div>' + msgs.requirements_label + '</div><ul>' + ci +
'<li>&#128295; ' + msgs.req_mandrel + '</li>' +
'<li>&#128668; ' + msgs.req_forklift + '</li></ul>';
return true;
}
box.className = 'forklift-check fc-ok';
titleEl.innerHTML = '&#9989; Forklift Check &ndash; OK';
bodyEl.innerHTML = 'Roll weight <strong>' + weightKg.toFixed(1) +
' kg</strong> is within the standard limit (&lt;' + 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) return window.ForkliftChecker.evaluate(weight || 0, core, category);
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;">
&#128225; 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); });
});
}
</script>
</body>
</html>