Spaces:
Running
Running

You are "Artisan Forge," a master digital craftsman and mechanical engineer specializing in metal fabrication. You are creating a comprehensive, interactive, and visually stunning web-based calculator for tube and pipe bending. The tool must be professional, intuitive, and powerful enough for both seasoned fabricators and engineering students. It should not only calculate but also *teach* and *visualize* the concepts. **Core Objective:** Design a single-page web application using HTML, CSS, and JavaScript. The page must include all features below in a clean, organized, and responsive layout. **Feature Set (Implement in Detail):** **1. Input Section (The Anvil):** * Create a form with clearly labeled input fields with appropriate units: * **Outer Diameter (OD):** (mm or in) - dropdown for unit selection. * **Wall Thickness (WT):** (mm or in) * **Bend Radius (CLR):** (mm or in) - Note: This is the Centerline Radius. * **Bend Angle (α):** (degrees) - Slider input (0° to 180°) alongside numeric input. * **Material:** Dropdown menu (e.g., Mild Steel, Stainless Steel 304, Aluminum 6061-T6, Copper). Each material must have a predefined **Tensile Strength (TS)** and **Modulus of Elasticity (E)** stored in a JavaScript object. * **K-Factor Input:** Auto-calculated but with an option for a user-defined "Expert Override" input field. **2. Calculated Results (The Measurements):** * Calculate and display the following results clearly: * **Bend Allowance (BA):** The length of the neutral axis within the bend. * **Bend Deduction (BD):** The difference between the sum of the flange lengths and the initial flat length. * **Elongation (%):** The percentage of stretching on the outer fiber. `Elongation % = ((OD / (2 * CLR)) * 100)`. **CRITICAL:** Compare this value to the material's typical maximum elongation (e.g., Mild Steel ~20-30%). Implement a color-coded system (Green/Yellow/Red) to warn users of potential cracking. * **Bending Force (F):** Calculate the force required for air bending (in tons). `F = (k * TS * width * thickness^2) / (die_width)`. Assume `k=1.33` for a air bending and provide tooltips explaining the variables. Let the user input `Die Width` or provide a reasonable default based on material and thickness. **3. K-Factor Visualization (The Soul of the Bend):** * This is the **"special" visual element**. Create an interactive SVG or Canvas graphic. * Draw a cross-section of the tube being bent. * Clearly label the Neutral Axis, Inside of Bend (Compression), and Outside of Bend (Tension). * The K-Factor is the ratio `(t - distance from inside face to neutral axis) / t`. Visually represent this ratio with a dynamic bar or a movable line that adjusts as the user changes inputs (especially Thickness and Radius). * **Animation:** When the user inputs a new Bend Radius, animate the graphic to show the tube bending and the neutral axis shifting inward. The shift should be proportional to the calculated K-Factor. A smaller radius should show a more significant shift. **4. Safety Factor & Warnings (The Guardian):** * This is the **"sense"** feature for practicality and safety. * Based on the calculated **Elongation %** and the selected material's known ductility limits, display a clear warning message. * *Green Checkmark:* "Elongation within safe limits." * *Yellow Exclamation:* "Approaching material limits. Consider a larger bend radius." * *Red X:* "Warning! Elongation exceeds typical material limits. High risk of cracking or failure." * For the **Bending Force,** compare the calculated tonnage to a range of common press brake capacities (e.g., 20-ton, 50-ton, 100-ton). Display a message: "Required force: X tons. Suitable for a [e.g., 50-ton] press brake." **5. Flange Length Calculator (The Practicality):** * Allow users to input their desired **Leg Length A** and **Leg Length B** (the straight sections on either side of the bend). * Calculate and output the **Total Flat Length** required before bending: `Flat_Length = Leg_A + Leg_B + BD - (2 * CLR)`. **UI/UX & Design (The Aesthetic):** * **Theme:** Use a dark theme interface reminiscent of a modern CNC machine control panel (dark slate gray background, light blue/green text for highlights, amber for warnings). * **Layout:** Use a grid layout. Left column for inputs, center column for results and warnings, right column for the K-Factor visualization. * **Interactivity:** Use real-time calculation. Results update instantly as the user types or moves sliders. * **Tooltips:** Add small (i) icons next to every technical term (K-Factor, BA, BD, CLR). On hover, show a brief, simple explanation. **JavaScript Logic:** * Pre-define material properties in an object: `materials = { "Mild Steel": { tensileStrength: 400, modulus: 210000, maxElongation: 22 }, ... }` * Write functions for each calculation: `calculateKFactor()`, `calculateBA()`, `calculateBD()`, `calculateElongation()`, `calculateForce()`. * Use event listeners (`oninput`, `onchange`) on all form elements to trigger a `updateCalculations()` function that runs all other functions and updates the DOM. **Final Instruction:** Output the complete, ready-to-run code in a single code block. Include all HTML structure, CSS styling, and JavaScript logic. The code must be clean, well-commented, and functional upon execution. - Initial Deployment
0067887
verified
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Artisan Forge | Tube & Pipe Bending Calculator</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
/* Custom styles for the CNC-like interface */ | |
.cnc-panel { | |
background-color: #1a202c; | |
color: #e2e8f0; | |
border: 1px solid #2d3748; | |
box-shadow: 0 0 15px rgba(16, 185, 129, 0.2); | |
} | |
.cnc-button { | |
background-color: #2d3748; | |
color: #81e6d9; | |
border: 1px solid #4a5568; | |
transition: all 0.3s ease; | |
} | |
.cnc-button:hover { | |
background-color: #4a5568; | |
color: #b2f5ea; | |
} | |
.input-highlight { | |
border-color: #38b2ac; | |
background-color: #2d3748; | |
} | |
.tooltip { | |
position: relative; | |
display: inline-block; | |
} | |
.tooltip .tooltiptext { | |
visibility: hidden; | |
width: 200px; | |
background-color: #2d3748; | |
color: #e2e8f0; | |
text-align: center; | |
border-radius: 6px; | |
padding: 5px; | |
position: absolute; | |
z-index: 1; | |
bottom: 125%; | |
left: 50%; | |
margin-left: -100px; | |
opacity: 0; | |
transition: opacity 0.3s; | |
border: 1px solid #4a5568; | |
font-size: 0.8rem; | |
} | |
.tooltip:hover .tooltiptext { | |
visibility: visible; | |
opacity: 1; | |
} | |
#kFactorVisualization { | |
background-color: #2d3748; | |
border: 1px solid #4a5568; | |
border-radius: 0.375rem; | |
} | |
.safe { | |
color: #68d391; | |
} | |
.warning { | |
color: #f6e05e; | |
} | |
.danger { | |
color: #fc8181; | |
} | |
.slidecontainer { | |
width: 100%; | |
} | |
.slider { | |
-webkit-appearance: none; | |
width: 100%; | |
height: 8px; | |
border-radius: 5px; | |
background: #4a5568; | |
outline: none; | |
} | |
.slider::-webkit-slider-thumb { | |
-webkit-appearance: none; | |
appearance: none; | |
width: 18px; | |
height: 18px; | |
border-radius: 50%; | |
background: #38b2ac; | |
cursor: pointer; | |
} | |
.slider::-moz-range-thumb { | |
width: 18px; | |
height: 18px; | |
border-radius: 50%; | |
background: #38b2ac; | |
cursor: pointer; | |
} | |
/* Animation for the bend visualization */ | |
@keyframes bendAnimation { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(var(--bend-angle)); } | |
} | |
.bend-animation { | |
transform-origin: left center; | |
animation: bendAnimation 1s ease-out forwards; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-gray-200 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="text-center mb-8"> | |
<h1 class="text-4xl font-bold text-teal-400 mb-2">Artisan Forge</h1> | |
<h2 class="text-2xl text-teal-300">Tube & Pipe Bending Calculator</h2> | |
<p class="text-gray-400 mt-2">Precision calculations for professional metal fabrication</p> | |
</header> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<!-- Input Section (Left Column) --> | |
<div class="cnc-panel p-6 rounded-lg"> | |
<h3 class="text-xl font-semibold text-teal-300 mb-4 border-b border-gray-700 pb-2"> | |
<i class="fas fa-tools mr-2"></i>Bend Parameters | |
</h3> | |
<div class="space-y-4"> | |
<!-- Unit Selection --> | |
<div class="flex items-center space-x-4 mb-4"> | |
<div> | |
<label class="block text-sm font-medium mb-1">Units</label> | |
<select id="unitSystem" class="cnc-button rounded px-3 py-2 w-full"> | |
<option value="metric">Metric (mm)</option> | |
<option value="imperial">Imperial (in)</option> | |
</select> | |
</div> | |
</div> | |
<!-- Outer Diameter --> | |
<div> | |
<label for="outerDiameter" class="block text-sm font-medium mb-1 flex items-center"> | |
Outer Diameter (OD) | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">The external diameter of the tube or pipe</span> | |
</span> | |
</label> | |
<div class="flex"> | |
<input type="number" id="outerDiameter" min="0" step="0.01" | |
class="input-highlight rounded-l px-3 py-2 w-full focus:outline-none focus:ring-1 focus:ring-teal-500"> | |
<span id="odUnit" class="cnc-button rounded-r px-3 py-2">mm</span> | |
</div> | |
</div> | |
<!-- Wall Thickness --> | |
<div> | |
<label for="wallThickness" class="block text-sm font-medium mb-1 flex items-center"> | |
Wall Thickness (WT) | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Thickness of the tube/pipe wall</span> | |
</span> | |
</label> | |
<div class="flex"> | |
<input type="number" id="wallThickness" min="0" step="0.01" | |
class="input-highlight rounded-l px-3 py-2 w-full focus:outline-none focus:ring-1 focus:ring-teal-500"> | |
<span id="wtUnit" class="cnc-button rounded-r px-3 py-2">mm</span> | |
</div> | |
</div> | |
<!-- Bend Radius --> | |
<div> | |
<label for="bendRadius" class="block text-sm font-medium mb-1 flex items-center"> | |
Bend Radius (CLR) | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Centerline Radius - distance from bend center to tube centerline</span> | |
</span> | |
</label> | |
<div class="flex"> | |
<input type="number" id="bendRadius" min="0" step="0.01" | |
class="input-highlight rounded-l px-3 py-2 w-full focus:outline-none focus:ring-1 focus:ring-teal-500"> | |
<span id="clrUnit" class="cnc-button rounded-r px-3 py-2">mm</span> | |
</div> | |
</div> | |
<!-- Bend Angle --> | |
<div> | |
<label for="bendAngle" class="block text-sm font-medium mb-1 flex items-center"> | |
Bend Angle (α) | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Angle of the bend in degrees (0-180°)</span> | |
</span> | |
</label> | |
<div class="slidecontainer mb-2"> | |
<input type="range" min="0" max="180" value="90" class="slider" id="bendAngleSlider"> | |
</div> | |
<div class="flex"> | |
<input type="number" id="bendAngle" min="0" max="180" step="1" | |
class="input-highlight rounded-l px-3 py-2 w-full focus:outline-none focus:ring-1 focus:ring-teal-500"> | |
<span class="cnc-button rounded-r px-3 py-2">°</span> | |
</div> | |
</div> | |
<!-- Material Selection --> | |
<div> | |
<label for="material" class="block text-sm font-medium mb-1 flex items-center"> | |
Material | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Material affects elongation limits and bending force</span> | |
</span> | |
</label> | |
<select id="material" class="cnc-button rounded px-3 py-2 w-full"> | |
<option value="Mild Steel">Mild Steel</option> | |
<option value="Stainless Steel 304">Stainless Steel 304</option> | |
<option value="Aluminum 6061-T6">Aluminum 6061-T6</option> | |
<option value="Copper">Copper</option> | |
<option value="Brass">Brass</option> | |
</select> | |
</div> | |
<!-- Die Width --> | |
<div> | |
<label for="dieWidth" class="block text-sm font-medium mb-1 flex items-center"> | |
Die Width | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Width of the die opening in the press brake</span> | |
</span> | |
</label> | |
<div class="flex"> | |
<input type="number" id="dieWidth" min="0" step="0.1" value="30" | |
class="input-highlight rounded-l px-3 py-2 w-full focus:outline-none focus:ring-1 focus:ring-teal-500"> | |
<span id="dieWidthUnit" class="cnc-button rounded-r px-3 py-2">mm</span> | |
</div> | |
</div> | |
<!-- K-Factor --> | |
<div> | |
<label for="kFactor" class="block text-sm font-medium mb-1 flex items-center"> | |
K-Factor | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Ratio of neutral axis position to material thickness</span> | |
</span> | |
</label> | |
<div class="flex items-center"> | |
<input type="number" id="kFactor" min="0" max="1" step="0.001" | |
class="input-highlight rounded-l px-3 py-2 w-full focus:outline-none focus:ring-1 focus:ring-teal-500"> | |
<button id="kFactorLock" class="cnc-button px-3 py-2 border-l-0 rounded-r"> | |
<i class="fas fa-lock"></i> | |
</button> | |
</div> | |
<div class="text-xs text-gray-400 mt-1">Auto-calculated. Click lock to override.</div> | |
</div> | |
<!-- Flange Lengths --> | |
<div class="pt-4 border-t border-gray-700"> | |
<h4 class="text-sm font-medium mb-2 text-teal-300">Flange Lengths (Optional)</h4> | |
<div class="grid grid-cols-2 gap-4"> | |
<div> | |
<label for="legLengthA" class="block text-xs font-medium mb-1">Leg Length A</label> | |
<div class="flex"> | |
<input type="number" id="legLengthA" min="0" step="1" | |
class="input-highlight rounded-l px-3 py-2 w-full focus:outline-none focus:ring-1 focus:ring-teal-500"> | |
<span id="flangeUnit" class="cnc-button rounded-r px-3 py-2">mm</span> | |
</div> | |
</div> | |
<div> | |
<label for="legLengthB" class="block text-xs font-medium mb-1">Leg Length B</label> | |
<div class="flex"> | |
<input type="number" id="legLengthB" min="0" step="1" | |
class="input-highlight rounded-l px-3 py-2 w-full focus:outline-none focus:ring-1 focus:ring-teal-500"> | |
<span class="cnc-button rounded-r px-3 py-2">mm</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Results Section (Middle Column) --> | |
<div class="cnc-panel p-6 rounded-lg"> | |
<h3 class="text-xl font-semibold text-teal-300 mb-4 border-b border-gray-700 pb-2"> | |
<i class="fas fa-calculator mr-2"></i>Calculated Results | |
</h3> | |
<div class="space-y-6"> | |
<!-- Bend Allowance --> | |
<div class="bg-gray-800 p-4 rounded-lg"> | |
<div class="flex justify-between items-center mb-2"> | |
<h4 class="font-medium flex items-center"> | |
Bend Allowance (BA) | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Length of the neutral axis within the bend</span> | |
</span> | |
</h4> | |
<div class="text-lg font-mono" id="bendAllowance">0.00</div> | |
</div> | |
<div class="text-xs text-gray-400">BA = α × (π/180) × (CLR + K×t)</div> | |
</div> | |
<!-- Bend Deduction --> | |
<div class="bg-gray-800 p-4 rounded-lg"> | |
<div class="flex justify-between items-center mb-2"> | |
<h4 class="font-medium flex items-center"> | |
Bend Deduction (BD) | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Difference between flange lengths and flat length</span> | |
</span> | |
</h4> | |
<div class="text-lg font-mono" id="bendDeduction">0.00</div> | |
</div> | |
<div class="text-xs text-gray-400">BD = 2 × (CLR + t) × tan(α/2) - BA</div> | |
</div> | |
<!-- Elongation Warning --> | |
<div class="bg-gray-800 p-4 rounded-lg" id="elongationContainer"> | |
<div class="flex justify-between items-center mb-2"> | |
<h4 class="font-medium flex items-center"> | |
Elongation | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Percentage of stretching on the outer fiber</span> | |
</span> | |
</h4> | |
<div class="text-lg font-mono" id="elongation">0.00</div> | |
</div> | |
<div class="text-xs text-gray-400 mb-2">Elongation % = (OD / (2 × CLR)) × 100</div> | |
<div class="flex items-center" id="elongationWarning"> | |
<i class="fas fa-check-circle mr-2 safe"></i> | |
<span class="text-sm safe">Elongation within safe limits</span> | |
</div> | |
<div class="mt-2 text-xs text-gray-400" id="materialMaxElongation">Material limit: 22%</div> | |
</div> | |
<!-- Bending Force --> | |
<div class="bg-gray-800 p-4 rounded-lg"> | |
<div class="flex justify-between items-center mb-2"> | |
<h4 class="font-medium flex items-center"> | |
Bending Force | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Force required for air bending</span> | |
</span> | |
</h4> | |
<div class="text-lg font-mono" id="bendingForce">0.00</div> | |
</div> | |
<div class="text-xs text-gray-400 mb-2">F = (1.33 × TS × t² × width) / die_width</div> | |
<div class="text-sm" id="forceSuggestion">Suitable for a 20-ton press brake</div> | |
</div> | |
<!-- Flange Length Results --> | |
<div class="bg-gray-800 p-4 rounded-lg" id="flangeResultsContainer"> | |
<div class="flex justify-between items-center mb-2"> | |
<h4 class="font-medium">Total Flat Length</h4> | |
<div class="text-lg font-mono" id="totalFlatLength">0.00</div> | |
</div> | |
<div class="text-xs text-gray-400">Flat Length = Leg_A + Leg_B + BD - (2 × CLR)</div> | |
</div> | |
<!-- Safety Notes --> | |
<div class="bg-gray-800 p-4 rounded-lg"> | |
<h4 class="font-medium text-teal-300 mb-2">Safety Notes</h4> | |
<ul class="text-sm space-y-2"> | |
<li class="flex items-start"> | |
<i class="fas fa-info-circle text-blue-400 mt-1 mr-2"></i> | |
<span>Always verify calculations with physical tests before full production</span> | |
</li> | |
<li class="flex items-start"> | |
<i class="fas fa-info-circle text-blue-400 mt-1 mr-2"></i> | |
<span>Consider springback - materials may return slightly after bending</span> | |
</li> | |
<li class="flex items-start"> | |
<i class="fas fa-info-circle text-blue-400 mt-1 mr-2"></i> | |
<span>Use proper tooling and safety equipment when bending</span> | |
</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
<!-- Visualization Section (Right Column) --> | |
<div class="cnc-panel p-6 rounded-lg"> | |
<h3 class="text-xl font-semibold text-teal-300 mb-4 border-b border-gray-700 pb-2"> | |
<i class="fas fa-project-diagram mr-2"></i>Bend Visualization | |
</h3> | |
<div class="space-y-6"> | |
<!-- K-Factor Visualization --> | |
<div> | |
<h4 class="font-medium mb-2 flex items-center"> | |
K-Factor Visualization | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Shows neutral axis position relative to thickness</span> | |
</span> | |
</h4> | |
<div class="relative h-48" id="kFactorVisualization"> | |
<canvas id="kFactorCanvas" class="absolute inset-0 w-full h-full"></canvas> | |
</div> | |
<div class="text-center mt-2 text-sm"> | |
<span id="kFactorValue">K-Factor: 0.42</span> | |
</div> | |
</div> | |
<!-- Bend Animation --> | |
<div> | |
<h4 class="font-medium mb-2 flex items-center"> | |
Bend Animation | |
<span class="tooltip ml-1"> | |
<i class="fas fa-info-circle text-blue-400"></i> | |
<span class="tooltiptext">Shows the bending process with neutral axis</span> | |
</span> | |
</h4> | |
<div class="relative h-48" id="bendAnimationContainer"> | |
<canvas id="bendAnimationCanvas" class="absolute inset-0 w-full h-full"></canvas> | |
</div> | |
<div class="text-center mt-2"> | |
<button id="animateBend" class="cnc-button px-4 py-2 rounded text-sm"> | |
<i class="fas fa-play mr-2"></i>Animate Bend | |
</button> | |
</div> | |
</div> | |
<!-- Material Properties --> | |
<div class="bg-gray-800 p-4 rounded-lg"> | |
<h4 class="font-medium text-teal-300 mb-2">Material Properties</h4> | |
<table class="w-full text-sm"> | |
<tbody> | |
<tr> | |
<td class="py-1">Tensile Strength</td> | |
<td class="py-1 text-right font-mono" id="matTensileStrength">400 MPa</td> | |
</tr> | |
<tr> | |
<td class="py-1">Modulus of Elasticity</td> | |
<td class="py-1 text-right font-mono" id="matModulus">210,000 MPa</td> | |
</tr> | |
<tr> | |
<td class="py-1">Max Elongation</td> | |
<td class="py-1 text-right font-mono" id="matMaxElongation">22%</td> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
</div> | |
<footer class="mt-12 text-center text-gray-500 text-sm"> | |
<p>Artisan Forge - Tube & Pipe Bending Calculator v1.0</p> | |
<p class="mt-1">For educational and professional use. Always verify with physical tests.</p> | |
</footer> | |
</div> | |
<script> | |
// Material properties database | |
const materials = { | |
"Mild Steel": { | |
tensileStrength: 400, // MPa | |
modulus: 210000, // MPa | |
maxElongation: 22, // % | |
color: "#9CA3AF" // gray | |
}, | |
"Stainless Steel 304": { | |
tensileStrength: 515, // MPa | |
modulus: 193000, // MPa | |
maxElongation: 40, // % | |
color: "#E5E7EB" // light gray | |
}, | |
"Aluminum 6061-T6": { | |
tensileStrength: 310, // MPa | |
modulus: 68900, // MPa | |
maxElongation: 12, // % | |
color: "#6EE7B7" // teal | |
}, | |
"Copper": { | |
tensileStrength: 210, // MPa | |
modulus: 117000, // MPa | |
maxElongation: 45, // % | |
color: "#D97706" // amber | |
}, | |
"Brass": { | |
tensileStrength: 340, // MPa | |
modulus: 102000, // MPa | |
maxElongation: 35, // % | |
color: "#F59E0B" // yellow | |
} | |
}; | |
// DOM elements | |
const unitSystem = document.getElementById('unitSystem'); | |
const outerDiameter = document.getElementById('outerDiameter'); | |
const wallThickness = document.getElementById('wallThickness'); | |
const bendRadius = document.getElementById('bendRadius'); | |
const bendAngle = document.getElementById('bendAngle'); | |
const bendAngleSlider = document.getElementById('bendAngleSlider'); | |
const material = document.getElementById('material'); | |
const dieWidth = document.getElementById('dieWidth'); | |
const kFactor = document.getElementById('kFactor'); | |
const kFactorLock = document.getElementById('kFactorLock'); | |
const legLengthA = document.getElementById('legLengthA'); | |
const legLengthB = document.getElementById('legLengthB'); | |
// Result elements | |
const bendAllowance = document.getElementById('bendAllowance'); | |
const bendDeduction = document.getElementById('bendDeduction'); | |
const elongation = document.getElementById('elongation'); | |
const elongationWarning = document.getElementById('elongationWarning'); | |
const materialMaxElongation = document.getElementById('materialMaxElongation'); | |
const bendingForce = document.getElementById('bendingForce'); | |
const forceSuggestion = document.getElementById('forceSuggestion'); | |
const totalFlatLength = document.getElementById('totalFlatLength'); | |
const flangeResultsContainer = document.getElementById('flangeResultsContainer'); | |
// Visualization elements | |
const kFactorValue = document.getElementById('kFactorValue'); | |
const animateBend = document.getElementById('animateBend'); | |
// Material property displays | |
const matTensileStrength = document.getElementById('matTensileStrength'); | |
const matModulus = document.getElementById('matModulus'); | |
const matMaxElongation = document.getElementById('matMaxElongation'); | |
// Unit displays | |
const odUnit = document.getElementById('odUnit'); | |
const wtUnit = document.getElementById('wtUnit'); | |
const clrUnit = document.getElementById('clrUnit'); | |
const dieWidthUnit = document.getElementById('dieWidthUnit'); | |
const flangeUnit = document.getElementById('flangeUnit'); | |
// Canvas elements | |
const kFactorCanvas = document.getElementById('kFactorCanvas'); | |
const kFactorCtx = kFactorCanvas.getContext('2d'); | |
const bendAnimationCanvas = document.getElementById('bendAnimationCanvas'); | |
const bendAnimationCtx = bendAnimationCanvas.getContext('2d'); | |
// State variables | |
let isKFactorLocked = false; | |
let currentKFactor = 0.42; | |
// Initialize the app | |
function init() { | |
// Set up event listeners | |
unitSystem.addEventListener('change', updateUnits); | |
outerDiameter.addEventListener('input', updateCalculations); | |
wallThickness.addEventListener('input', updateCalculations); | |
bendRadius.addEventListener('input', updateCalculations); | |
bendAngle.addEventListener('input', updateBendAngleFromInput); | |
bendAngleSlider.addEventListener('input', updateBendAngleFromSlider); | |
material.addEventListener('change', updateMaterialProperties); | |
dieWidth.addEventListener('input', updateCalculations); | |
kFactor.addEventListener('input', updateKFactorFromInput); | |
kFactorLock.addEventListener('click', toggleKFactorLock); | |
legLengthA.addEventListener('input', updateCalculations); | |
legLengthB.addEventListener('input', updateCalculations); | |
animateBend.addEventListener('click', animateBendProcess); | |
// Set up canvas | |
resizeCanvases(); | |
window.addEventListener('resize', resizeCanvases); | |
// Set default values | |
outerDiameter.value = 50; | |
wallThickness.value = 2; | |
bendRadius.value = 75; | |
bendAngle.value = 90; | |
bendAngleSlider.value = 90; | |
// Initialize calculations | |
updateMaterialProperties(); | |
updateCalculations(); | |
} | |
// Update all unit displays | |
function updateUnits() { | |
const isMetric = unitSystem.value === 'metric'; | |
const unit = isMetric ? 'mm' : 'in'; | |
odUnit.textContent = unit; | |
wtUnit.textContent = unit; | |
clrUnit.textContent = unit; | |
dieWidthUnit.textContent = unit; | |
flangeUnit.textContent = unit; | |
updateCalculations(); | |
} | |
// Update material property displays | |
function updateMaterialProperties() { | |
const selectedMaterial = material.value; | |
const matProps = materials[selectedMaterial]; | |
matTensileStrength.textContent = `${matProps.tensileStrength} MPa`; | |
matModulus.textContent = `${matProps.modulus.toLocaleString()} MPa`; | |
matMaxElongation.textContent = `${matProps.maxElongation}%`; | |
updateCalculations(); | |
} | |
// Update bend angle from slider | |
function updateBendAngleFromSlider() { | |
bendAngle.value = bendAngleSlider.value; | |
updateCalculations(); | |
} | |
// Update bend angle from input | |
function updateBendAngleFromInput() { | |
let angle = parseFloat(bendAngle.value); | |
if (isNaN(angle)) angle = 0; | |
if (angle < 0) angle = 0; | |
if (angle > 180) angle = 180; | |
bendAngle.value = angle; | |
bendAngleSlider.value = angle; | |
updateCalculations(); | |
} | |
// Toggle K-Factor lock | |
function toggleKFactorLock() { | |
isKFactorLocked = !isKFactorLocked; | |
kFactorLock.innerHTML = isKFactorLocked ? '<i class="fas fa-lock-open"></i>' : '<i class="fas fa-lock"></i>'; | |
kFactorLock.title = isKFactorLocked ? 'Click to unlock and auto-calculate' : 'Click to lock and override'; | |
updateCalculations(); | |
} | |
// Update K-Factor from input | |
function updateKFactorFromInput() { | |
let factor = parseFloat(kFactor.value); | |
if (isNaN(factor)) factor = 0.42; | |
if (factor < 0) factor = 0; | |
if (factor > 1) factor = 1; | |
currentKFactor = factor; | |
updateCalculations(); | |
} | |
// Calculate K-Factor based on inputs | |
function calculateKFactor() { | |
if (isKFactorLocked) return currentKFactor; | |
const t = parseFloat(wallThickness.value); | |
const r = parseFloat(bendRadius.value); | |
if (isNaN(t) || isNaN(r) || t === 0 || r === 0) return 0.42; | |
// Empirical formula for K-Factor | |
const ratio = r / t; | |
let factor; | |
if (ratio < 1) { | |
factor = 0.3; | |
} else if (ratio < 2) { | |
factor = 0.33; | |
} else if (ratio < 3) { | |
factor = 0.4; | |
} else if (ratio < 4) { | |
factor = 0.45; | |
} else { | |
factor = 0.5; | |
} | |
currentKFactor = factor; | |
kFactor.value = factor.toFixed(3); | |
return factor; | |
} | |
// Calculate Bend Allowance | |
function calculateBendAllowance() { | |
const angle = parseFloat(bendAngle.value); | |
const r = parseFloat(bendRadius.value); | |
const t = parseFloat(wallThickness.value); | |
const k = calculateKFactor(); | |
if (isNaN(angle) || isNaN(r) || isNaN(t)) return 0; | |
// BA = angle × (π/180) × (radius + K × thickness) | |
const ba = angle * (Math.PI / 180) * (r + k * t); | |
return ba; | |
} | |
// Calculate Bend Deduction | |
function calculateBendDeduction() { | |
const angle = parseFloat(bendAngle.value); | |
const r = parseFloat(bendRadius.value); | |
const t = parseFloat(wallThickness.value); | |
const ba = calculateBendAllowance(); | |
if (isNaN(angle) || isNaN(r) || isNaN(t)) return 0; | |
// BD = 2 × (radius + thickness) × tan(angle/2) - BA | |
const bd = 2 * (r + t) * Math.tan(angle * Math.PI / 360) - ba; | |
return bd; | |
} | |
// Calculate Elongation | |
function calculateElongation() { | |
const od = parseFloat(outerDiameter.value); | |
const r = parseFloat(bendRadius.value); | |
if (isNaN(od) || isNaN(r) || r === 0) return 0; | |
// Elongation % = (OD / (2 × CLR)) × 100 | |
const elongationPercent = (od / (2 * r)) * 100; | |
return elongationPercent; | |
} | |
// Calculate Bending Force | |
function calculateBendingForce() { | |
const selectedMaterial = material.value; | |
const matProps = materials[selectedMaterial]; | |
const t = parseFloat(wallThickness.value); | |
const dw = parseFloat(dieWidth.value); | |
const od = parseFloat(outerDiameter.value); | |
if (isNaN(t) || isNaN(dw) || isNaN(od) || dw === 0) return 0; | |
// Convert units if necessary | |
let width = od * Math.PI; // Approximate width as circumference | |
if (unitSystem.value === 'imperial') { | |
// Convert from inches to mm for calculation (material props are in MPa) | |
width *= 25.4; | |
t *= 25.4; | |
dw *= 25.4; | |
} | |
// F = (1.33 × TS × t² × width) / die_width | |
// Result will be in Newtons (divide by 9806.65 to get metric tons) | |
const forceNewtons = (1.33 * matProps.tensileStrength * Math.pow(t, 2) * width) / dw; | |
const forceTons = forceNewtons / 9806.65; | |
if (unitSystem.value === 'imperial') { | |
// Convert to US tons (1 metric ton = 1.10231 US tons) | |
return forceTons * 1.10231; | |
} | |
return forceTons; | |
} | |
// Calculate Total Flat Length | |
function calculateTotalFlatLength() { | |
const legA = parseFloat(legLengthA.value); | |
const legB = parseFloat(legLengthB.value); | |
const bd = calculateBendDeduction(); | |
const r = parseFloat(bendRadius.value); | |
if (isNaN(legA) || isNaN(legB)) return null; | |
// Flat Length = Leg_A + Leg_B + BD - (2 × CLR) | |
const flatLength = legA + legB + bd - (2 * r); | |
return flatLength; | |
} | |
// Update all calculations and displays | |
function updateCalculations() { | |
// Calculate all values | |
const k = calculateKFactor(); | |
const ba = calculateBendAllowance(); | |
const bd = calculateBendDeduction(); | |
const elongationPercent = calculateElongation(); | |
const force = calculateBendingForce(); | |
const flatLength = calculateTotalFlatLength(); | |
// Get material properties | |
const selectedMaterial = material.value; | |
const matProps = materials[selectedMaterial]; | |
// Update displays | |
kFactorValue.textContent = `K-Factor: ${k.toFixed(3)}`; | |
// Format values with units | |
const isMetric = unitSystem.value === 'metric'; | |
const lengthUnit = isMetric ? 'mm' : 'in'; | |
const forceUnit = isMetric ? 'tons' : 'US tons'; | |
bendAllowance.textContent = `${ba.toFixed(2)} ${lengthUnit}`; | |
bendDeduction.textContent = `${bd.toFixed(2)} ${lengthUnit}`; | |
elongation.textContent = `${elongationPercent.toFixed(2)}%`; | |
// Update elongation warning | |
updateElongationWarning(elongationPercent, matProps.maxElongation); | |
// Update bending force | |
bendingForce.textContent = `${force.toFixed(2)} ${forceUnit}`; | |
updateForceSuggestion(force); | |
// Update flat length if provided | |
if (flatLength !== null) { | |
totalFlatLength.textContent = `${flatLength.toFixed(2)} ${lengthUnit}`; | |
flangeResultsContainer.classList.remove('hidden'); | |
} else { | |
flangeResultsContainer.classList.add('hidden'); | |
} | |
// Update visualizations | |
drawKFactorVisualization(); | |
drawBendAnimation(); | |
} | |
// Update elongation warning based on material limits | |
function updateElongationWarning(elongationPercent, maxElongation) { | |
const warningIcon = elongationWarning.querySelector('i'); | |
const warningText = elongationWarning.querySelector('span'); | |
// Clear all classes | |
warningIcon.className = 'fas mr-2'; | |
warningText.className = 'text-sm'; | |
// Set warning level | |
if (elongationPercent > maxElongation) { | |
// Danger - exceeds limits | |
warningIcon.classList.add('fa-times-circle', 'danger'); | |
warningText.classList.add('danger'); | |
warningText.textContent = 'Warning! Elongation exceeds material limits. High risk of cracking.'; | |
elongationWarning.parentElement.classList.add('border', 'border-red-500'); | |
} else if (elongationPercent > maxElongation * 0.8) { | |
// Warning - approaching limits | |
warningIcon.classList.add('fa-exclamation-circle', 'warning'); | |
warningText.classList.add('warning'); | |
warningText.textContent = 'Approaching material limits. Consider a larger bend radius.'; | |
elongationWarning.parentElement.classList.add('border', 'border-yellow-500'); | |
} else { | |
// Safe - within limits | |
warningIcon.classList.add('fa-check-circle', 'safe'); | |
warningText.classList.add('safe'); | |
warningText.textContent = 'Elongation within safe limits.'; | |
elongationWarning.parentElement.classList.remove('border', 'border-red-500', 'border-yellow-500'); | |
} | |
// Update material max elongation display | |
materialMaxElongation.textContent = `Material limit: ${maxElongation}%`; | |
} | |
// Update press brake suggestion | |
function updateForceSuggestion(force) { | |
if (isNaN(force)) { | |
forceSuggestion.textContent = 'Enter valid parameters to calculate force'; | |
return; | |
} | |
const isMetric = unitSystem.value === 'metric'; | |
const commonPresses = isMetric ? | |
[10, 20, 40, 60, 80, 100, 150, 200] : | |
[11, 22, 44, 66, 88, 110, 165, 220]; // US tons | |
let suitablePress = commonPresses.find(capacity => capacity >= force * 1.2); // 20% safety margin | |
if (!suitablePress) { | |
suitablePress = commonPresses[commonPresses.length - 1]; | |
forceSuggestion.textContent = `Required force: ${force.toFixed(1)} ${isMetric ? 'tons' : 'US tons'}. Exceeds standard press brakes (max ${suitablePress} ${isMetric ? 'tons' : 'US tons'}).`; | |
} else { | |
forceSuggestion.textContent = `Required force: ${force.toFixed(1)} ${isMetric ? 'tons' : 'US tons'}. Suitable for a ${suitablePress}${isMetric ? '-ton' : ' US-ton'} press brake.`; | |
} | |
} | |
// Draw K-Factor visualization | |
function drawKFactorVisualization() { | |
const canvas = kFactorCanvas; | |
const ctx = kFactorCtx; | |
const width = canvas.width; | |
const height = canvas.height; | |
// Clear canvas | |
ctx.clearRect(0, 0, width, height); | |
const od = parseFloat(outerDiameter.value); | |
const t = parseFloat(wallThickness.value); | |
const k = calculateKFactor(); | |
if (isNaN(od) || isNaN(t) || t === 0) return; | |
// Calculate dimensions | |
const tubeRadius = od / 2; | |
const innerRadius = tubeRadius - t; | |
const neutralAxisRadius = innerRadius + (t * k); | |
// Scale to fit canvas | |
const scale = Math.min(width / (od * 1.5), height / (od * 1.5)); | |
const centerX = width / 2; | |
const centerY = height / 2; | |
// Draw tube cross-section | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, tubeRadius * scale, 0, Math.PI * 2); | |
ctx.fillStyle = '#4A5568'; | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, innerRadius * scale, 0, Math.PI * 2); | |
ctx.fillStyle = '#1A202C'; | |
ctx.fill(); | |
// Draw neutral axis | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, neutralAxisRadius * scale, 0, Math.PI * 2); | |
ctx.strokeStyle = '#38B2AC'; | |
ctx.lineWidth = 2; | |
ctx.setLineDash([5, 3]); | |
ctx.stroke(); | |
ctx.setLineDash([]); | |
// Draw indicators | |
ctx.font = '10px Arial'; | |
ctx.fillStyle = '#E2E8F0'; | |
ctx.textAlign = 'center'; | |
// Inside of bend (compression) | |
ctx.fillText('Compression', centerX, centerY - (innerRadius + t/2) * scale - 10); | |
// Outside of bend (tension) | |
ctx.fillText('Tension', centerX, centerY + (innerRadius + t/2) * scale + 20); | |
// Neutral axis label | |
ctx.fillText('Neutral Axis', centerX, centerY - neutralAxisRadius * scale - 10); | |
// Draw dimension lines | |
ctx.strokeStyle = '#E2E8F0'; | |
ctx.lineWidth = 1; | |
// Wall thickness dimension | |
ctx.beginPath(); | |
ctx.moveTo(centerX + tubeRadius * scale + 10, centerY); | |
ctx.lineTo(centerX + innerRadius * scale - 10, centerY); | |
ctx.stroke(); | |
ctx.beginPath(); | |
ctx.moveTo(centerX + tubeRadius * scale + 5, centerY - 5); | |
ctx.lineTo(centerX + tubeRadius * scale + 5, centerY + 5); | |
ctx.stroke(); | |
ctx.beginPath(); | |
ctx.moveTo(centerX + innerRadius * scale - 5, centerY - 5); | |
ctx.lineTo(centerX + innerRadius * scale - 5, centerY + 5); | |
ctx.stroke(); | |
ctx.fillText(`t = ${t.toFixed(2)}`, centerX + (tubeRadius + innerRadius) * scale / 2, centerY + 15); | |
// K-Factor dimension | |
ctx.beginPath(); | |
ctx.moveTo(centerX + innerRadius * scale, centerY); | |
ctx.lineTo(centerX + neutralAxisRadius * scale, centerY); | |
ctx.stroke(); | |
ctx.fillText(`K·t = ${(k * t).toFixed(2)}`, centerX + (innerRadius + neutralAxisRadius) * scale / 2, centerY - 5); | |
} | |
// Draw bend animation | |
function drawBendAnimation() { | |
const canvas = bendAnimationCanvas; | |
const ctx = bendAnimationCtx; | |
const width = canvas.width; | |
const height = canvas.height; | |
// Clear canvas | |
ctx.clearRect(0, 0, width, height); | |
const od = parseFloat(outerDiameter.value); | |
const t = parseFloat(wallThickness.value); | |
const r = parseFloat(bendRadius.value); | |
const angle = parseFloat(bendAngle.value); | |
const k = calculateKFactor(); | |
if (isNaN(od) || isNaN(t) || isNaN(r) || isNaN(angle)) return; | |
// Calculate dimensions | |
const tubeRadius = od / 2; | |
const innerRadius = tubeRadius - t; | |
const neutralAxisRadius = innerRadius + (t * k); | |
// Scale to fit canvas | |
const scale = Math.min(width / (od + r * 2), height / (od + r * 2)); | |
const startX = width * 0.2; | |
const startY = height / 2; | |
// Draw straight tube section | |
ctx.beginPath(); | |
ctx.rect(startX, startY - tubeRadius * scale, r * scale * 1.5, od * scale); | |
ctx.fillStyle = '#4A5568'; | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.rect(startX, startY - innerRadius * scale, r * scale * 1.5, t * 2 * scale); | |
ctx.fillStyle = '#1A202C'; | |
ctx.fill(); | |
// Draw bend center | |
const centerX = startX + r * scale * 1.5; | |
const centerY = startY; | |
// Draw bent section | |
const startAngle = 0; | |
const endAngle = angle * Math.PI / 180; | |
// Outer bend | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, (r + tubeRadius) * scale, startAngle, endAngle); | |
ctx.lineTo(centerX + (r + tubeRadius) * scale * Math.cos(endAngle), centerY + (r + tubeRadius) * scale * Math.sin(endAngle) - tubeRadius * scale); | |
ctx.arc(centerX, centerY, (r - tubeRadius) * scale, endAngle, startAngle, true); | |
ctx.closePath(); | |
ctx.fillStyle = '#4A5568'; | |
ctx.fill(); | |
// Inner bend (hollow) | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, (r + innerRadius) * scale, startAngle, endAngle); | |
ctx.lineTo(centerX + (r + innerRadius) * scale * Math.cos(endAngle), centerY + (r + innerRadius) * scale * Math.sin(endAngle) - innerRadius * scale); | |
ctx.arc(centerX, centerY, (r - innerRadius) * scale, endAngle, startAngle, true); | |
ctx.closePath(); | |
ctx.fillStyle = '#1A202C'; | |
ctx.fill(); | |
// Neutral axis | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, r * scale, startAngle, endAngle); | |
ctx.strokeStyle = '#38B2AC'; | |
ctx.lineWidth = 2; | |
ctx.setLineDash([5, 3]); | |
ctx.stroke(); | |
ctx.setLineDash([]); | |
// Labels | |
ctx.font = '12px Arial'; | |
ctx.fillStyle = '#E2E8F0'; | |
ctx.textAlign = 'center'; | |
// Bend radius label | |
ctx.beginPath(); | |
ctx.moveTo(centerX, centerY); | |
ctx.lineTo(centerX + r * scale * Math.cos(endAngle / 2), centerY + r * scale * Math.sin(endAngle / 2)); | |
ctx.strokeStyle = '#E2E8F0'; | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
ctx.fillText(`CLR = ${r.toFixed(2)}`, | |
centerX + r * scale * Math.cos(endAngle / 2) / 2, | |
centerY + r * scale * Math.sin(endAngle / 2) / 2 - 5); | |
// Angle label | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, r * scale * 0.3, startAngle, endAngle); | |
ctx.strokeStyle = '#E2E8F0'; | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
ctx.fillText(`${angle.toFixed(0)}°`, | |
centerX + r * scale * 0.3 * Math.cos(endAngle / 2), | |
centerY + r * scale * 0.3 * Math.sin(endAngle / 2) + 15); | |
} | |
// Animate the bending process | |
function animateBendProcess() { | |
const canvas = bendAnimationCanvas; | |
const ctx = bendAnimationCtx; | |
const width = canvas.width; | |
const height = canvas.height; | |
// Clear canvas | |
ctx.clearRect(0, 0, width, height); | |
const od = parseFloat(outerDiameter.value); | |
const t = parseFloat(wallThickness.value); | |
const r = parseFloat(bendRadius.value); | |
const angle = parseFloat(bendAngle.value); | |
const k = calculateKFactor(); | |
if (isNaN(od) || isNaN(t) || isNaN(r) || isNaN(angle)) return; | |
// Calculate dimensions | |
const tubeRadius = od / 2; | |
const innerRadius = tubeRadius - t; | |
const neutralAxisRadius = innerRadius + (t * k); | |
// Scale to fit canvas | |
const scale = Math.min(width / (od + r * 2), height / (od + r * 2)); | |
const startX = width * 0.2; | |
const startY = height / 2; | |
// Draw straight tube section (will remain static) | |
ctx.beginPath(); | |
ctx.rect(startX, startY - tubeRadius * scale, r * scale * 1.5, od * scale); | |
ctx.fillStyle = '#4A5568'; | |
ctx.fill(); | |
ctx.beginPath(); | |
ctx.rect(startX, startY - innerRadius * scale, r * scale * 1.5, t * 2 * scale); | |
ctx.fillStyle = '#1A202C'; | |
ctx.fill(); | |
// Bend center | |
const centerX = startX + r * scale * 1.5; | |
const centerY = startY; | |
// Animation variables | |
let currentAngle = 0; | |
const targetAngle = angle * Math.PI / 180; | |
const animationDuration = 1000; // ms | |
const startTime = performance.now(); | |
function animate(timestamp) { | |
const elapsed = timestamp - startTime; | |
const progress = Math.min(elapsed / animationDuration, 1); | |
currentAngle = progress * targetAngle; | |
// Clear the bending area | |
ctx.clearRect(centerX - (r + tubeRadius) * scale, | |
centerY - (r + tubeRadius) * scale, | |
(r + tubeRadius) * scale * 2, | |
(r + tubeRadius) * scale * 2); | |
// Draw bent section | |
// Outer bend | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, (r + tubeRadius) * scale, 0, currentAngle); | |
ctx.lineTo(centerX + (r + tubeRadius) * scale * Math.cos(currentAngle), | |
centerY + (r + tubeRadius) * scale * Math.sin(currentAngle) - tubeRadius * scale); | |
ctx.arc(centerX, centerY, (r - tubeRadius) * scale, currentAngle, 0, true); | |
ctx.closePath(); | |
ctx.fillStyle = '#4A5568'; | |
ctx.fill(); | |
// Inner bend (hollow) | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, (r + innerRadius) * scale, 0, currentAngle); | |
ctx.lineTo(centerX + (r + innerRadius) * scale * Math.cos(currentAngle), | |
centerY + (r + innerRadius) * scale * Math.sin(currentAngle) - innerRadius * scale); | |
ctx.arc(centerX, centerY, (r - innerRadius) * scale, currentAngle, 0, true); | |
ctx.closePath(); | |
ctx.fillStyle = '#1A202C'; | |
ctx.fill(); | |
// Neutral axis | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, r * scale, 0, currentAngle); | |
ctx.strokeStyle = '#38B2AC'; | |
ctx.lineWidth = 2; | |
ctx.setLineDash([5, 3]); | |
ctx.stroke(); | |
ctx.setLineDash([]); | |
// Angle label | |
ctx.font = '12px Arial'; | |
ctx.fillStyle = '#E2E8F0'; | |
ctx.textAlign = 'center'; | |
ctx.beginPath(); | |
ctx.arc(centerX, centerY, r * scale * 0.3, 0, currentAngle); | |
ctx.strokeStyle = '#E2E8F0'; | |
ctx.lineWidth = 1; | |
ctx.stroke(); | |
const degrees = (currentAngle * 180 / Math.PI).toFixed(0); | |
ctx.fillText(`${degrees}°`, | |
centerX + r * scale * 0.3 * Math.cos(currentAngle / 2), | |
centerY + r * scale * 0.3 * Math.sin(currentAngle / 2) + 15); | |
if (progress < 1) { | |
requestAnimationFrame(animate); | |
} | |
} | |
requestAnimationFrame(animate); | |
} | |
// Resize canvases to maintain aspect ratio | |
function resizeCanvases() { | |
const containers = document.querySelectorAll('#kFactorVisualization, #bendAnimationContainer'); | |
containers.forEach(container => { | |
const canvas = container.querySelector('canvas'); | |
const width = container.clientWidth; | |
const height = container.clientHeight; | |
// Set display size | |
canvas.style.width = width + 'px'; | |
canvas.style.height = height + 'px'; | |
// Set actual size in memory (scaled for retina displays) | |
const scale = window.devicePixelRatio || 1; | |
canvas.width = width * scale; | |
canvas.height = height * scale; | |
// Normalize coordinate system to use CSS pixels | |
const ctx = canvas.getContext('2d'); | |
ctx.scale(scale, scale); | |
}); | |
// Redraw visualizations | |
drawKFactorVisualization(); | |
drawBendAnimation(); | |
} | |
// Initialize the application | |
window.addEventListener('load', init); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MRevenant/artisan-prompt" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |