<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>볼트토크 &#8211; MyEngNote</title>
	<atom:link href="https://myengnote.com/tag/%eb%b3%bc%ed%8a%b8%ed%86%a0%ed%81%ac/feed/" rel="self" type="application/rss+xml" />
	<link>https://myengnote.com</link>
	<description></description>
	<lastBuildDate>Thu, 28 May 2026 23:33:40 +0000</lastBuildDate>
	<language>ko-KR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>
	<item>
		<title>볼트 체결 토크 계산기 &#038; 시뮬레이터</title>
		<link>https://myengnote.com/bolt-torque-tension-calculator-simulator/</link>
					<comments>https://myengnote.com/bolt-torque-tension-calculator-simulator/#respond</comments>
		
		<dc:creator><![CDATA[동동]]></dc:creator>
		<pubDate>Thu, 28 May 2026 23:10:08 +0000</pubDate>
				<category><![CDATA[공학계산기]]></category>
		<category><![CDATA[VDI2230]]></category>
		<category><![CDATA[볼트토크]]></category>
		<category><![CDATA[체결축력]]></category>
		<category><![CDATA[토크계수]]></category>
		<category><![CDATA[항복강도]]></category>
		<guid isPermaLink="false">https://myengnote.com/bolt-torque-tension-calculator-simulator/</guid>

					<description><![CDATA[볼트 호칭 지름, 강도 등급, 마찰 계수에 따른 체결 토크(Torque)와 볼트 축력(Tension) 간의 물리적 상호작용을 정밀 계산하고, 축부 인장 응력과 피체 체결압 분포를 시각화하는 2D 기계요소 시뮬레이터입니다.]]></description>
										<content:encoded><![CDATA[
<h2 style="font-size: 1.6em; font-weight: 800; color: #0c0e25; border-bottom: 2px solid #00f2fe; padding-bottom: 8px; margin-bottom: 20px;"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 볼트 체결 토크 계산기 &#038; 시뮬레이터</h2>



<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&#038;family=Outfit:wght@400;500;600;700;800&#038;display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Scoped & Isolated Styles for bolttorque-calculator-wrapper */

        /* Modern Reset and Design Tokens */
        .bolttorque-calculator-wrapper *, .bolttorque-calculator-wrapper *::before, .bolttorque-calculator-wrapper *::after {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        .bolttorque-calculator-wrapper {
            --color-bg-dark: #f8f9fd;
            --color-bg-deep: #ffffff;
            --color-panel-bg: rgba(255, 255, 255, 0.85);
            --color-border: rgba(0, 0, 0, 0.06);
            --color-border-hover: rgba(0, 0, 0, 0.12);
            
            --color-text-main: #1e293b;
            --color-text-muted: #475569;
            --color-text-dark: #94a3b8;

            --color-cyan: #0284c7;
            --color-cyan-glow: rgba(2, 132, 199, 0.15);
            --color-magenta: #db2777;
            --color-magenta-glow: rgba(219, 39, 119, 0.15);
            --color-purple: #7c3aed;
            --color-purple-glow: rgba(124, 58, 237, 0.12);
            --color-success: #10b981;
            --color-warning: #f59e0b;

            --gradient-primary: linear-gradient(135deg, var(--color-cyan) 0%, var(--color-purple) 100%);
            --gradient-accent: linear-gradient(135deg, var(--color-magenta) 0%, var(--color-purple) 100%);
            --gradient-panel: linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(248, 250, 252, 0.95) 100%);

            --font-heading: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
            
            --shadow-neon-cyan: 0 0 15px var(--color-cyan-glow);
            --shadow-neon-magenta: 0 0 15px var(--color-magenta-glow);
            --shadow-card: 0 8px 32px 0 rgba(15, 23, 42, 0.05);
            --blur-glass: blur(16px);
        }

        .bolttorque-calculator-wrapper {
            background-color: var(--color-bg-dark);
            color: var(--color-text-main);
            font-family: var(--font-body);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            overflow-x: hidden;
            position: relative;
        }

        .app-background-glow {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: -1;
            overflow: hidden;
            pointer-events: none;
        }

        .app-background-glow::before, 
        .app-background-glow::after {
            content: '';
            position: absolute;
            width: 600px;
            height: 600px;
            border-radius: 50%;
            filter: blur(140px);
            opacity: 0.12;
        }

        .app-background-glow::before { background: var(--color-cyan); top: -10%; right: -5%; animation: pulse-slow 15s infinite alternate; }
        .app-background-glow::after { background: var(--color-purple); bottom: -10%; left: -5%; animation: pulse-slow 20s infinite alternate-reverse; }

        @keyframes pulse-slow {
            0% { transform: scale(1) translate(0, 0); opacity: 0.08; }
            100% { transform: scale(1.2) translate(50px, 50px); opacity: 0.15; }
        }

        .app-container {
            width: 100%;
            max-width: 1440px;
            padding: 24px;
            display: flex;
            flex-direction: column;
            gap: 24px;
            container-type: inline-size;
            container-name: bolttorque-container;
        }

        .app-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 20px 24px;
            background: var(--color-panel-bg);
            backdrop-filter: var(--blur-glass);
            border: 1px solid var(--color-border);
            border-radius: 16px;
            box-shadow: var(--shadow-card);
        }

        .logo-area {
            display: flex;
            align-items: center;
            gap: 16px;
        }

        .logo-icon {
            font-size: 32px;
            background: linear-gradient(135deg, var(--color-cyan), var(--color-magenta));
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .logo-area h1 {
            font-family: var(--font-heading);
            font-weight: 800;
            font-size: 24px;
            letter-spacing: 1.5px;
            background: linear-gradient(90deg, var(--color-text-main), var(--color-text-dark));
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .logo-area .subtitle {
            font-size: 12px;
            color: var(--color-text-muted);
            font-weight: 500;
            letter-spacing: 0.5px;
        }

        .header-badge {
            display: flex;
            align-items: center;
            gap: 10px;
            background: rgba(219, 39, 119, 0.1);
            border: 1px solid rgba(219, 39, 119, 0.2);
            padding: 6px 14px;
            border-radius: 20px;
        }

        .pulse-dot {
            width: 8px;
            height: 8px;
            background-color: var(--color-magenta);
            border-radius: 50%;
            box-shadow: 0 0 10px var(--color-magenta);
            animation: pulse-dot-anim 1.5s infinite;
        }

        @keyframes pulse-dot-anim {
            0% { transform: scale(0.9); opacity: 0.6; }
            50% { transform: scale(1.2); opacity: 1; }
            100% { transform: scale(0.9); opacity: 0.6; }
        }

        .badge-text {
            font-size: 11px;
            font-weight: 600;
            color: var(--color-magenta);
            letter-spacing: 0.5px;
        }

        .app-main-grid {
            display: grid;
            grid-template-columns: 340px 1fr 340px;
            gap: 24px;
            align-items: start;
        }

        .panel {
            background: var(--gradient-panel);
            backdrop-filter: var(--blur-glass);
            border: 1px solid var(--color-border);
            border-radius: 20px;
            padding: 24px;
            box-shadow: var(--shadow-card);
            display: flex;
            flex-direction: column;
            gap: 18px;
            transition: border-color 0.3s ease, box-shadow 0.3s ease;
        }

        .panel:hover { border-color: var(--color-border-hover); }

        .panel-header {
            display: flex;
            align-items: center;
            gap: 12px;
            border-bottom: 1px solid var(--color-border);
            padding-bottom: 14px;
        }

        .panel-header i { font-size: 18px; }
        .panel-header h2 {
            font-family: var(--font-heading);
            font-size: 18px;
            font-weight: 600;
            letter-spacing: 0.5px;
        }

        .text-cyan { color: var(--color-cyan); }
        .text-magenta { color: var(--color-magenta); }
        .text-purple { color: var(--color-purple); }

        .control-panel { grid-column: 1; }

        .mode-select-container {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .mode-label {
            font-size: 12px;
            font-weight: 700;
            color: var(--color-text-muted);
            letter-spacing: 0.5px;
        }

        .segmented-control {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            background: rgba(0, 0, 0, 0.03);
            border: 1px solid var(--color-border);
            padding: 4px;
            border-radius: 12px;
            gap: 4px;
        }

        .segmented-btn {
            background: transparent;
            border: none;
            color: var(--color-text-muted);
            padding: 8px 2px;
            border-radius: 8px;
            font-size: 10.5px;
            font-weight: 700;
            cursor: pointer;
            transition: all 0.2s ease;
            text-align: center;
        }

        .segmented-btn:hover {
            color: var(--color-text-main);
            background: rgba(0, 0, 0, 0.015);
        }

        .segmented-btn.active {
            background: #ffffff;
            color: var(--color-cyan);
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
        }

        .input-group {
            display: flex;
            flex-direction: column;
            gap: 8px;
            transition: all 0.3s ease;
        }

        .input-group.disabled {
            opacity: 0.4;
            pointer-events: none;
        }

        .input-label-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .input-label-row label {
            font-size: 13px;
            font-weight: 600;
            color: var(--color-text-main);
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .helper-text {
            font-size: 10px;
            color: var(--color-text-muted);
            font-weight: 500;
        }

        .number-input-wrapper {
            display: flex;
            position: relative;
            border-radius: 8px;
            overflow: hidden;
            border: 1px solid var(--color-border);
            background: #ffffff;
            transition: all 0.2s ease;
        }

        .number-input-wrapper:focus-within {
            border-color: var(--color-cyan);
            box-shadow: 0 0 10px var(--color-cyan-glow);
        }

        .custom-number-input {
            width: 100%;
            background: #f8fafc !important;
            border: none !important;
            outline: none !important;
            color: #0f172a !important;
            padding: 10px 14px !important;
            font-family: var(--font-body) !important;
            font-size: 14.5px !important;
            font-weight: 700 !important;
            text-align: right !important;
            padding-right: 60px !important;
            -webkit-appearance: none !important;
            -moz-appearance: textfield !important;
        }

        .custom-number-input::-webkit-outer-spin-button,
        .custom-number-input::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }

        .unit-badge {
            position: absolute;
            right: 1px;
            top: 1px;
            bottom: 1px;
            background: rgba(0, 0, 0, 0.03);
            color: var(--color-text-muted);
            font-size: 11px;
            font-weight: 700;
            padding: 0 14px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-left: 1px solid var(--color-border);
            pointer-events: none;
            letter-spacing: 0.5px;
        }

        .custom-slider {
            -webkit-appearance: none;
            width: 100%;
            height: 6px;
            border-radius: 3px;
            background: rgba(0, 0, 0, 0.05);
            outline: none;
            margin: 4px 0;
            transition: background 0.2s ease;
        }

        .custom-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 18px;
            height: 18px;
            border-radius: 50%;
            cursor: pointer;
            transition: transform 0.1s ease, box-shadow 0.2s ease;
        }

        .slider-cyan::-webkit-slider-thumb { background: var(--color-cyan); box-shadow: 0 0 8px var(--color-cyan); }
        .slider-cyan::-webkit-slider-thumb:hover { transform: scale(1.2); }
        .slider-magenta::-webkit-slider-thumb { background: var(--color-magenta); box-shadow: 0 0 8px var(--color-magenta); }
        .slider-magenta::-webkit-slider-thumb:hover { transform: scale(1.2); }
        .slider-purple::-webkit-slider-thumb { background: var(--color-purple); box-shadow: 0 0 8px var(--color-purple); }
        .slider-purple::-webkit-slider-thumb:hover { transform: scale(1.2); }

        .slider-cyan::-moz-range-thumb { width: 18px; height: 18px; border: none; border-radius: 50%; background: var(--color-cyan); }
        .slider-magenta::-moz-range-thumb { width: 18px; height: 18px; border: none; border-radius: 50%; background: var(--color-magenta); }
        .slider-purple::-moz-range-thumb { width: 18px; height: 18px; border: none; border-radius: 50%; background: var(--color-purple); }

        .select-wrapper {
            position: relative;
            display: flex;
            border-radius: 8px;
            overflow: hidden;
            border: 1px solid var(--color-border);
            background: #ffffff;
        }

        .custom-select {
            width: 100%;
            background: #f8fafc;
            border: none;
            outline: none;
            color: #0f172a;
            padding: 10px 14px;
            font-family: var(--font-body);
            font-size: 14px;
            font-weight: 600;
            appearance: none;
            cursor: pointer;
        }

        .select-arrow {
            position: absolute;
            right: 14px;
            top: 50%;
            transform: translateY(-50%);
            pointer-events: none;
            color: var(--color-text-muted);
            font-size: 12px;
        }

        .presets-section {
            display: flex;
            flex-direction: column;
            gap: 10px;
            border-top: 1px solid var(--color-border);
            padding-top: 16px;
        }

        .presets-section h3 {
            font-size: 13px;
            font-weight: 700;
            color: var(--color-text-main);
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .presets-grid {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .preset-btn {
            background: rgba(0, 0, 0, 0.015);
            border: 1px solid var(--color-border);
            border-radius: 10px;
            padding: 8px 12px;
            cursor: pointer;
            color: var(--color-text-main);
            display: flex;
            align-items: center;
            gap: 12px;
            text-align: left;
            transition: all 0.2s ease;
        }

        .preset-btn:hover {
            background: rgba(0, 0, 0, 0.04);
            transform: translateX(4px);
        }

        .preset-btn.active {
            background: linear-gradient(90deg, var(--color-cyan-glow), var(--color-magenta-glow));
            border-color: var(--color-cyan);
            box-shadow: 0 0 10px var(--color-cyan-glow);
        }

        .preset-icon {
            width: 28px;
            height: 28px;
            background: rgba(0, 0, 0, 0.02);
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 12px;
            color: var(--color-cyan);
        }

        .preset-btn:hover .preset-icon, .preset-btn.active .preset-icon {
            background: var(--gradient-primary);
            color: #fff;
        }

        .preset-name { font-size: 12px; font-weight: 600; }

        .simulation-panel {
            align-self: stretch;
            display: flex;
            flex-direction: column;
            justify-content: flex-start !important;
            gap: 16px;
        }

        .canvas-wrapper {
            position: relative;
            width: 100%;
            background: radial-gradient(circle at center, #ffffff 0%, #f1f5f9 100%);
            border: 1px solid rgba(0, 0, 0, 0.06);
            border-radius: 16px;
            overflow: hidden;
            display: flex;
            align-items: center;
            justify-content: center;
            
        }

        #physics-canvas {
            display: block;
            width: 100%;
            height: auto;
            aspect-ratio: 16 / 12;
        }

        .canvas-overlay-data {
            position: absolute;
            top: 16px;
            left: 16px;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            gap: 6px;
        }

        .overlay-item {
            background: rgba(255, 255, 255, 0.9);
            color: var(--color-text-main);
            backdrop-filter: blur(4px);
            border: 1px solid var(--color-border);
            padding: 6px 12px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 11px;
        }

        .overlay-item .label { color: var(--color-text-muted); font-weight: 500; }
        .overlay-item .value { font-weight: 700; }

        .simulation-metrics-strip {
            display: flex;
            background: rgba(0, 0, 0, 0.01);
            border: 1px solid var(--color-border);
            border-radius: 12px;
            padding: 12px 20px;
            justify-content: space-around;
            align-items: center;
            gap: 10px;
            margin-top: 10px;
        }

        .mini-metric {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
            text-align: center;
        }

        .mini-metric .label {
            font-size: 10px;
            font-weight: 600;
            color: var(--color-text-muted);
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }

        .mini-metric .value {
            font-size: 14px;
            font-weight: 700;
            color: var(--color-text-main);
            font-family: var(--font-heading);
        }

        .mini-divider { width: 1px; height: 24px; background: var(--color-border); }

        .results-panel { grid-column: 3; }

        .ratio-readout-box {
            background: linear-gradient(135deg, var(--color-cyan-glow) 0%, var(--color-purple-glow) 100%);
            border: 1px solid rgba(2, 132, 199, 0.15);
            border-radius: 16px;
            padding: 20px;
            text-align: center;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 6px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02);
        }

        .ratio-title {
            font-size: 11px;
            font-weight: 700;
            color: var(--color-cyan);
            letter-spacing: 1px;
            text-transform: uppercase;
        }

        .ratio-value {
            font-family: var(--font-heading);
            font-weight: 800;
            font-size: 24px;
            background: linear-gradient(90deg, var(--color-text-main), var(--color-cyan));
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .ratio-type { font-size: 11px; font-weight: 600; color: var(--color-text-muted); }

        .results-grid { display: flex; flex-direction: column; gap: 12px; }

        .result-card {
            background: rgba(255, 255, 255, 0.85);
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
            border: 1px solid var(--color-border);
            border-radius: 12px;
            padding: 12px 16px;
            display: flex;
            align-items: center;
            gap: 16px;
            transition: all 0.2s ease;
        }

        .result-card:hover {
            transform: translateY(-2px);
            border-color: var(--color-cyan);
            background: var(--color-cyan-glow);
        }

        .card-icon {
            width: 38px;
            height: 38px;
            background: rgba(0, 0, 0, 0.015);
            border: 1px solid var(--color-border);
            border-radius: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 14px;
            color: var(--color-text-muted);
        }

        .result-card:hover .card-icon {
            color: var(--color-cyan);
            border-color: var(--color-cyan);
            background: var(--color-cyan-glow);
        }

        .card-content { display: flex; flex-direction: column; gap: 2px; }
        .card-unit { font-size: 11px; font-weight: 600; color: var(--color-text-muted); }
        .card-value { font-family: var(--font-heading); font-weight: 700; font-size: 17px; color: var(--color-text-main); }

        .formula-card {
            background: #ffffff;
            border: 1px solid var(--color-border);
            border-radius: 12px;
            padding: 16px;
            display: flex;
            flex-direction: column;
            gap: 8px;
            font-size: 12px;
        }

        .formula-card h4 { font-weight: 700; color: var(--color-text-main); }
        .formula-equation {
            font-family: 'Outfit', 'Cambria Math', 'Times New Roman', monospace;
            color: #0284c7;
            font-size: 13px;
            background: #f8fafc;
            border-color: var(--color-border);
            padding: 8px 10px;
            border-radius: 6px;
        }

        /* Container Query for Responsiveness inside WP */
        @container bolttorque-container (max-width: 1050px) {
            .app-main-grid {
                grid-template-columns: 1fr 1fr;
                gap: 20px;
            }
            .control-panel { 
                grid-column: 1; 
                grid-row: 1 / span 2; 
            }
            .simulation-panel { 
                grid-column: 2; 
                grid-row: 1; 
            }
            .results-panel { 
                grid-column: 2; 
                grid-row: 2; 
            }
            .results-grid { 
                display: flex; 
                flex-direction: column; 
                gap: 12px; 
            }
        }
        @container bolttorque-container (max-width: 768px) {
            .app-main-grid {
                grid-template-columns: 1fr;
            }
            .control-panel, .simulation-panel, .results-panel {
                grid-column: 1;
            }
            .simulation-panel { order: -1; }
            .results-grid { display: flex; flex-direction: column; gap: 12px; }
        }
    
</style>

<div class="bolttorque-calculator-wrapper" style="position: relative; width: 100%; box-sizing: border-box; overflow: hidden; margin: 30px auto; border-radius: 20px;">
    <div class="app-background-glow" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; overflow: hidden;"></div>
    <div style="position: relative; z-index: 2; width: 100%;">
        
    
    
    <div class="app-container">
        <!-- Header -->
        <header class="app-header">
            <div class="logo-area">
                <div class="logo-icon"><i class="fa-solid fa-screwdriver"></i></div>
                <div>
                    <h1>BOLT TORQUE</h1>
                    <div class="subtitle">볼트 체결 토크 및 축력 계산기 및 2D 시뮬레이터</div>
                </div>
            </div>
            <div class="header-badge">
                <div class="pulse-dot"></div>
                <div class="badge-text">VDI 2230 MODEL ACTIVE</div>
            </div>
        </header>

        <!-- Main Layout Grid -->
        <main class="app-main-grid">
            <!-- Left Panel: Controls -->
            <section class="panel control-panel">
                <div class="panel-header">
                    <i class="fa-solid fa-sliders text-cyan"></i>
                    <h2>체결 제어 변수</h2>
                </div>

                <!-- Calculation Mode -->
                <div class="mode-select-container">
                    <span class="mode-label">계산 방식 선택</span>
                    <div class="segmented-control">
                        <button class="segmented-btn active" id="btn-mode-t" title="토크로 축력 계산">토크 입력 모드</button>
                        <button class="segmented-btn" id="btn-mode-f" title="목표축력으로 토크 계산">목표축력 모드</button>
                    </div>
                </div>

                <!-- Bolt size -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="select-size"><i class="fa-solid fa-nut-bolt text-cyan"></i> 볼트 규격 (d)</label>
                    </div>
                    <div class="select-wrapper">
                        <select id="select-size" class="custom-select">
                            <option value="6">M6 (Pitch: 1.0mm, As: 20.1 mm²)</option>
                            <option value="8">M8 (Pitch: 1.25mm, As: 36.6 mm²)</option>
                            <option value="10" selected>M10 (Pitch: 1.5mm, As: 58.0 mm²)</option>
                            <option value="12">M12 (Pitch: 1.75mm, As: 84.3 mm²)</option>
                            <option value="16">M16 (Pitch: 2.0mm, As: 157.0 mm²)</option>
                            <option value="20">M20 (Pitch: 2.5mm, As: 245.0 mm²)</option>
                        </select>
                        <i class="fa-solid fa-chevron-down select-arrow"></i>
                    </div>
                </div>

                <!-- Strength Grade -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="select-grade"><i class="fa-solid fa-trophy text-purple"></i> 볼트 강도 등급</label>
                    </div>
                    <div class="select-wrapper">
                        <select id="select-grade" class="custom-select">
                            <option value="8.8">Grade 8.8 (항복 640 MPa)</option>
                            <option value="10.9" selected>Grade 10.9 (항복 900 MPa)</option>
                            <option value="12.9">Grade 12.9 (항복 1080 MPa)</option>
                        </select>
                        <i class="fa-solid fa-chevron-down select-arrow"></i>
                    </div>
                </div>

                <!-- Torque Coefficient K -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-k"><i class="fa-solid fa-oil-can text-cyan"></i> 토크 계수 (K)</label>
                        <span class="helper-text">(0.08 ~ 0.30)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-k" class="custom-number-input" min="0.08" max="0.30" value="0.20" step="any">
                        <div class="unit-badge">K</div>
                    </div>
                    <input type="range" id="slider-k" class="custom-slider slider-cyan" min="8" max="30" value="20">
                </div>

                <!-- Tightening Torque T -->
                <div class="input-group" id="group-torque">
                    <div class="input-label-row">
                        <label for="input-torque"><i class="fa-solid fa-wrench text-magenta"></i> 체결 토크 (T)</label>
                        <span class="helper-text" id="torque-range-label">(1.0 ~ 500.0 N·m)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-torque" class="custom-number-input" min="1" max="500" value="50" step="any">
                        <div class="unit-badge">N·m</div>
                    </div>
                    <input type="range" id="slider-torque" class="custom-slider slider-magenta" min="1" max="500" value="50">
                </div>

                <!-- Target Tension F -->
                <div class="input-group disabled" id="group-tension">
                    <div class="input-label-row">
                        <label for="input-tension"><i class="fa-solid fa-weight-hanging text-magenta"></i> 목표 축력 (Fi)</label>
                        <span class="helper-text" id="tension-range-label">(0.5 ~ 250.0 kN)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-tension" class="custom-number-input" min="0.5" max="250" value="25" step="any">
                        <div class="unit-badge">kN</div>
                    </div>
                    <input type="range" id="slider-tension" class="custom-slider slider-magenta" min="5" max="2500" value="250">
                </div>

                <!-- Presets -->
                <div class="presets-section">
                    <h3><i class="fa-solid fa-tags text-cyan"></i> 기계 체결부 프리셋</h3>
                    <div class="presets-grid">
                        <button class="preset-btn active" id="preset-dry">
                            <div class="preset-icon"><i class="fa-solid fa-industry"></i></div>
                            <div class="preset-name">표준 프레임 결합 (M10, Grade 10.9, 무윤활)</div>
                        </button>
                        <button class="preset-btn" id="preset-lubricated">
                            <div class="preset-icon"><i class="fa-solid fa-car"></i></div>
                            <div class="preset-name">엔진 실린더 헤드 볼트 (M12, Grade 12.9, 오일윤활)</div>
                        </button>
                        <button class="preset-btn" id="preset-structural">
                            <div class="preset-icon"><i class="fa-solid fa-building"></i></div>
                            <div class="preset-name">고중량 철골 구조물 (M20, Grade 8.8, 아연도금)</div>
                        </button>
                    </div>
                </div>
            </section>
    
            <!-- Center Panel: Physics Simulator -->
            <section class="panel simulation-panel">
                <div class="panel-header">
                    <i class="fa-solid fa-dharmachakra text-magenta"></i>
                    <h2>볼트 축 인장 및 피체 압축 상태 가시화</h2>
                </div>
                
                <div class="canvas-wrapper">
                    <canvas id="physics-canvas" width="640" height="400"></canvas>
                    <div class="canvas-overlay-data">
                        <div class="overlay-item">
                            <span class="label">체결 상태:</span>
                            <span class="value" id="overlay-safety-status">안전 (탄성변형 영역)</span>
                        </div>
                    </div>
                </div>

                <div class="simulation-metrics-strip">
                    <div class="mini-metric">
                        <span class="label">볼트 단면적 (As)</span>
                        <span class="value" id="val-tensile-area">58.0 mm²</span>
                    </div>
                    <div class="mini-divider"></div>
                    <div class="mini-metric">
                        <span class="label">나사 인장 응력</span>
                        <span class="value" id="val-bolt-stress">431.0 MPa</span>
                    </div>
                    <div class="mini-divider"></div>
                    <div class="mini-metric">
                        <span class="label">항복 대비 하중 비</span>
                        <span class="value" id="val-yield-ratio">47.9%</span>
                    </div>
                </div>
            </section>

            <!-- Right Panel: Results Analysis -->
            <section class="panel results-panel">
                <div class="panel-header">
                    <i class="fa-solid fa-chart-bar text-cyan"></i>
                    <h2>체결 하중 계산서</h2>
                </div>

                <div class="ratio-readout-box">
                    <div class="ratio-title" id="txt-readout-title">계산된 발생 축력 (Fi)</div>
                    <div class="ratio-value" id="txt-readout-val">25.0 kN</div>
                    <div class="ratio-type" id="txt-readout-sub">체결 토크: 50.0 N·m</div>
                </div>

                <div class="results-grid">
                    <div class="result-card">
                        <div class="card-icon"><i class="fa-solid fa-gauge-simple"></i></div>
                        <div class="card-content">
                            <span class="card-unit">볼트 재질 항복 강도</span>
                            <span class="card-value" id="res-yield">900 MPa</span>
                        </div>
                    </div>

                    <div class="result-card">
                        <div class="card-icon"><i class="fa-solid fa-triangle-exclamation"></i></div>
                        <div class="card-content">
                            <span class="card-unit">발생 인장 응력 (&sigma;)</span>
                            <span class="card-value" id="res-stress">431 MPa</span>
                        </div>
                    </div>

                    <div class="result-card">
                        <div class="card-icon"><i class="fa-solid fa-heart-circle-check"></i></div>
                        <div class="card-content">
                            <span class="card-unit">체결부 허용 안전 계수</span>
                            <span class="card-value" id="res-safety">2.09</span>
                        </div>
                    </div>
                </div>

                <div class="formula-card">
                    <h4><i class="fa-solid fa-info-circle text-cyan"></i> 볼트 체결 핵심 공식</h4>
                    <div class="formula-equation">
                        T = K × d × Fi
                    </div>
                    <div class="formula-equation">
                        &sigma; = Fi / As &nbsp;&nbsp;&lt; &sigma;_yield
                    </div>
                </div>
            </section>
        </main>
    </div>

    <!-- CORE INTERACTIVE ENGINE -->
    <script>
        (function() {
            if (window.__bolttorque_initialized) return;
            window.__bolttorque_initialized = true;

            const btnModeT = document.getElementById('btn-mode-t');
            const btnModeF = document.getElementById('btn-mode-f');
            const groupTorque = document.getElementById('group-torque');
            const groupTension = document.getElementById('group-tension');

            const selectSize = document.getElementById('select-size');
            const selectGrade = document.getElementById('select-grade');

            const inputK = document.getElementById('input-k');
            const sliderK = document.getElementById('slider-k');
            const inputTorque = document.getElementById('input-torque');
            const sliderTorque = document.getElementById('slider-torque');
            const inputTension = document.getElementById('input-tension');
            const sliderTension = document.getElementById('slider-tension');

            const txtReadoutTitle = document.getElementById('txt-readout-title');
            const txtReadoutVal = document.getElementById('txt-readout-val');
            const txtReadoutSub = document.getElementById('txt-readout-sub');

            const valTensileArea = document.getElementById('val-tensile-area');
            const valBoltStress = document.getElementById('val-bolt-stress');
            const valYieldRatio = document.getElementById('val-yield-ratio');

            const resYield = document.getElementById('res-yield');
            const resStress = document.getElementById('res-stress');
            const resSafety = document.getElementById('res-safety');
            const overlaySafetyStatus = document.getElementById('overlay-safety-status');

            const state = {
                mode: 'T', // 'T' = calc Preload from Torque, 'F' = calc Torque from Preload
                size: 10,  // M10
                grade: 10.9,
                k: 0.20,
                torque: 50.0,  // N·m
                tension: 25.0, // kN
                wrenchAngle: -0.8
            };

            const ranges = {
                k: { min: 0.08, max: 0.30 },
                torque: { min: 1.0, max: 500.0 },
                tension: { min: 0.5, max: 250.0 }
            };

            // Nominal details helper
            function getBoltDetails() {
                const s = parseInt(state.size);
                let p = 1.5;
                let As = 58.0;
                if (s === 6) { p = 1.0; As = 20.1; }
                if (s === 8) { p = 1.25; As = 36.6; }
                if (s === 12) { p = 1.75; As = 84.3; }
                if (s === 16) { p = 2.0; As = 157.0; }
                if (s === 20) { p = 2.5; As = 245.0; }
                return { pitch: p, area: As };
            }

            function getGradeDetails() {
                const g = parseFloat(state.grade);
                let sy = 900.0;
                if (g === 8.8) { sy = 640.0; }
                if (g === 12.9) { sy = 1080.0; }
                return { yieldStr: sy };
            }

            function calculateMetrics() {
                const b = getBoltDetails();
                const g = getGradeDetails();

                if (state.mode === 'T') {
                    // Preload Fi = T / (K * d)
                    // T in N*m, d in mm => Fi in kN
                    state.tension = state.torque / (state.k * state.size);
                    if (state.tension < ranges.tension.min) { state.tension = ranges.tension.min; }
                    if (state.tension > ranges.tension.max) { state.tension = ranges.tension.max; }
                } else {
                    // Torque T = K * d * Fi
                    state.torque = state.k * state.size * state.tension;
                    if (state.torque < ranges.torque.min) { state.torque = ranges.torque.min; }
                    if (state.torque > ranges.torque.max) { state.torque = ranges.torque.max; }
                }

                // Stress = Fi / As
                // Fi in kN => Stress in MPa
                const stress = (state.tension * 1000.0) / b.area;
                const safety = g.yieldStr / stress;
                const ratio = (stress / g.yieldStr) * 100.0;

                state.stress = stress;
                state.safety = safety;
                state.yieldRatio = ratio;

                updateUIValues();
            }

            function syncKFromInput() {
                let val = parseFloat(inputK.value);
                if (isNaN(val)) { val = ranges.k.min; }
                if (val < ranges.k.min) { val = ranges.k.min; }
                if (val > ranges.k.max) { val = ranges.k.max; }
                state.k = val;
                inputK.value = val.toFixed(2);
                sliderK.value = Math.round(val * 100);
                calculateMetrics();
            }
            function syncKFromSlider() {
                state.k = parseFloat(sliderK.value) / 100;
                inputK.value = state.k.toFixed(2);
                calculateMetrics();
            }

            function syncTorqueFromInput() {
                let val = parseFloat(inputTorque.value);
                if (isNaN(val)) { val = ranges.torque.min; }
                if (val < ranges.torque.min) { val = ranges.torque.min; }
                if (val > ranges.torque.max) { val = ranges.torque.max; }
                state.torque = val;
                inputTorque.value = val.toFixed(1);
                sliderTorque.value = Math.round(val);
                calculateMetrics();
            }
            function syncTorqueFromSlider() {
                state.torque = parseFloat(sliderTorque.value);
                inputTorque.value = state.torque.toFixed(1);
                calculateMetrics();
            }

            function syncTensionFromInput() {
                let val = parseFloat(inputTension.value);
                if (isNaN(val)) { val = ranges.tension.min; }
                if (val < ranges.tension.min) { val = ranges.tension.min; }
                if (val > ranges.tension.max) { val = ranges.tension.max; }
                state.tension = val;
                inputTension.value = val.toFixed(1);
                sliderTension.value = Math.round(val * 10);
                calculateMetrics();
            }
            function syncTensionFromSlider() {
                state.tension = parseFloat(sliderTension.value) / 10;
                inputTension.value = state.tension.toFixed(1);
                calculateMetrics();
            }

            function updateUIValues() {
                const b = getBoltDetails();
                const g = getGradeDetails();

                // Left panel updates
                if (state.mode === 'T') {
                    inputTension.value = state.tension.toFixed(1);
                    sliderTension.value = Math.round(state.tension * 10);
                } else {
                    inputTorque.value = state.torque.toFixed(1);
                    sliderTorque.value = Math.round(state.torque);
                }

                // Middle panel mini metrics
                valTensileArea.innerText = b.area.toFixed(1) + ' mm²';
                valBoltStress.innerText = state.stress.toFixed(0) + ' MPa';
                valYieldRatio.innerText = state.yieldRatio.toFixed(1) + '%';

                // Right panel readout box
                if (state.mode === 'T') {
                    txtReadoutTitle.innerText = '계산된 발생 축력 (Fi)';
                    txtReadoutVal.innerText = state.tension.toFixed(1) + ' kN';
                    txtReadoutSub.innerText = '적용 체결 토크: ' + state.torque.toFixed(1) + ' N·m';
                } else {
                    txtReadoutTitle.innerText = '필요 체결 토크 (T)';
                    txtReadoutVal.innerText = state.torque.toFixed(1) + ' N·m';
                    txtReadoutSub.innerText = '목표 설계 축력: ' + state.tension.toFixed(1) + ' kN';
                }

                resYield.innerText = g.yieldStr.toFixed(0) + ' MPa';
                resStress.innerText = state.stress.toFixed(0) + ' MPa';
                
                if (state.safety > 10.0) {
                    resSafety.innerText = '10.0+';
                } else {
                    resSafety.innerText = state.safety.toFixed(2);
                }

                // Safety check
                if (state.stress > g.yieldStr) {
                    overlaySafetyStatus.innerText = '항복 초과 (영구 변형/파손 위험)';
                    overlaySafetyStatus.style.color = '#db2777';
                } else {
                    if (state.yieldRatio > 85.0) {
                        overlaySafetyStatus.innerText = '경고 (탄성 한계 근접)';
                        overlaySafetyStatus.style.color = '#f59e0b';
                    } else {
                        overlaySafetyStatus.innerText = '안전 (적정 인장 범위)';
                        overlaySafetyStatus.style.color = '#10b981';
                    }
                }
            }

            function setMode(newMode) {
                state.mode = newMode;
                if (newMode === 'T') {
                    btnModeT.classList.add('active');
                    btnModeF.classList.remove('active');
                    groupTorque.classList.remove('disabled');
                    groupTension.classList.add('disabled');
                } else {
                    btnModeF.classList.add('active');
                    btnModeT.classList.remove('active');
                    groupTension.classList.remove('disabled');
                    groupTorque.classList.add('disabled');
                }
                calculateMetrics();
            }

            // Bind selectors
            selectSize.addEventListener('change', function() {
                state.size = parseInt(selectSize.value);
                calculateMetrics();
            });

            selectGrade.addEventListener('change', function() {
                state.grade = parseFloat(selectGrade.value);
                calculateMetrics();
            });

            btnModeT.addEventListener('click', function() { setMode('T'); });
            btnModeF.addEventListener('click', function() { setMode('F'); });

            inputK.addEventListener('change', syncKFromInput);
            sliderK.addEventListener('input', syncKFromSlider);

            inputTorque.addEventListener('change', syncTorqueFromInput);
            sliderTorque.addEventListener('input', syncTorqueFromSlider);

            inputTension.addEventListener('change', syncTensionFromInput);
            sliderTension.addEventListener('input', syncTensionFromSlider);

            // Presets
            document.getElementById('preset-dry').addEventListener('click', function() {
                setActivePreset('preset-dry');
                state.size = 10;
                state.grade = 10.9;
                state.k = 0.20;
                state.torque = 50.0;
                syncControlsToState();
                setMode('T');
            });

            document.getElementById('preset-lubricated').addEventListener('click', function() {
                setActivePreset('preset-lubricated');
                state.size = 12;
                state.grade = 12.9;
                state.k = 0.15;
                state.torque = 95.0;
                syncControlsToState();
                setMode('T');
            });

            document.getElementById('preset-structural').addEventListener('click', function() {
                setActivePreset('preset-structural');
                state.size = 20;
                state.grade = 8.8;
                state.k = 0.25;
                state.torque = 180.0;
                syncControlsToState();
                setMode('T');
            });

            function setActivePreset(id) {
                document.getElementById('preset-dry').classList.remove('active');
                document.getElementById('preset-lubricated').classList.remove('active');
                document.getElementById('preset-structural').classList.remove('active');
                document.getElementById(id).classList.add('active');
            }

            function syncControlsToState() {
                selectSize.value = state.size;
                selectGrade.value = state.grade.toFixed(1);
                inputK.value = state.k.toFixed(2);
                sliderK.value = Math.round(state.k * 100);
                inputTorque.value = state.torque.toFixed(1);
                sliderTorque.value = Math.round(state.torque);
            }

            // Canvas drawing
            const canvas = document.getElementById('physics-canvas');
            const ctx = canvas.getContext('2d');

            function getDPR() {
                return window.devicePixelRatio || 1;
            }

            function initCanvas() {
                const dpr = getDPR();
                const rect = canvas.getBoundingClientRect();
                canvas.width = rect.width * dpr;
                canvas.height = rect.height * dpr;
                ctx.scale(dpr, dpr);
            }

            window.addEventListener('resize', initCanvas);
            initCanvas();

            let lastTime = 0;
            function animate(currentTime) {
                if (lastTime === 0) { lastTime = currentTime; }
                const dt = (currentTime - lastTime) / 1000;
                lastTime = currentTime;

                const dpr = getDPR();
                const width = canvas.width / dpr;
                const height = canvas.height / dpr;

                // Animate rotating wrench based on torque magnitude
                if (state.torque > 1.0) {
                    state.wrenchAngle = -0.8 + Math.sin(currentTime * 0.003) * 0.03 * (state.torque / 250);
                }

                ctx.clearRect(0, 0, width, height);

                // Grid
                ctx.strokeStyle = 'rgba(2, 132, 199, 0.05)';
                ctx.lineWidth = 1;
                const gridSize = 25;
                for (let x = 0; x < width; x += gridSize) {
                    ctx.beginPath();
                    ctx.moveTo(x, 0);
                    ctx.lineTo(x, height);
                    ctx.stroke();
                }
                for (let y = 0; y < height; y += gridSize) {
                    ctx.beginPath();
                    ctx.moveTo(0, y);
                    ctx.lineTo(width, y);
                    ctx.stroke();
                }

                const cx = width / 2;
                const baseSize = Math.min(width, height);
                const scale = (baseSize / 400) * 0.95;
                const cy = height / 2 + 20 * scale; // Y축 오프셋을 살짝 내려 렌치가 상단 밖으로 탈출해 잘리는 문제를 방지합니다.

                // Stress factor
                const g = getGradeDetails();
                const stressFactor = Math.min(state.stress / g.yieldStr, 1.2);

                // 1. Draw Clamped Plates (Two steel blocks)
                ctx.save();
                ctx.fillStyle = '#f1f5f9';
                ctx.strokeStyle = '#cbd5e1';
                ctx.lineWidth = 2.5 * scale;

                // Top Plate
                ctx.beginPath();
                ctx.roundRect(cx - 150 * scale, cy - 60 * scale, 300 * scale, 56 * scale, 4 * scale);
                ctx.fill();
                ctx.stroke();

                // Bottom Plate
                ctx.fillStyle = '#e2e8f0';
                ctx.beginPath();
                ctx.roundRect(cx - 150 * scale, cy + 4 * scale, 300 * scale, 56 * scale, 4 * scale);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // 2. Draw Compression stress waves in plates
                if (state.tension > 1.0) {
                    ctx.save();
                    const clpIntensity = Math.min(state.tension / 100.0, 0.8);
                    const gradTop = ctx.createRadialGradient(cx, cy - 60 * scale, 10 * scale, cx, cy - 2 * scale, 70 * scale);
                    gradTop.addColorStop(0, 'rgba(2, 132, 199, ' + clpIntensity + ')');
                    gradTop.addColorStop(1, 'rgba(2, 132, 199, 0.0)');
                    
                    ctx.fillStyle = gradTop;
                    ctx.beginPath();
                    ctx.moveTo(cx - 70 * scale, cy - 60 * scale);
                    ctx.lineTo(cx + 70 * scale, cy - 60 * scale);
                    ctx.lineTo(cx + 120 * scale, cy);
                    ctx.lineTo(cx - 120 * scale, cy);
                    ctx.closePath();
                    ctx.fill();

                    const gradBot = ctx.createRadialGradient(cx, cy + 60 * scale, 10 * scale, cx, cy + 2 * scale, 70 * scale);
                    gradBot.addColorStop(0, 'rgba(2, 132, 199, ' + clpIntensity + ')');
                    gradBot.addColorStop(1, 'rgba(2, 132, 199, 0.0)');
                    
                    ctx.fillStyle = gradBot;
                    ctx.beginPath();
                    ctx.moveTo(cx - 70 * scale, cy + 60 * scale);
                    ctx.lineTo(cx + 70 * scale, cy + 60 * scale);
                    ctx.lineTo(cx + 120 * scale, cy);
                    ctx.lineTo(cx - 120 * scale, cy);
                    ctx.closePath();
                    ctx.fill();
                    ctx.restore();
                }

                // 3. Draw Bolt Shank (Bolt body inside plates)
                const bWidth = Math.min(state.size * 2.2 + 8, 56) * scale;
                ctx.save();
                ctx.beginPath();
                ctx.rect(cx - bWidth / 2, cy - 70 * scale, bWidth, 140 * scale);
                
                let shankColor = '#94a3b8';
                if (stressFactor > 0.05) {
                    if (stressFactor < 0.8) {
                        const r = Math.round(148 + (2 - 148) * (stressFactor / 0.8));
                        const g_col = Math.round(163 + (132 - 163) * (stressFactor / 0.8));
                        const b_col = Math.round(184 + (199 - 184) * (stressFactor / 0.8));
                        shankColor = 'rgb(' + r + ',' + g_col + ',' + b_col + ')';
                    } else {
                        const pct = Math.min((stressFactor - 0.8) / 0.4, 1.0);
                        const r = Math.round(2 + (219 - 2) * pct);
                        const g_col = Math.round(132 + (39 - 132) * pct);
                        const b_col = Math.round(199 + (119 - 199) * pct);
                        shankColor = 'rgb(' + r + ',' + g_col + ',' + b_col + ')';
                    }
                }
                
                ctx.fillStyle = shankColor;
                ctx.fill();
                ctx.strokeStyle = '#475569';
                ctx.lineWidth = 2 * scale;
                ctx.stroke();

                // Draw helical thread lines inside shank
                ctx.strokeStyle = 'rgba(0,0,0,0.1)';
                ctx.lineWidth = 1.5 * scale;
                for (let ty = cy - 50 * scale; ty <= cy + 50 * scale; ty += 8 * scale) {
                    ctx.beginPath();
                    ctx.moveTo(cx - bWidth / 2, ty);
                    ctx.lineTo(cx + bWidth / 2, ty + 3 * scale);
                    ctx.stroke();
                }
                ctx.restore();

                // 4. Draw Bolt Head (Top)
                const hWidth = bWidth * 1.7;
                ctx.save();
                ctx.fillStyle = '#64748b';
                ctx.strokeStyle = '#334155';
                ctx.lineWidth = 2.5 * scale;
                ctx.beginPath();
                ctx.roundRect(cx - hWidth / 2, cy - 86 * scale, hWidth, 26 * scale, 4 * scale);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // 5. Draw Nut (Bottom)
                ctx.save();
                ctx.fillStyle = '#64748b';
                ctx.strokeStyle = '#334155';
                ctx.lineWidth = 2.5 * scale;
                ctx.beginPath();
                ctx.roundRect(cx - hWidth / 2, cy + 60 * scale, hWidth, 26 * scale, 4 * scale);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // 6. Draw Wrench tool tightening bolt head
                ctx.save();
                ctx.translate(cx, cy - 73 * scale);
                ctx.rotate(state.wrenchAngle);
                
                ctx.strokeStyle = 'rgba(71, 85, 105, 0.9)';
                ctx.fillStyle = 'rgba(148, 163, 184, 0.9)';
                ctx.lineWidth = 3 * scale;
                
                // Draw Spanner handle
                ctx.beginPath();
                ctx.roundRect(40 * scale, -10 * scale, 160 * scale, 20 * scale, 6 * scale);
                ctx.fill();
                ctx.stroke();
                
                // Draw Wrench head open jaw
                ctx.beginPath();
                ctx.arc(0, 0, 44 * scale, -2.718, 2.718, false);
                ctx.lineTo(-40 * scale, 18 * scale);
                ctx.lineTo(-15 * scale, 18 * scale);
                ctx.lineTo(-15 * scale, -18 * scale);
                ctx.lineTo(-40 * scale, -18 * scale);
                ctx.closePath();
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // 7. Draw Torque rotating arrow vector (Arc around head)
                if (state.torque > 1.0) {
                    ctx.save();
                    ctx.strokeStyle = '#db2777';
                    ctx.fillStyle = '#db2777';
                    ctx.lineWidth = Math.min(state.torque * 0.015 + 2, 8) * scale;
                    ctx.shadowBlur = 10 * scale;
                    ctx.shadowColor = '#db2777';
                    
                    ctx.beginPath();
                    ctx.arc(cx, cy - 73 * scale, 56 * scale, -0.6, Math.PI * 0.8);
                    ctx.stroke();

                    // Arrowhead
                    ctx.beginPath();
                    ctx.moveTo(cx - (56 + 6) * scale, cy - 73 * scale);
                    ctx.lineTo(cx - 56 * scale, cy - (73 - 12) * scale);
                    ctx.lineTo(cx - (56 - 6) * scale, cy - 73 * scale);
                    ctx.closePath();
                    ctx.fill();
                    
                    ctx.fillStyle = '#db2777';
                    ctx.font = 'bold ' + Math.max(9, Math.round(11 * scale)) + 'px Inter';
                    ctx.fillText('Torque T = ' + state.torque.toFixed(1) + ' N·m', cx + 64 * scale, cy - 105 * scale);
                    ctx.restore();
                }

                // 8. Draw Tension Vector (Pulling forces inside bolt)
                if (state.tension > 1.0) {
                    ctx.save();
                    ctx.strokeStyle = '#0284c7';
                    ctx.fillStyle = '#0284c7';
                    ctx.lineWidth = Math.min(state.tension * 0.04 + 2, 7) * scale;
                    ctx.shadowBlur = 8 * scale;
                    ctx.shadowColor = '#0284c7';
                    
                    // Tension Force Arrows pointing outwards from center
                    ctx.beginPath();
                    ctx.moveTo(cx - 30 * scale, cy - 15 * scale);
                    ctx.lineTo(cx - 30 * scale, cy - 50 * scale);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(cx - 30 * scale, cy - 50 * scale);
                    ctx.lineTo(cx - 35 * scale, cy - 45 * scale);
                    ctx.lineTo(cx - 25 * scale, cy - 45 * scale);
                    ctx.closePath();
                    ctx.fill();

                    ctx.beginPath();
                    ctx.moveTo(cx - 30 * scale, cy + 15 * scale);
                    ctx.lineTo(cx - 30 * scale, cy + 50 * scale);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(cx - 30 * scale, cy + 50 * scale);
                    ctx.lineTo(cx - 35 * scale, cy + 45 * scale);
                    ctx.lineTo(cx - 25 * scale, cy + 45 * scale);
                    ctx.closePath();
                    ctx.fill();

                    ctx.fillStyle = '#0284c7';
                    ctx.font = 'bold ' + Math.max(9, Math.round(11 * scale)) + 'px Inter';
                    ctx.fillText('Tension Fi = ' + state.tension.toFixed(1) + ' kN', cx - 130 * scale, cy + 105 * scale);
                    ctx.restore();
                }

                // 9. Draw visual safety load bar on bottom of canvas
                ctx.save();
                ctx.fillStyle = 'rgba(255, 255, 255, 0.85)';
                ctx.roundRect(cx - 150 * scale, height - 35 * scale, 300 * scale, 20 * scale, 10 * scale);
                ctx.fill();
                ctx.strokeStyle = '#e2e8f0';
                ctx.lineWidth = 1 * scale;
                ctx.stroke();

                // Fill ratio
                const clipWidth = Math.min(stressFactor * 300.0, 300.0) * scale;
                let barColor = '#10b981';
                if (stressFactor > 0.85) { barColor = '#f59e0b'; }
                if (stressFactor > 1.0) { barColor = '#db2777'; }

                ctx.fillStyle = barColor;
                ctx.beginPath();
                ctx.roundRect(cx - 150 * scale, height - 35 * scale, clipWidth, 20 * scale, 10 * scale);
                ctx.fill();

                ctx.fillStyle = '#1e293b';
                ctx.font = 'bold ' + Math.max(8, Math.round(10 * scale)) + 'px Inter';
                ctx.textAlign = 'center';
                ctx.fillText('Tensile Stress Level vs Yield Strength: ' + state.yieldRatio.toFixed(1) + '%', cx, height - 21 * scale);
                ctx.restore();

                requestAnimationFrame(animate);
            }
            requestAnimationFrame(animate);

            // Initial calculation
            calculateMetrics();

            // Right click / Copy Protection
            (function() {
                function blockEvents() {
                    document.addEventListener('contextmenu', function(e) {
                        e.preventDefault();
                        alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                        return false;
                    }, { capture: true });
                    
                    document.addEventListener('selectstart', function(e) {
                        e.preventDefault();
                        return false;
                    }, { capture: true });
                }
                if (document.readyState === 'complete' || document.readyState === 'interactive') {
                    blockEvents();
                } else {
                    document.addEventListener('DOMContentLoaded', blockEvents);
                }
            })();

        })();
    </script>

    </div>
</div>

<script>
        (function() {
            if (window.__bolttorque_initialized) return;
            window.__bolttorque_initialized = true;

            const btnModeT = document.getElementById('btn-mode-t');
            const btnModeF = document.getElementById('btn-mode-f');
            const groupTorque = document.getElementById('group-torque');
            const groupTension = document.getElementById('group-tension');

            const selectSize = document.getElementById('select-size');
            const selectGrade = document.getElementById('select-grade');

            const inputK = document.getElementById('input-k');
            const sliderK = document.getElementById('slider-k');
            const inputTorque = document.getElementById('input-torque');
            const sliderTorque = document.getElementById('slider-torque');
            const inputTension = document.getElementById('input-tension');
            const sliderTension = document.getElementById('slider-tension');

            const txtReadoutTitle = document.getElementById('txt-readout-title');
            const txtReadoutVal = document.getElementById('txt-readout-val');
            const txtReadoutSub = document.getElementById('txt-readout-sub');

            const valTensileArea = document.getElementById('val-tensile-area');
            const valBoltStress = document.getElementById('val-bolt-stress');
            const valYieldRatio = document.getElementById('val-yield-ratio');

            const resYield = document.getElementById('res-yield');
            const resStress = document.getElementById('res-stress');
            const resSafety = document.getElementById('res-safety');
            const overlaySafetyStatus = document.getElementById('overlay-safety-status');

            const state = {
                mode: 'T', // 'T' = calc Preload from Torque, 'F' = calc Torque from Preload
                size: 10,  // M10
                grade: 10.9,
                k: 0.20,
                torque: 50.0,  // N·m
                tension: 25.0, // kN
                wrenchAngle: -0.8
            };

            const ranges = {
                k: { min: 0.08, max: 0.30 },
                torque: { min: 1.0, max: 500.0 },
                tension: { min: 0.5, max: 250.0 }
            };

            // Nominal details helper
            function getBoltDetails() {
                const s = parseInt(state.size);
                let p = 1.5;
                let As = 58.0;
                if (s === 6) { p = 1.0; As = 20.1; }
                if (s === 8) { p = 1.25; As = 36.6; }
                if (s === 12) { p = 1.75; As = 84.3; }
                if (s === 16) { p = 2.0; As = 157.0; }
                if (s === 20) { p = 2.5; As = 245.0; }
                return { pitch: p, area: As };
            }

            function getGradeDetails() {
                const g = parseFloat(state.grade);
                let sy = 900.0;
                if (g === 8.8) { sy = 640.0; }
                if (g === 12.9) { sy = 1080.0; }
                return { yieldStr: sy };
            }

            function calculateMetrics() {
                const b = getBoltDetails();
                const g = getGradeDetails();

                if (state.mode === 'T') {
                    // Preload Fi = T / (K * d)
                    // T in N*m, d in mm => Fi in kN
                    state.tension = state.torque / (state.k * state.size);
                    if (state.tension < ranges.tension.min) { state.tension = ranges.tension.min; }
                    if (state.tension > ranges.tension.max) { state.tension = ranges.tension.max; }
                } else {
                    // Torque T = K * d * Fi
                    state.torque = state.k * state.size * state.tension;
                    if (state.torque < ranges.torque.min) { state.torque = ranges.torque.min; }
                    if (state.torque > ranges.torque.max) { state.torque = ranges.torque.max; }
                }

                // Stress = Fi / As
                // Fi in kN => Stress in MPa
                const stress = (state.tension * 1000.0) / b.area;
                const safety = g.yieldStr / stress;
                const ratio = (stress / g.yieldStr) * 100.0;

                state.stress = stress;
                state.safety = safety;
                state.yieldRatio = ratio;

                updateUIValues();
            }

            function syncKFromInput() {
                let val = parseFloat(inputK.value);
                if (isNaN(val)) { val = ranges.k.min; }
                if (val < ranges.k.min) { val = ranges.k.min; }
                if (val > ranges.k.max) { val = ranges.k.max; }
                state.k = val;
                inputK.value = val.toFixed(2);
                sliderK.value = Math.round(val * 100);
                calculateMetrics();
            }
            function syncKFromSlider() {
                state.k = parseFloat(sliderK.value) / 100;
                inputK.value = state.k.toFixed(2);
                calculateMetrics();
            }

            function syncTorqueFromInput() {
                let val = parseFloat(inputTorque.value);
                if (isNaN(val)) { val = ranges.torque.min; }
                if (val < ranges.torque.min) { val = ranges.torque.min; }
                if (val > ranges.torque.max) { val = ranges.torque.max; }
                state.torque = val;
                inputTorque.value = val.toFixed(1);
                sliderTorque.value = Math.round(val);
                calculateMetrics();
            }
            function syncTorqueFromSlider() {
                state.torque = parseFloat(sliderTorque.value);
                inputTorque.value = state.torque.toFixed(1);
                calculateMetrics();
            }

            function syncTensionFromInput() {
                let val = parseFloat(inputTension.value);
                if (isNaN(val)) { val = ranges.tension.min; }
                if (val < ranges.tension.min) { val = ranges.tension.min; }
                if (val > ranges.tension.max) { val = ranges.tension.max; }
                state.tension = val;
                inputTension.value = val.toFixed(1);
                sliderTension.value = Math.round(val * 10);
                calculateMetrics();
            }
            function syncTensionFromSlider() {
                state.tension = parseFloat(sliderTension.value) / 10;
                inputTension.value = state.tension.toFixed(1);
                calculateMetrics();
            }

            function updateUIValues() {
                const b = getBoltDetails();
                const g = getGradeDetails();

                // Left panel updates
                if (state.mode === 'T') {
                    inputTension.value = state.tension.toFixed(1);
                    sliderTension.value = Math.round(state.tension * 10);
                } else {
                    inputTorque.value = state.torque.toFixed(1);
                    sliderTorque.value = Math.round(state.torque);
                }

                // Middle panel mini metrics
                valTensileArea.innerText = b.area.toFixed(1) + ' mm²';
                valBoltStress.innerText = state.stress.toFixed(0) + ' MPa';
                valYieldRatio.innerText = state.yieldRatio.toFixed(1) + '%';

                // Right panel readout box
                if (state.mode === 'T') {
                    txtReadoutTitle.innerText = '계산된 발생 축력 (Fi)';
                    txtReadoutVal.innerText = state.tension.toFixed(1) + ' kN';
                    txtReadoutSub.innerText = '적용 체결 토크: ' + state.torque.toFixed(1) + ' N·m';
                } else {
                    txtReadoutTitle.innerText = '필요 체결 토크 (T)';
                    txtReadoutVal.innerText = state.torque.toFixed(1) + ' N·m';
                    txtReadoutSub.innerText = '목표 설계 축력: ' + state.tension.toFixed(1) + ' kN';
                }

                resYield.innerText = g.yieldStr.toFixed(0) + ' MPa';
                resStress.innerText = state.stress.toFixed(0) + ' MPa';
                
                if (state.safety > 10.0) {
                    resSafety.innerText = '10.0+';
                } else {
                    resSafety.innerText = state.safety.toFixed(2);
                }

                // Safety check
                if (state.stress > g.yieldStr) {
                    overlaySafetyStatus.innerText = '항복 초과 (영구 변형/파손 위험)';
                    overlaySafetyStatus.style.color = '#db2777';
                } else {
                    if (state.yieldRatio > 85.0) {
                        overlaySafetyStatus.innerText = '경고 (탄성 한계 근접)';
                        overlaySafetyStatus.style.color = '#f59e0b';
                    } else {
                        overlaySafetyStatus.innerText = '안전 (적정 인장 범위)';
                        overlaySafetyStatus.style.color = '#10b981';
                    }
                }
            }

            function setMode(newMode) {
                state.mode = newMode;
                if (newMode === 'T') {
                    btnModeT.classList.add('active');
                    btnModeF.classList.remove('active');
                    groupTorque.classList.remove('disabled');
                    groupTension.classList.add('disabled');
                } else {
                    btnModeF.classList.add('active');
                    btnModeT.classList.remove('active');
                    groupTension.classList.remove('disabled');
                    groupTorque.classList.add('disabled');
                }
                calculateMetrics();
            }

            // Bind selectors
            selectSize.addEventListener('change', function() {
                state.size = parseInt(selectSize.value);
                calculateMetrics();
            });

            selectGrade.addEventListener('change', function() {
                state.grade = parseFloat(selectGrade.value);
                calculateMetrics();
            });

            btnModeT.addEventListener('click', function() { setMode('T'); });
            btnModeF.addEventListener('click', function() { setMode('F'); });

            inputK.addEventListener('change', syncKFromInput);
            sliderK.addEventListener('input', syncKFromSlider);

            inputTorque.addEventListener('change', syncTorqueFromInput);
            sliderTorque.addEventListener('input', syncTorqueFromSlider);

            inputTension.addEventListener('change', syncTensionFromInput);
            sliderTension.addEventListener('input', syncTensionFromSlider);

            // Presets
            document.getElementById('preset-dry').addEventListener('click', function() {
                setActivePreset('preset-dry');
                state.size = 10;
                state.grade = 10.9;
                state.k = 0.20;
                state.torque = 50.0;
                syncControlsToState();
                setMode('T');
            });

            document.getElementById('preset-lubricated').addEventListener('click', function() {
                setActivePreset('preset-lubricated');
                state.size = 12;
                state.grade = 12.9;
                state.k = 0.15;
                state.torque = 95.0;
                syncControlsToState();
                setMode('T');
            });

            document.getElementById('preset-structural').addEventListener('click', function() {
                setActivePreset('preset-structural');
                state.size = 20;
                state.grade = 8.8;
                state.k = 0.25;
                state.torque = 180.0;
                syncControlsToState();
                setMode('T');
            });

            function setActivePreset(id) {
                document.getElementById('preset-dry').classList.remove('active');
                document.getElementById('preset-lubricated').classList.remove('active');
                document.getElementById('preset-structural').classList.remove('active');
                document.getElementById(id).classList.add('active');
            }

            function syncControlsToState() {
                selectSize.value = state.size;
                selectGrade.value = state.grade.toFixed(1);
                inputK.value = state.k.toFixed(2);
                sliderK.value = Math.round(state.k * 100);
                inputTorque.value = state.torque.toFixed(1);
                sliderTorque.value = Math.round(state.torque);
            }

            // Canvas drawing
            const canvas = document.getElementById('physics-canvas');
            const ctx = canvas.getContext('2d');

            function getDPR() {
                return window.devicePixelRatio || 1;
            }

            function initCanvas() {
                const dpr = getDPR();
                const rect = canvas.getBoundingClientRect();
                canvas.width = rect.width * dpr;
                canvas.height = rect.height * dpr;
                ctx.scale(dpr, dpr);
            }

            window.addEventListener('resize', initCanvas);
            initCanvas();

            let lastTime = 0;
            function animate(currentTime) {
                if (lastTime === 0) { lastTime = currentTime; }
                const dt = (currentTime - lastTime) / 1000;
                lastTime = currentTime;

                const dpr = getDPR();
                const width = canvas.width / dpr;
                const height = canvas.height / dpr;

                // Animate rotating wrench based on torque magnitude
                if (state.torque > 1.0) {
                    state.wrenchAngle = -0.8 + Math.sin(currentTime * 0.003) * 0.03 * (state.torque / 250);
                }

                ctx.clearRect(0, 0, width, height);

                // Grid
                ctx.strokeStyle = 'rgba(2, 132, 199, 0.05)';
                ctx.lineWidth = 1;
                const gridSize = 25;
                for (let x = 0; x < width; x += gridSize) {
                    ctx.beginPath();
                    ctx.moveTo(x, 0);
                    ctx.lineTo(x, height);
                    ctx.stroke();
                }
                for (let y = 0; y < height; y += gridSize) {
                    ctx.beginPath();
                    ctx.moveTo(0, y);
                    ctx.lineTo(width, y);
                    ctx.stroke();
                }

                const cx = width / 2;
                const baseSize = Math.min(width, height);
                const scale = (baseSize / 400) * 0.95;
                const cy = height / 2 + 20 * scale; // Y축 오프셋을 살짝 내려 렌치가 상단 밖으로 탈출해 잘리는 문제를 방지합니다.

                // Stress factor
                const g = getGradeDetails();
                const stressFactor = Math.min(state.stress / g.yieldStr, 1.2);

                // 1. Draw Clamped Plates (Two steel blocks)
                ctx.save();
                ctx.fillStyle = '#f1f5f9';
                ctx.strokeStyle = '#cbd5e1';
                ctx.lineWidth = 2.5 * scale;

                // Top Plate
                ctx.beginPath();
                ctx.roundRect(cx - 150 * scale, cy - 60 * scale, 300 * scale, 56 * scale, 4 * scale);
                ctx.fill();
                ctx.stroke();

                // Bottom Plate
                ctx.fillStyle = '#e2e8f0';
                ctx.beginPath();
                ctx.roundRect(cx - 150 * scale, cy + 4 * scale, 300 * scale, 56 * scale, 4 * scale);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // 2. Draw Compression stress waves in plates
                if (state.tension > 1.0) {
                    ctx.save();
                    const clpIntensity = Math.min(state.tension / 100.0, 0.8);
                    const gradTop = ctx.createRadialGradient(cx, cy - 60 * scale, 10 * scale, cx, cy - 2 * scale, 70 * scale);
                    gradTop.addColorStop(0, 'rgba(2, 132, 199, ' + clpIntensity + ')');
                    gradTop.addColorStop(1, 'rgba(2, 132, 199, 0.0)');
                    
                    ctx.fillStyle = gradTop;
                    ctx.beginPath();
                    ctx.moveTo(cx - 70 * scale, cy - 60 * scale);
                    ctx.lineTo(cx + 70 * scale, cy - 60 * scale);
                    ctx.lineTo(cx + 120 * scale, cy);
                    ctx.lineTo(cx - 120 * scale, cy);
                    ctx.closePath();
                    ctx.fill();

                    const gradBot = ctx.createRadialGradient(cx, cy + 60 * scale, 10 * scale, cx, cy + 2 * scale, 70 * scale);
                    gradBot.addColorStop(0, 'rgba(2, 132, 199, ' + clpIntensity + ')');
                    gradBot.addColorStop(1, 'rgba(2, 132, 199, 0.0)');
                    
                    ctx.fillStyle = gradBot;
                    ctx.beginPath();
                    ctx.moveTo(cx - 70 * scale, cy + 60 * scale);
                    ctx.lineTo(cx + 70 * scale, cy + 60 * scale);
                    ctx.lineTo(cx + 120 * scale, cy);
                    ctx.lineTo(cx - 120 * scale, cy);
                    ctx.closePath();
                    ctx.fill();
                    ctx.restore();
                }

                // 3. Draw Bolt Shank (Bolt body inside plates)
                const bWidth = Math.min(state.size * 2.2 + 8, 56) * scale;
                ctx.save();
                ctx.beginPath();
                ctx.rect(cx - bWidth / 2, cy - 70 * scale, bWidth, 140 * scale);
                
                let shankColor = '#94a3b8';
                if (stressFactor > 0.05) {
                    if (stressFactor < 0.8) {
                        const r = Math.round(148 + (2 - 148) * (stressFactor / 0.8));
                        const g_col = Math.round(163 + (132 - 163) * (stressFactor / 0.8));
                        const b_col = Math.round(184 + (199 - 184) * (stressFactor / 0.8));
                        shankColor = 'rgb(' + r + ',' + g_col + ',' + b_col + ')';
                    } else {
                        const pct = Math.min((stressFactor - 0.8) / 0.4, 1.0);
                        const r = Math.round(2 + (219 - 2) * pct);
                        const g_col = Math.round(132 + (39 - 132) * pct);
                        const b_col = Math.round(199 + (119 - 199) * pct);
                        shankColor = 'rgb(' + r + ',' + g_col + ',' + b_col + ')';
                    }
                }
                
                ctx.fillStyle = shankColor;
                ctx.fill();
                ctx.strokeStyle = '#475569';
                ctx.lineWidth = 2 * scale;
                ctx.stroke();

                // Draw helical thread lines inside shank
                ctx.strokeStyle = 'rgba(0,0,0,0.1)';
                ctx.lineWidth = 1.5 * scale;
                for (let ty = cy - 50 * scale; ty <= cy + 50 * scale; ty += 8 * scale) {
                    ctx.beginPath();
                    ctx.moveTo(cx - bWidth / 2, ty);
                    ctx.lineTo(cx + bWidth / 2, ty + 3 * scale);
                    ctx.stroke();
                }
                ctx.restore();

                // 4. Draw Bolt Head (Top)
                const hWidth = bWidth * 1.7;
                ctx.save();
                ctx.fillStyle = '#64748b';
                ctx.strokeStyle = '#334155';
                ctx.lineWidth = 2.5 * scale;
                ctx.beginPath();
                ctx.roundRect(cx - hWidth / 2, cy - 86 * scale, hWidth, 26 * scale, 4 * scale);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // 5. Draw Nut (Bottom)
                ctx.save();
                ctx.fillStyle = '#64748b';
                ctx.strokeStyle = '#334155';
                ctx.lineWidth = 2.5 * scale;
                ctx.beginPath();
                ctx.roundRect(cx - hWidth / 2, cy + 60 * scale, hWidth, 26 * scale, 4 * scale);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // 6. Draw Wrench tool tightening bolt head
                ctx.save();
                ctx.translate(cx, cy - 73 * scale);
                ctx.rotate(state.wrenchAngle);
                
                ctx.strokeStyle = 'rgba(71, 85, 105, 0.9)';
                ctx.fillStyle = 'rgba(148, 163, 184, 0.9)';
                ctx.lineWidth = 3 * scale;
                
                // Draw Spanner handle
                ctx.beginPath();
                ctx.roundRect(40 * scale, -10 * scale, 160 * scale, 20 * scale, 6 * scale);
                ctx.fill();
                ctx.stroke();
                
                // Draw Wrench head open jaw
                ctx.beginPath();
                ctx.arc(0, 0, 44 * scale, -2.718, 2.718, false);
                ctx.lineTo(-40 * scale, 18 * scale);
                ctx.lineTo(-15 * scale, 18 * scale);
                ctx.lineTo(-15 * scale, -18 * scale);
                ctx.lineTo(-40 * scale, -18 * scale);
                ctx.closePath();
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // 7. Draw Torque rotating arrow vector (Arc around head)
                if (state.torque > 1.0) {
                    ctx.save();
                    ctx.strokeStyle = '#db2777';
                    ctx.fillStyle = '#db2777';
                    ctx.lineWidth = Math.min(state.torque * 0.015 + 2, 8) * scale;
                    ctx.shadowBlur = 10 * scale;
                    ctx.shadowColor = '#db2777';
                    
                    ctx.beginPath();
                    ctx.arc(cx, cy - 73 * scale, 56 * scale, -0.6, Math.PI * 0.8);
                    ctx.stroke();

                    // Arrowhead
                    ctx.beginPath();
                    ctx.moveTo(cx - (56 + 6) * scale, cy - 73 * scale);
                    ctx.lineTo(cx - 56 * scale, cy - (73 - 12) * scale);
                    ctx.lineTo(cx - (56 - 6) * scale, cy - 73 * scale);
                    ctx.closePath();
                    ctx.fill();
                    
                    ctx.fillStyle = '#db2777';
                    ctx.font = 'bold ' + Math.max(9, Math.round(11 * scale)) + 'px Inter';
                    ctx.fillText('Torque T = ' + state.torque.toFixed(1) + ' N·m', cx + 64 * scale, cy - 105 * scale);
                    ctx.restore();
                }

                // 8. Draw Tension Vector (Pulling forces inside bolt)
                if (state.tension > 1.0) {
                    ctx.save();
                    ctx.strokeStyle = '#0284c7';
                    ctx.fillStyle = '#0284c7';
                    ctx.lineWidth = Math.min(state.tension * 0.04 + 2, 7) * scale;
                    ctx.shadowBlur = 8 * scale;
                    ctx.shadowColor = '#0284c7';
                    
                    // Tension Force Arrows pointing outwards from center
                    ctx.beginPath();
                    ctx.moveTo(cx - 30 * scale, cy - 15 * scale);
                    ctx.lineTo(cx - 30 * scale, cy - 50 * scale);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(cx - 30 * scale, cy - 50 * scale);
                    ctx.lineTo(cx - 35 * scale, cy - 45 * scale);
                    ctx.lineTo(cx - 25 * scale, cy - 45 * scale);
                    ctx.closePath();
                    ctx.fill();

                    ctx.beginPath();
                    ctx.moveTo(cx - 30 * scale, cy + 15 * scale);
                    ctx.lineTo(cx - 30 * scale, cy + 50 * scale);
                    ctx.stroke();
                    ctx.beginPath();
                    ctx.moveTo(cx - 30 * scale, cy + 50 * scale);
                    ctx.lineTo(cx - 35 * scale, cy + 45 * scale);
                    ctx.lineTo(cx - 25 * scale, cy + 45 * scale);
                    ctx.closePath();
                    ctx.fill();

                    ctx.fillStyle = '#0284c7';
                    ctx.font = 'bold ' + Math.max(9, Math.round(11 * scale)) + 'px Inter';
                    ctx.fillText('Tension Fi = ' + state.tension.toFixed(1) + ' kN', cx - 130 * scale, cy + 105 * scale);
                    ctx.restore();
                }

                // 9. Draw visual safety load bar on bottom of canvas
                ctx.save();
                ctx.fillStyle = 'rgba(255, 255, 255, 0.85)';
                ctx.roundRect(cx - 150 * scale, height - 35 * scale, 300 * scale, 20 * scale, 10 * scale);
                ctx.fill();
                ctx.strokeStyle = '#e2e8f0';
                ctx.lineWidth = 1 * scale;
                ctx.stroke();

                // Fill ratio
                const clipWidth = Math.min(stressFactor * 300.0, 300.0) * scale;
                let barColor = '#10b981';
                if (stressFactor > 0.85) { barColor = '#f59e0b'; }
                if (stressFactor > 1.0) { barColor = '#db2777'; }

                ctx.fillStyle = barColor;
                ctx.beginPath();
                ctx.roundRect(cx - 150 * scale, height - 35 * scale, clipWidth, 20 * scale, 10 * scale);
                ctx.fill();

                ctx.fillStyle = '#1e293b';
                ctx.font = 'bold ' + Math.max(8, Math.round(10 * scale)) + 'px Inter';
                ctx.textAlign = 'center';
                ctx.fillText('Tensile Stress Level vs Yield Strength: ' + state.yieldRatio.toFixed(1) + '%', cx, height - 21 * scale);
                ctx.restore();

                requestAnimationFrame(animate);
            }
            requestAnimationFrame(animate);

            // Initial calculation
            calculateMetrics();

            // Right click / Copy Protection
            (function() {
                function blockEvents() {
                    document.addEventListener('contextmenu', function(e) {
                        e.preventDefault();
                        alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                        return false;
                    }, { capture: true });
                    
                    document.addEventListener('selectstart', function(e) {
                        e.preventDefault();
                        return false;
                    }, { capture: true });
                }
                if (document.readyState === 'complete' || document.readyState === 'interactive') {
                    blockEvents();
                } else {
                    document.addEventListener('DOMContentLoaded', blockEvents);
                }
            })();

        })();
    </script>




<div style="background: linear-gradient(135deg, rgba(0,242,254,0.03), rgba(138,43,226,0.03)); border: 1px solid rgba(0,242,254,0.15); border-radius: 12px; padding: 18px 24px; margin: 25px auto 35px auto; font-size: 0.95em; color: #4b5563; line-height: 1.7; font-family: sans-serif;">
    <strong style="color: #1f2937; font-size: 1.05em; display: flex; align-items: center; gap: 8px;">
        <span style="font-size: 1.2em;"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /></span> 간편 사용 설명서
    </strong>
    <ol style="margin: 10px 0 0 0; padding-left: 20px;">
        <li style="margin-bottom: 6px;"><strong>계산 방식 설정: &#8216;토크 입력 시 축력 계산&#8217; 모드 또는 &#8216;목표 축력 입력 시 요구 토크 계산&#8217; 모드 중 선택합니다.</strong></li>
<li style="margin-bottom: 6px;">볼트 규격 및 강도 설정: 볼트 호칭 규격(M6 ~ M20)과 탄소강 강도 등급(8.8, 10.9, 12.9)을 지정합니다.</li>
<li style="margin-bottom: 6px;">토크 계수(K) 지정: 볼트 나사산의 윤활 조건에 맞춰 토크 계수(마찰 계수)를 슬라이더 또는 세그먼트 버튼으로 조절합니다.</li>
<li style="margin-bottom: 6px;">체결 변수 입력: 선택한 모드에 맞춰 체결 토크 또는 목표 축력을 제어합니다.</li>
<li style="margin-bottom: 6px;">시각 응력 및 파손 유무 검증: 2D 단면도 내부의 볼트 축부의 인장 응력 플로우(Red/Purple Glow) 및 플레이트의 압축 응력 구름을 관찰하고, 항복 응력 초과에 따른 소성 변형 위험도를 확인합니다.</li>
    </ol>
</div>



<details class="premium-seo-accordion" style="border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; background: #fbfbfc; padding: 0; margin: 30px auto; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.01); font-family: sans-serif;">
    <summary style="display: flex; justify-content: space-between; align-items: center; padding: 20px 24px; font-size: 1.1em; font-weight: 700; color: #1f2937; cursor: pointer; user-select: none; outline: none; list-style: none;">
        <span><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 상세 기계공학 해설 및 체결 설계 규격 (VDI 2230) 확인하기</span>
        <span class="accordion-arrow" style="font-size: 0.9em; color: #9ca3af; transition: transform 0.2s ease;">▼</span>
    </summary>
    <div style="padding: 0 24px 24px 24px; border-top: 1px solid rgba(0,0,0,0.04); background: #ffffff; border-radius: 0 0 12px 12px; font-size: 0.98em; color: #374151; line-height: 1.8;">
        <div style="margin-top: 20px;">
            <h3>1. 나사 체결의 물리학적 원리와 토크-축력 관계식</h3>
<p>볼트 체결(Bolted Joint)은 회전하는 토크(Torque)를 나사산의 경사면을 통해 강력한 축 방향의 인장 힘인 <strong>축력(Tension / Preload)</strong>으로 변환하는 대표적인 기계식 쐐기 장치입니다. 볼트에 적절한 축력을 주어 부품들을 단단히 밀착시켜야만 외부 인장 부하 및 반복 전단 부하 속에서 나사가 풀리지 않고 유지될 수 있습니다.</p><p>실무에서 축력 <code>F<sub>i</sub></code>와 체결 토크 <code>T</code> 간의 정밀한 해석은 복잡한 마찰 조건(자리면 마찰 및 나사산 마찰)을 수반하나, 일반적으로 다음과 같은 <strong>토크 계수 공식(Nut Factor Equation)</strong>이 가장 널리 사용됩니다:</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">T = K &middot; d &middot; F<sub>i</sub></p><p>여기서 인자들은 다음과 같습니다:</p><ul><li><strong>T: 체결 토크 (Tightening Torque, N&middot;m)</strong></li><li><strong>F<sub>i</sub>: 볼트 체결 축력 (Preload, N)</strong></li><li><strong>d: 볼트의 호칭 지름 (Nominal Diameter, m)</strong></li><li><strong>K: 토크 계수 (Torque Coefficient / Nut Factor)</strong> &#8211; 무차원 상수로서 나사산각, 피치, 자리면 윤활상태에 의해 결정됩니다. 통상 무윤활 강철은 0.20, 오일 윤활 상태는 0.15, 극압 그리스(Anti-seize) 도포 시에는 0.10 내외로 감소합니다.</li></ul>
<h3>2. 볼트 강도 등급 및 단면적과 허용 체결력</h3>
<p>볼트가 안전하게 축력을 발휘하기 위해서는 체결 시 발생하는 인장 응력과 비틀림 전단 응력의 합성 응력이 볼트 재질의 항복 강도를 초과하지 않아야 합니다. ISO 898-1 규격에 의거한 강도 등급(Strength Class) 표시법은 다음과 같습니다:</p><ul><li><strong>Grade 8.8:</strong> 인장강도 800 MPa, 항복강도 640 MPa (800 &times; 0.8)</li><li><strong>Grade 10.9:</strong> 인장강도 1000 MPa, 항복강도 900 MPa (1000 &times; 0.9)</li><li><strong>Grade 12.9:</strong> 인장강도 1200 MPa, 항복강도 1080 MPa (1200 &times; 0.9)</li></ul><p>볼트 축부의 유효 단면적인 <strong>인장 응력 단면적 (Tensile Stress Area, A<sub>s</sub>)</strong>은 나사골 지름과 피치를 고려해 다음과 같이 정의되며, 이 단면적에 기초해 볼트의 <strong>보증 하중(Proof Load)</strong>이 최종 계산됩니다:</p><p style="text-align: center; font-weight: bold; background: #e0f2fe; padding: 16px; border-radius: 8px; font-size: 1.1em; color: #0369a1;">A<sub>s</sub> = &pi;/4 &times; (d &#8211; 0.9382 &middot; P)<sup>2</sup> &nbsp;[mm<sup>2</sup>]</p><p>일반적으로 안전한 기계 설계를 위해 최대 가해지는 정적 체결 축력은 볼트 항복 강도 도달점의 <strong>75% ~ 90% 이내</strong>(탄성 영역)로 엄격히 관리되어야 하며, 이를 초과할 경우 나사산의 소성 변형 및 파손 위험이 극대화됩니다.</p>
<h3>3. 마찰 산포와 토크 제어 체결법의 한계</h3>
<p>토크 렌치를 이용한 단순 토크 제어법(Torque Control Tightening)은 인가하는 체결 에너지의 약 <strong>90%가 마찰열(나사산 마찰 50%, 자리면 마찰 40%)로 손실</strong>되고, 단 10%만이 유효한 체결 축력으로 변환된다는 기계공학적 한계가 존재합니다. 따라서 접촉면의 윤활 상태가 불균일할 경우 체결 축력에 <u>&plusmn;30% 이상의 큰 산포</u>가 발생하게 되므로 극단적인 정밀도가 요구되는 엔진 헤드 볼트 등에는 회전 각도법(Torque-Angle Control)이 병행 사용됩니다.</p>

        </div>
    </div>
</details>
<style>
details.premium-seo-accordion[open] summary .accordion-arrow { transform: rotate(180deg); color: #00f2fe; }
details.premium-seo-accordion summary::-webkit-details-marker { display: none; }
details.premium-seo-accordion:hover { border-color: rgba(0,242,254,0.3); }
</style>

]]></content:encoded>
					
					<wfw:commentRss>https://myengnote.com/bolt-torque-tension-calculator-simulator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
