Files
RollCalculator/single html/roll_calculator_v9.html

1631 lines
59 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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;
}
@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; }
}
/* ===== 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; }
.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; }
.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; }
</style>
</head>
<body>
<!-- ============================================================
DISCLAIMER MODAL
============================================================ -->
<div id="disclaimerOverlay">
<div id="disclaimerBox">
<div class="disclaimer-badge">⚠ BETA VERSION · INTERNAL USE ONLY</div>
<h2>Roll Diameter Calculator — Naue GmbH &amp; Co. KG</h2>
<div class="disclaimer-section">
<h4>🔒 Authorized Departments Only / Nur autorisierte Abteilungen</h4>
This tool is exclusively intended for internal use by:<br>
<strong>Production Planning · Logistics · Product Management</strong><br><br>
Dieses Tool ist ausschließlich für den internen Gebrauch in:<br>
<strong>Produktionsplanung · Logistik · Produktmanagement</strong>
</div>
<div class="disclaimer-section">
<h4>🧪 Beta-Status</h4>
This application is in Beta. Results should be verified before operational use.<br>
Diese Anwendung befindet sich im Beta-Stadium. Ergebnisse sind vor dem produktiven Einsatz zu überprüfen.
</div>
<div class="disclaimer-section">
<h4>📊 Product Thickness Values</h4>
Pre-filled product thickness values are derived from an automated data evaluation and serve as a starting point only.
They must be independently verified before use in any calculation.<br>
Die vorgeschlagenen Produktdicken stammen aus einer automatischen Datenauswertung und müssen vor Verwendung in Berechnungen eigenständig überprüft werden.
</div>
<div class="disclaimer-confirm">
<input type="checkbox" id="confirmCheck">
<label for="confirmCheck">
Ich bestätige, dass ich zur Nutzung dieses Tools berechtigt bin und die obigen Hinweise gelesen und verstanden habe.<br>
<em>I confirm that I am authorised to use this tool and have read and understood the notices above.</em>
</label>
</div>
<div class="disclaimer-buttons">
<button id="btnCancel">✕ Abbrechen / Cancel</button>
<button id="btnAccept" disabled>✓ Akzeptieren &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 · Roll Diameter · Product Length · Roll Weight</div>
</div>
</div>
<!-- ============================================================
MAIN
============================================================ -->
<div class="app-main">
<div class="card">
<div class="card-header">
<h2>🧮 Roll Diameter Calculator</h2>
</div>
<div class="card-body">
<!-- TABS -->
<div class="tab-bar">
<button class="tab-btn active" data-tab="direct">🧮 Direct Calculation</button>
<button class="tab-btn" data-tab="extrap">📐 Extrapolation</button>
</div>
<!-- ===================== TAB: DIRECT CALCULATION ===================== -->
<div class="tab-pane active" id="tab-direct">
<!-- Product Category + Site -->
<div class="form-row">
<div class="form-group">
<label>Product Category</label>
<select id="d-cat">
<option value="">— Select —</option>
<option value="bentofix">Bentofix</option>
<option value="secutex">Secutex</option>
<option value="carbofol">Carbofol</option>
<option value="secumat">Secumat</option>
<option value="secudrain">Secudrain</option>
</select>
</div>
<div class="form-group">
<label>Production Site</label>
<select id="d-site">
<option value="">— Select —</option>
<option value="espelkamp">Espelkamp</option>
<option value="malaysia">Malaysia</option>
</select>
</div>
</div>
<!-- Calculation Mode -->
<div class="form-group" style="margin-bottom:14px;">
<label>Calculation Mode</label>
<div class="mode-selector">
<button class="mode-btn active" data-mode="diameter">→ Roll Diameter</button>
<button class="mode-btn" data-mode="length">→ Product Length</button>
<button class="mode-btn" data-mode="target">🎯 Target Diameter</button>
<button class="mode-btn" data-mode="thickness">→ Product Thickness</button>
</div>
</div>
<!-- Diagram -->
<div class="diagram-row">
<div class="diagram-svg-wrap">
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="50" fill="#d0e8fa" stroke="#0066cc" stroke-width="2"/>
<circle cx="60" cy="60" r="20" fill="#fff" stroke="#666" stroke-width="2" stroke-dasharray="4,3"/>
<line x1="60" y1="60" x2="110" y2="60" stroke="#0066cc" stroke-width="1.5"/>
<line x1="60" y1="60" x2="80" y2="60" stroke="#666" stroke-width="1.5"/>
<text x="82" y="56" font-size="9" fill="#0066cc">D</text>
<text x="68" y="56" font-size="9" fill="#666">d</text>
</svg>
</div>
<div class="diagram-legend">
<div><strong>D</strong> = Roll outer diameter</div>
<div><strong>d</strong> = Core outer diameter</div>
<div><strong>t</strong> = Product thickness</div>
<div><strong>L</strong> = Product length</div>
</div>
</div>
<hr class="section-sep">
<!-- Input Parameters -->
<h4 style="font-size:13px;color:#555;margin-bottom:14px;">Input Parameters</h4>
<div class="form-row">
<div class="form-group">
<label>Core Outer Diameter (d)</label>
<div class="input-with-unit">
<input type="number" id="d-co" value="150" min="0" step="1">
<span class="unit-label">mm</span>
</div>
<div class="core-presets">
<button class="core-preset-btn" data-core="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">±</span>
<input type="number" id="d-tol" placeholder="mm" min="0" step="0.01" style="width:80px;">
<span class="unit-label">mm</span>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Product Length (L)</label>
<div class="input-with-unit">
<input type="number" id="d-le" placeholder="m" min="0" step="0.1">
<span class="unit-label">m</span>
</div>
</div>
<div class="form-group">
<label>Roll Diameter (D)</label>
<div class="input-with-unit">
<input type="number" id="d-di" placeholder="mm" min="0" step="1">
<span class="unit-label">mm</span>
</div>
</div>
</div>
<!-- Weight (optional) -->
<div class="weight-section">
<h4>Roll Weight (optional)</h4>
<div class="form-row">
<div class="form-group">
<label>Roll Width</label>
<div class="input-with-unit">
<input type="number" id="de-wi" placeholder="m" min="0" step="0.01">
<span class="unit-label">m</span>
</div>
</div>
<div class="form-group">
<label>Area Weight</label>
<div class="input-with-unit">
<input type="number" id="d-aw" placeholder="g/m²" min="0" step="1">
<span class="unit-label">g/m²</span>
</div>
</div>
<div class="form-group">
<label>Calculated Weight</label>
<div class="input-with-unit">
<input type="text" id="d-wo" readonly placeholder="—" style="background:#f0f0f0;">
<span class="unit-label">kg</span>
</div>
</div>
</div>
</div>
<div style="margin-top:18px;">
<button class="btn-calc" id="d-calc">► Calculate</button>
<button class="btn-reset" id="d-reset">Reset</button>
</div>
<!-- Result -->
<div id="d-result" style="display:none; margin-top:20px;">
<div class="result-box">
<div class="result-label" id="d-res-label">Result</div>
<div class="result-value" id="d-res-val">—</div>
<div class="result-range" id="d-res-range"></div>
</div>
</div>
<div class="formula-box">
Geometry: π/4 × (D² − d²) = L × t &nbsp;|&nbsp; Weight: G [kg] = Area Weight [g/m²] × L [m] × Width [m] / 1000
</div>
</div>
<!-- END TAB DIRECT -->
<!-- ===================== TAB: EXTRAPOLATION ===================== -->
<div class="tab-pane" id="tab-extrap">
<h4 style="font-size:13px;color:#555;margin-bottom:14px;">Known Reference Roll</h4>
<div class="form-row">
<div class="form-group">
<label>Article Number (optional)</label>
<input type="text" id="e-art" list="e-art-list" placeholder="Search article no. or name..." autocomplete="off" style="width:100%;">
<datalist id="e-art-list"></datalist>
</div>
<div class="form-group">
<label>Core Outer Diameter (d)</label>
<div class="input-with-unit">
<input type="number" id="e-co" value="150" min="0" step="1">
<span class="unit-label">mm</span>
</div>
<div class="core-presets">
<button class="core-preset-btn" data-core="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₀)</label>
<div class="input-with-unit">
<input type="number" id="e-d0" placeholder="mm" min="0" step="1">
<span class="unit-label">mm</span>
</div>
</div>
<div class="form-group">
<label>Known Roll Length (L₀)</label>
<div class="input-with-unit">
<input type="number" id="e-l0" placeholder="m" min="0" step="0.1">
<span class="unit-label">m</span>
</div>
</div>
</div>
<hr class="section-sep">
<h4 style="font-size:13px;color:#555;margin-bottom:14px;">New Roll Length</h4>
<div class="form-row">
<div class="form-group">
<label>New Roll Length (L₁)</label>
<div class="input-with-unit">
<input type="number" id="e-l1" placeholder="m" min="0" step="0.1">
<span class="unit-label">m</span>
</div>
</div>
<div class="form-group">
<label>Tolerance (optional)</label>
<div class="tol-row">
<span class="unit-label">±</span>
<input type="number" id="e-tol" placeholder="mm" min="0" step="0.1" style="width:100px;">
<span class="unit-label">mm</span>
</div>
</div>
</div>
<!-- Weight (optional) -->
<div class="weight-section">
<h4>Roll Weight (optional)</h4>
<div class="form-row">
<div class="form-group">
<label>Roll Width</label>
<div class="input-with-unit">
<input type="number" id="e-wi" placeholder="m" min="0" step="0.01">
<span class="unit-label">m</span>
</div>
</div>
<div class="form-group">
<label>Area Weight</label>
<div class="input-with-unit">
<input type="number" id="e-aw" placeholder="g/m²" min="0" step="1">
<span class="unit-label">g/m²</span>
</div>
</div>
</div>
</div>
<div style="margin-top:18px;">
<button class="btn-calc" id="e-calc">► Calculate Roll Diameter</button>
<button class="btn-reset" id="e-reset">Reset</button>
</div>
<!-- Result -->
<div id="e-result" style="display:none; margin-top:20px;">
<div class="result-box">
<div class="result-label">Extrapolated Roll Diameter (D₁)</div>
<div class="result-value" id="e-res-val">—</div>
<div class="result-range" id="e-res-range"></div>
</div>
</div>
<div class="formula-box">
Geometry: D₁ = √(d² + L₁/L₀ × (D₀² − d²)) &nbsp;|&nbsp; Weight: G [kg] = Area Weight × L₁ × Width / 1000
</div>
</div>
<!-- END TAB EXTRAP -->
</div><!-- card-body -->
</div><!-- card -->
<!-- INFO NOTE -->
<div class="info-note">
<strong>ⓘ Calculation Notes</strong><br>
The calculated roll diameters assume a perfectly cylindrical roll with uniform, gap-free winding.<br><br>
The product thickness can vary during production within the permitted manufacturing tolerance.
The actual roll diameter may therefore differ from the calculated values.<br><br>
Use the <em>± Tolerance</em> field next to the product thickness to estimate this effect — the result will then show the minimum, nominal and maximum roll diameter.
</div>
<!-- ============================================================
LOAD OPTIMIZER SECTION
============================================================ -->
<div id="loadOptimizerContainer" class="load-optimizer-section">
<div class="lo-accordion-header" id="loAccordionToggle">
<span class="lo-accordion-icon">▼</span>
<h3>🎯 Load Optimizer — Transport Capacity Calculator</h3>
<span class="badge-beta-lo">BETA</span>
</div>
<div class="lo-accordion-content" id="loAccordionContent" style="display:none;">
<div class="lo-warning">
⚠ The Load Optimizer uses roll data from the <strong>Direct Calculation</strong> tab above.
Please complete a roll calculation first (Roll Diameter + Roll Width + Roll Weight must be filled).
</div>
<div class="lo-inputs">
<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 &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">► Calculate</button>
<button class="lo-btn-secondary" id="resetLoad">↺ Reset</button>
</div>
</div>
<div class="lo-results" id="resultsContainer" style="display:none;">
<div class="lo-results-summary">
<h4>Results:</h4>
<div class="lo-summary-item">
<span><strong>Limiting Factor:</strong></span>
<span id="limitingFactorValue" class="lo-value-highlight">—</span>
</div>
<div class="lo-summary-item">
<span><strong>Total Rolls:</strong></span>
<div style="text-align:right;">
<span id="totalRollsValue" class="lo-value-highlight">—</span>
<small id="rollsDetailValue" style="display:block;color:#888;"></small>
</div>
</div>
<div class="lo-summary-item">
<span><strong>Utilization:</strong></span>
<div class="lo-util-group">
<div>Weight: <span id="weightUtilValue" class="lo-value-number">—</span> %</div>
<div>Volume: <span id="volumeUtilValue" class="lo-value-number">—</span> %</div>
<div>Product: <span id="productM2Value" class="lo-value-number">—</span> m²</div>
</div>
</div>
</div>
<div class="lo-results-table">
<h4>Comparison Table:</h4>
<table id="comparisonTable">
<thead>
<tr>
<th>Scenario</th>
<th>Rolls</th>
<th>kg</th>
<th>m²</th>
<th>Util. %</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div class="lo-sideview-section" id="sideViewSection" style="display:none;">
<h4>📐 Front View — Cross-Section</h4>
<div class="lo-sideview-wrap">
<svg id="sideViewSVG" width="100%" viewBox="0 0 800 300" xmlns="http://www.w3.org/2000/svg">
<text x="400" y="130" text-anchor="middle" fill="#aaa" font-size="14">Run a calculation to see the side view.</text>
</svg>
</div>
<div class="lo-sideview-legend">
<span class="lo-legend-item"><span class="lo-legend-box roll-color"></span> Roll (cross-section, Ø = diameter)</span>
<span class="lo-legend-item"><span class="lo-legend-box container-color"></span> Container / Vehicle</span>
</div>
</div>
</div>
</div>
</div>
<!-- END LOAD OPTIMIZER -->
</div><!-- app-main -->
<div class="app-footer">
■ Naue GmbH &amp; Co. KG &nbsp;·&nbsp; Roll Diameter Calculator &nbsp;·&nbsp; Internal Application
</div>
<!-- ============================================================
ARTICLE DATA (JSON)
============================================================ -->
<script>
window.ARTICLE_DATA = [];
// Load article data from external JSON
(async function() {
try {
const response = await fetch('article-data.json');
if (response.ok) {
window.ARTICLE_DATA = await response.json();
console.log('Article data loaded:', window.ARTICLE_DATA.length, 'items');
// Populate dropdowns
populateArticleDropdowns();
} else {
console.error('Failed to load article data:', response.status);
}
} catch (error) {
console.error('Error loading article data:', error);
}
})();
</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
};
}
calculateLoadUtilization(dim, maxLoadWeight) {
const { rollDiameter, coreDiameter, rollWidth, rollWeight, m2PerRoll } = this.state;
const D = rollDiameter / 1000, d = coreDiameter / 1000;
// Rollen liegen auf der Seite: Länge der Rolle = rollWidth, Durchmesser = D
const rollsAlongLength = Math.floor(dim.length / rollWidth); // entlang Containerlänge
const rollsAlongWidth = Math.floor(dim.width / D); // quer (Durchmesser)
const rollsPerLayer = rollsAlongLength * rollsAlongWidth;
const maxStackHeight = Math.floor(dim.height / D); // Stapellagen
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 maxByVolume = volPerRoll > 0 ? Math.floor(containerVol / 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
};
}
fmt(n, dec=1) { return isFinite(n) ? n.toFixed(dec) : '—'; }
}
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());
}
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;
}
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; }
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;
}
this.optimizer.setRollData(rollData);
const res = this.optimizer.calculateLoadUtilization({ length:L, width:W, height:H }, MW);
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 × ' + 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').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 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;
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 += '</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"/>';
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="#0066cc" 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="#0052a3" 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 \u00d7 '+layers+' high \u00d7 '+aL+' deep \u00b7 '+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="#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 = ''
);
document.getElementById('resultsContainer').style.display = 'none';
document.getElementById('sideViewSection').style.display = 'none';
this.clearPresets();
}
}
</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 + ' – ' + 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 + ' –') || val === a.nr; });
if (match) {
document.getElementById('d-th').value = match.thickness;
}
});
}
// Auto-fill Dicke bei Artikelauswahl (e-art) - kein Dicken-Feld im Extrap-Tab
// (kann spaeter ergaenzt werden)
// 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');
// Weight calc helper
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);
}
return 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 = '';
}
// write result back to d-di for Load Optimizer
document.getElementById('d-di').value = D_nom.toFixed(1);
calcWeight(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);
calcWeight(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);
calcWeight(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);
calcWeight(L_m);
}
resBox.style.display = 'block';
});
// --- 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').selectedIndex = 0;
document.getElementById('d-result').style.display = 'none';
});
// --- 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₀, L₀ and L₁.'); 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 = '';
}
const wi = parseFloat(document.getElementById('e-wi').value) || 0;
const aw = parseFloat(document.getElementById('e-aw').value) || 0;
if (wi > 0 && aw > 0) {
// no weight output field on extrap tab in this version
}
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', () => {
// Populate wird jetzt async von der ARTICLE_DATA fetch() aufgerufen
// Falls die Daten schnell geladen sind, wird es dort aufgerufen
// Falls DOMContentLoaded vor dem fetch fertig ist, rufen wir es hier auf:
if (window.ARTICLE_DATA && window.ARTICLE_DATA.length > 0) {
populateArticleDropdowns();
}
window.loadOptimizerUI = new LoadOptimizerUI();
});
</script>
<!-- ============================================================
SERVICE WORKER REGISTRATION
============================================================ -->
<!-- Offline Indicator -->
<div id="offlineIndicator" style="display:none; position:fixed; top:0; left:0; right:0; background:#ff6600; color:#fff; padding:10px; text-align:center; font-weight:bold; z-index:10000;">
📡 You are offline - using cached data
</div>
<script>
window.addEventListener('offline', () => {
document.getElementById('offlineIndicator').style.display = 'block';
console.log('[App] Offline mode activated');
});
window.addEventListener('online', () => {
document.getElementById('offlineIndicator').style.display = 'none';
console.log('[App] Online mode activated');
});
// Check initial state
if (!navigator.onLine) {
document.getElementById('offlineIndicator').style.display = 'block';
}
</script>
<script>
// Register Service Worker für Offline-Funktionalität
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);
});
});
} else {
console.warn('[App] ServiceWorker not supported in this browser');
}
</script>
</body>
</html>