<?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/%ec%99%80%ec%95%8c%ea%b3%84%ec%88%98/feed/" rel="self" type="application/rss+xml" />
	<link>https://myengnote.com</link>
	<description></description>
	<lastBuildDate>Fri, 29 May 2026 04:29:32 +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/spring-deflection-stiffness-calculator-simulator/</link>
					<comments>https://myengnote.com/spring-deflection-stiffness-calculator-simulator/#respond</comments>
		
		<dc:creator><![CDATA[동동]]></dc:creator>
		<pubDate>Fri, 29 May 2026 00:32:41 +0000</pubDate>
				<category><![CDATA[공학계산기]]></category>
		<category><![CDATA[기계설계]]></category>
		<category><![CDATA[변형량계산]]></category>
		<category><![CDATA[스프링상수]]></category>
		<category><![CDATA[와알계수]]></category>
		<category><![CDATA[코일스프링]]></category>
		<guid isPermaLink="false">https://myengnote.com/spring-deflection-stiffness-calculator-simulator/</guid>

					<description><![CDATA[소선 지름, 코일 평균 지름, 유효 권수 및 가해진 하중에 따른 코일 스프링의 강성(Stiffness), 처짐량(Deflection), Wahl 보정 응력을 정밀 계산하고 스프링의 실시간 기하학적 압축 상태를 시각화하는 2D 시뮬레이터입니다. <a href="https://myengnote.com/spring-deflection-stiffness-calculator-simulator/" style="text-decoration:none; color:#0073aa; font-weight:bold;">[본문 전체보기 >]</a>]]></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 springdeflection-calculator-wrapper */

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

        .springdeflection-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);
        }

        .springdeflection-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;
        }

        .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(124, 58, 237, 0.1); border: 1px solid rgba(124, 58, 237, 0.2); padding: 6px 14px; border-radius: 20px;
        }
        .pulse-dot {
            width: 8px; height: 8px; background-color: var(--color-purple); border-radius: 50%; box-shadow: 0 0 10px var(--color-purple); 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-purple); letter-spacing: 0.5px; }

        .app-main-grid { display: grid; grid-template-columns: 340px 1fr; 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; }

        .right-column {
            display: flex;
            flex-direction: column;
            gap: 24px;
            min-width: 0;
        }

        .input-group { display: flex; flex-direction: column; gap: 8px; }
        .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; justify-content: space-between; }

        .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 / 10; }

        .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 { }

        .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;
        }

        @media (max-width: 800px) {
            .app-main-grid { grid-template-columns: 1fr; }
            .control-panel { order: 2; }
            .right-column { order: 1; }
        }
    
</style>

<div class="springdeflection-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-wave-square"></i></div>
                <div>
                    <h1>SPRING DEFLECTION</h1>
                    <div class="subtitle">코일 스프링 처짐량 계산기 및 실시간 2D 시뮬레이터</div>
                </div>
            </div>
            <div class="header-badge">
                <div class="pulse-dot"></div>
                <div class="badge-text">SPRING PHYSICS 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>

                <!-- Material select -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="select-material"><i class="fa-solid fa-atom text-cyan"></i> 스프링 재료 (G)</label>
                    </div>
                    <div class="select-wrapper">
                        <select id="select-material" class="custom-select">
                            <option value="79.3" selected>탄소 스프링강 (G = 79.3 GPa)</option>
                            <option value="69.0">스테인리스강 (G = 69.0 GPa)</option>
                            <option value="78.5">크롬 바나듐강 (G = 78.5 GPa)</option>
                            <option value="41.0">인청동 (G = 41.0 GPa)</option>
                        </select>
                        <i class="fa-solid fa-chevron-down select-arrow"></i>
                    </div>
                </div>

                <!-- Wire diameter d -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-d"><i class="fa-solid fa-circle text-magenta"></i> 소선 지름 (d)</label>
                        <span class="helper-text">(0.5 ~ 20.0 mm)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-d" class="custom-number-input" min="0.5" max="20.0" value="4.0" step="any">
                        <div class="unit-badge">mm</div>
                    </div>
                    <input type="range" id="slider-d" class="custom-slider slider-magenta" min="5" max="200" value="40">
                </div>

                <!-- Mean diameter D -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-mean-d"><i class="fa-solid fa-circle-notch text-cyan"></i> 코일 평균 지름 (D)</label>
                        <span class="helper-text">(5.0 ~ 200.0 mm)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-mean-d" class="custom-number-input" min="5" max="200" value="30.0" step="any">
                        <div class="unit-badge">mm</div>
                    </div>
                    <input type="range" id="slider-mean-d" class="custom-slider slider-cyan" min="50" max="2000" value="300">
                </div>

                <!-- Active Coils n -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-n"><i class="fa-solid fa-calculator text-purple"></i> 유효 감김수 (n)</label>
                        <span class="helper-text">(2 ~ 30 회)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-n" class="custom-number-input" min="2" max="30" value="8" step="any">
                        <div class="unit-badge">회</div>
                    </div>
                    <input type="range" id="slider-n" class="custom-slider slider-purple" min="2" max="30" value="8">
                </div>

                <!-- Load Force F -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-force"><i class="fa-solid fa-weight-hanging text-magenta"></i> 인가 압축 하중 (F)</label>
                        <span class="helper-text">(0 ~ 2000 N)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-force" class="custom-number-input" min="0" max="2000" value="250" step="any">
                        <div class="unit-badge">N</div>
                    </div>
                    <input type="range" id="slider-force" class="custom-slider slider-magenta" min="0" max="2000" 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-auto">
                            <div class="preset-icon"><i class="fa-solid fa-car"></i></div>
                            <div class="preset-name">차량 서스펜션 (고강성 대형 스프링)</div>
                        </button>
                        <button class="preset-btn" id="preset-valve">
                            <div class="preset-icon"><i class="fa-solid fa-gear"></i></div>
                            <div class="preset-name">엔진 흡배기 밸브 (고밀도 정밀 강성)</div>
                        </button>
                        <button class="preset-btn" id="preset-pen">
                            <div class="preset-icon"><i class="fa-solid fa-pen"></i></div>
                            <div class="preset-name">볼펜 스프링 (초미세 경량 와이어)</div>
                        </button>
                    </div>
                </div>
            </section>

            <!-- Right Column: Simulator & Results -->
            <div class="right-column">
                <!-- 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-run-status">정상 탄성 변형 중</span>
                            </div>
                        </div>
                    </div>

                    <div class="simulation-metrics-strip">
                        <div class="mini-metric">
                            <span class="label">스프링 지수 (C)</span>
                            <span class="value" id="val-index">7.50</span>
                        </div>
                        <div class="mini-divider"></div>
                        <div class="mini-metric">
                            <span class="label">밀착 높이 (Solid Height)</span>
                            <span class="value" id="val-solid-height">40.0 mm</span>
                        </div>
                        <div class="mini-divider"></div>
                        <div class="mini-metric">
                            <span class="label">Wahl 응력 보정 계수 (Kw)</span>
                            <span class="value" id="val-wahl">1.20</span>
                        </div>
                    </div>
                </section>

                <!-- Right Panel: Results Analysis -->
                <section class="panel results-panel">
                    <div class="panel-header">
                        <i class="fa-solid fa-chart-line text-cyan"></i>
                        <h2>정밀 탄성 특성 계산서</h2>
                    </div>

                    <div class="ratio-readout-box">
                        <div class="ratio-title">계산된 탄성 처짐량 (&delta;)</div>
                        <div class="ratio-value" id="txt-deflection">12.50 mm</div>
                        <div class="ratio-type" id="txt-stiffness">스프링 상수: 20.0 N/mm</div>
                    </div>

                    <div class="results-grid">
                        <div class="result-card">
                            <div class="card-icon"><i class="fa-solid fa-shield-halved"></i></div>
                            <div class="card-content">
                                <span class="card-unit">스프링 상수 (Stiffness)</span>
                                <span class="card-value" id="res-stiffness">20.06 N/mm</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">Wahl 보정 전단 응력 (&tau;)</span>
                                <span class="card-value" id="res-stress">143 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">전단 항복 안전 여유 (Sf)</span>
                                <span class="card-value" id="res-safety">4.20</span>
                            </div>
                        </div>
                    </div>

                    <div class="formula-card">
                        <h4><i class="fa-solid fa-info-circle text-cyan"></i> 스프링 거동 설계 관계식</h4>
                        <div class="formula-equation">
                            k = (G × d^4) / (8 × D^3 × n)
                        </div>
                        <div class="formula-equation">
                            &tau; = Kw × (8 × F × D) / (&pi; × d^3)
                        </div>
                    </div>
                </section>
            </div>
        </main>
    </div>

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

            const selectMaterial = document.getElementById('select-material');
            const inputD = document.getElementById('input-d');
            const sliderD = document.getElementById('slider-d');
            const inputMeanD = document.getElementById('input-mean-d');
            const sliderMeanD = document.getElementById('slider-mean-d');
            const inputN = document.getElementById('input-n');
            const sliderN = document.getElementById('slider-n');
            const inputForce = document.getElementById('input-force');
            const sliderForce = document.getElementById('slider-force');

            const txtDeflection = document.getElementById('txt-deflection');
            const txtStiffness = document.getElementById('txt-stiffness');
            const valIndex = document.getElementById('val-index');
            const valSolidHeight = document.getElementById('val-solid-height');
            const valWahl = document.getElementById('val-wahl');

            const resStiffness = document.getElementById('res-stiffness');
            const resStress = document.getElementById('res-stress');
            const resSafety = document.getElementById('res-safety');
            const overlayRunStatus = document.getElementById('overlay-run-status');

            const state = {
                g: 79.3, // GPa
                d: 4.0,  // mm
                D: 30.0, // mm
                n: 8,    // active coils
                force: 250.0, // N
                deflection: 0.0,
                stiffness: 0.0,
                stress: 0.0,
                safety: 0.0,
                isSolidLock: false
            };

            const ranges = {
                d: { min: 0.5, max: 20.0 },
                D: { min: 5.0, max: 200.0 },
                n: { min: 2.0, max: 30.0 },
                force: { min: 0.0, max: 2000.0 }
            };

            function calculateSpring() {
                // Ensure mean diameter is larger than wire diameter to avoid impossible geometry
                if (state.D < state.d + 1.0) {
                    state.D = state.d + 1.0;
                    inputMeanD.value = state.D.toFixed(1);
                    sliderMeanD.value = Math.round(state.D * 10);
                }

                const G_MPa = state.g * 1000.0;
                
                // Spring constant: k = (G * d^4) / (8 * D^3 * n)
                const d4 = Math.pow(state.d, 4);
                const D3 = Math.pow(state.D, 3);
                const k = (G_MPa * d4) / (8.0 * D3 * state.n);
                state.stiffness = k;

                // Total coils (squared and ground ends has 2 inactive coils)
                const totalCoils = state.n + 2;
                const solidHeight = totalCoils * state.d;
                state.solidHeight = solidHeight;

                // Free Height
                // Make it dynamic: solid height + 60mm so we always have compression room
                const freeHeight = solidHeight + 60.0;
                state.freeHeight = freeHeight;

                // Theoretical deflection
                let def = state.force / k;
                
                // Real physical boundary: cannot compress beyond solid height
                const maxDef = freeHeight - solidHeight - 1.5;
                state.isSolidLock = false;
                if (def > maxDef) {
                    def = maxDef;
                    state.isSolidLock = true;
                }
                state.deflection = def;

                // Spring Index C
                let C = state.D / state.d;
                if (C < 1.1) { C = 1.1; }
                state.springIndex = C;

                // Wahl Correction factor Kw
                const Kw = ((4.0 * C - 1.0) / (4.0 * C - 4.0)) + (0.615 / C);
                state.wahlFactor = Kw;

                // Shear stress
                // To display true stress when spring is bottomed out, compute force at solid height
                const actualForce = state.isSolidLock ? (maxDef * k) : state.force;
                const stress = Kw * (8.0 * actualForce * state.D) / (Math.PI * Math.pow(state.d, 3));
                state.stress = stress;

                // Safety factor against shear yield (assume yield stress is 600 MPa)
                const yieldStress = 600.0;
                state.safety = yieldStress / stress;

                updateUIValues();
            }

            function syncDFromInput() {
                let val = parseFloat(inputD.value);
                if (isNaN(val)) { val = ranges.d.min; }
                if (val < ranges.d.min) { val = ranges.d.min; }
                if (val > ranges.d.max) { val = ranges.d.max; }
                state.d = val;
                inputD.value = val.toFixed(1);
                sliderD.value = Math.round(val * 10);
                calculateSpring();
            }
            function syncDFromSlider() {
                state.d = parseFloat(sliderD.value) / 10;
                inputD.value = state.d.toFixed(1);
                calculateSpring();
            }

            function syncMeanDFromInput() {
                let val = parseFloat(inputMeanD.value);
                if (isNaN(val)) { val = ranges.D.min; }
                if (val < ranges.D.min) { val = ranges.D.min; }
                if (val > ranges.D.max) { val = ranges.D.max; }
                state.D = val;
                inputMeanD.value = val.toFixed(1);
                sliderMeanD.value = Math.round(val * 10);
                calculateSpring();
            }
            function syncMeanDFromSlider() {
                state.D = parseFloat(sliderMeanD.value) / 10;
                inputMeanD.value = state.D.toFixed(1);
                calculateSpring();
            }

            function syncNFromInput() {
                let val = parseFloat(inputN.value);
                if (isNaN(val)) { val = ranges.n.min; }
                if (val < ranges.n.min) { val = ranges.n.min; }
                if (val > ranges.n.max) { val = ranges.n.max; }
                state.n = val;
                inputN.value = val.toFixed(1);
                sliderN.value = Math.round(val);
                calculateSpring();
            }
            function syncNFromSlider() {
                state.n = parseFloat(sliderN.value);
                inputN.value = state.n.toFixed(0);
                calculateSpring();
            }

            function syncForceFromInput() {
                let val = parseFloat(inputForce.value);
                if (isNaN(val)) { val = ranges.force.min; }
                if (val < ranges.force.min) { val = ranges.force.min; }
                if (val > ranges.force.max) { val = ranges.force.max; }
                state.force = val;
                inputForce.value = val.toFixed(0);
                sliderForce.value = Math.round(val);
                calculateSpring();
            }
            function syncForceFromSlider() {
                state.force = parseFloat(sliderForce.value);
                inputForce.value = state.force.toFixed(0);
                calculateSpring();
            }

            function updateUIValues() {
                txtDeflection.innerText = state.deflection.toFixed(2) + ' mm';
                txtStiffness.innerText = '스프링 상수: ' + state.stiffness.toFixed(2) + ' N/mm';

                valIndex.innerText = state.springIndex.toFixed(2);
                valSolidHeight.innerText = state.solidHeight.toFixed(1) + ' mm';
                valWahl.innerText = state.wahlFactor.toFixed(3);

                resStiffness.innerText = state.stiffness.toFixed(2) + ' N/mm';
                resStress.innerText = state.stress.toFixed(0) + ' MPa';
                
                if (state.safety > 10.0) {
                    resSafety.innerText = '10.0+';
                } else {
                    resSafety.innerText = state.safety.toFixed(2);
                }

                if (state.isSolidLock) {
                    overlayRunStatus.innerText = '코일 밀착 상태 (밀착 차단 경보!)';
                    overlayRunStatus.style.color = '#db2777';
                } else {
                    if (state.stress > 600.0) {
                        overlayRunStatus.innerText = '항복 도달 (영구적 소성 처짐 발생)';
                        overlayRunStatus.style.color = '#db2777';
                    } else {
                        if (state.safety < 1.5) {
                            overlayRunStatus.innerText = '위험 (고응력 반복 피로 주의)';
                            overlayRunStatus.style.color = '#f59e0b';
                        } else {
                            overlayRunStatus.innerText = '안전 (적정 탄성 작동 중)';
                            overlayRunStatus.style.color = '#10b981';
                        }
                    }
                }
            }

            // Material bindings
            selectMaterial.addEventListener('change', function() {
                state.g = parseFloat(selectMaterial.value);
                calculateSpring();
            });

            inputD.addEventListener('change', syncDFromInput);
            sliderD.addEventListener('input', syncDFromSlider);
            inputMeanD.addEventListener('change', syncMeanDFromInput);
            sliderMeanD.addEventListener('input', syncMeanDFromSlider);
            inputN.addEventListener('change', syncNFromInput);
            sliderN.addEventListener('input', syncNFromSlider);
            inputForce.addEventListener('change', syncForceFromInput);
            sliderForce.addEventListener('input', syncForceFromSlider);

            // Presets
            document.getElementById('preset-auto').addEventListener('click', function() {
                setActivePreset('preset-auto');
                state.g = 79.3;
                state.d = 12.0;
                state.D = 110.0;
                state.n = 6;
                state.force = 1200;
                syncControlsToState();
                calculateSpring();
            });

            document.getElementById('preset-valve').addEventListener('click', function() {
                setActivePreset('preset-valve');
                state.g = 78.5;
                state.d = 3.5;
                state.D = 24.0;
                state.n = 8;
                state.force = 320;
                syncControlsToState();
                calculateSpring();
            });

            document.getElementById('preset-pen').addEventListener('click', function() {
                setActivePreset('preset-pen');
                state.g = 69.0;
                state.d = 0.6;
                state.D = 4.8;
                state.n = 15;
                state.force = 4;
                syncControlsToState();
                calculateSpring();
            });

            function setActivePreset(id) {
                document.getElementById('preset-auto').classList.remove('active');
                document.getElementById('preset-valve').classList.remove('active');
                document.getElementById('preset-pen').classList.remove('active');
                document.getElementById(id).classList.add('active');
            }

            function syncControlsToState() {
                selectMaterial.value = state.g.toFixed(1);
                inputD.value = state.d.toFixed(1);
                sliderD.value = Math.round(state.d * 10);
                inputMeanD.value = state.D.toFixed(1);
                sliderMeanD.value = Math.round(state.D * 10);
                inputN.value = state.n.toFixed(0);
                sliderN.value = Math.round(state.n);
                inputForce.value = state.force.toFixed(0);
                sliderForce.value = Math.round(state.force);
            }

            // 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();

            function animate(currentTime) {
                const dpr = getDPR();
                const width = canvas.width / dpr;
                const height = canvas.height / dpr;

                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 cy = height / 2 + 15;

                // Scale factor based on standard width 640px
                const S = width / 640;

                // Dynamic auto-zoom: scale spring to fill canvas properly
                const availH = height * 0.62; // use 62% of canvas height for spring
                const drawHeight = Math.max(80, availH);
                const activeCoils = state.n;
                const totalCoils = activeCoils + 2;

                // Current compressed height ratio
                const ratioHeight = (state.freeHeight - state.deflection) / state.freeHeight;
                const currentHeight = drawHeight * ratioHeight;
                const startY = cy - drawHeight / 2;
                const endY = startY + currentHeight;

                // Dynamic diameter: normalize to canvas width (fills ~35% of width)
                const maxDWidth = Math.min(width * 0.35, 160);
                const drawD = (40.0 + (state.D / 200.0) * (maxDWidth - 40.0)) * S;

                // Wire width visual scaling
                const drawWire = Math.min(Math.max(state.d * 1.5 * S, 2.5), 22 * S);

                // Draw Top Support Plate
                ctx.save();
                ctx.fillStyle = '#cbd5e1';
                ctx.strokeStyle = '#475569';
                ctx.lineWidth = 3 * S;
                ctx.beginPath();
                ctx.roundRect(cx - drawD / 2 - 25 * S, startY - 14 * S, drawD + 50 * S, 14 * S, 2 * S);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // Draw Bottom Moving Plate (which pushes or hangs)
                ctx.save();
                ctx.fillStyle = '#94a3b8';
                ctx.strokeStyle = '#334155';
                ctx.lineWidth = 3 * S;
                ctx.beginPath();
                ctx.roundRect(cx - drawD / 2 - 20 * S, endY, drawD + 40 * S, 14 * S, 2 * S);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // Draw Helix Coil Spring (Beautiful 3D representation)
                ctx.save();
                ctx.lineJoin = 'round';
                ctx.lineCap = 'round';
                ctx.lineWidth = drawWire;

                // Determine stress factor for segment coloring (grey -> magenta glow)
                const yldStr = 600.0;
                const stressFactor = Math.min(state.stress / yldStr, 1.2);
                let springColor = '#64748b';
                if (stressFactor > 0.05) {
                    const r = Math.round(100 + (219 - 100) * Math.min(stressFactor, 1));
                    const g = Math.round(116 + (39 - 116) * Math.min(stressFactor, 1));
                    const b = Math.round(139 + (119 - 139) * Math.min(stressFactor, 1));
                    springColor = 'rgb(' + r + ',' + g + ',' + b + ')';
                }
                ctx.strokeStyle = springColor;

                if (stressFactor > 0.85) {
                    ctx.shadowBlur = 10 * S;
                    ctx.shadowColor = '#db2777';
                }

                // Parametric helix curve
                ctx.beginPath();
                const segments = totalCoils * 24;
                const stepY = currentHeight / segments;
                const stepAngle = (totalCoils * Math.PI * 2) / segments;

                for (let i = 0; i <= segments; i++) {
                    const angle = i * stepAngle;
                    const x = cx + Math.cos(angle) * (drawD / 2);
                    const y = startY + i * stepY;
                    
                    if (i === 0) {
                        ctx.moveTo(x, y);
                    } else {
                        ctx.lineTo(x, y);
                    }
                }
                ctx.stroke();
                ctx.restore();

                // Draw Force Arrow (Magenta, pushing down)
                if (state.force > 0.0) {
                    ctx.save();
                    ctx.strokeStyle = '#db2777';
                    ctx.fillStyle = '#db2777';
                    ctx.lineWidth = Math.min(state.force * 0.005 + 2, 7) * S;
                    ctx.shadowBlur = 8 * S;
                    ctx.shadowColor = '#db2777';

                    // Draw vertical arrow above top plate pushing down
                    const arrowY = startY - 70 * S;
                    ctx.beginPath();
                    ctx.moveTo(cx, arrowY);
                    ctx.lineTo(cx, startY - 20 * S);
                    ctx.stroke();

                    // Arrowhead
                    ctx.beginPath();
                    ctx.moveTo(cx, startY - 17 * S);
                    ctx.lineTo(cx - 8 * S, startY - 28 * S);
                    ctx.lineTo(cx + 8 * S, startY - 28 * S);
                    ctx.closePath();
                    ctx.fill();

                    // Label text
                    ctx.fillStyle = '#db2777';
                    ctx.font = 'bold ' + Math.max(9, 11 * S) + 'px Inter';
                    ctx.textAlign = 'center';
                    ctx.fillText('Load F = ' + state.force.toFixed(0) + ' N', cx, arrowY - 8 * S);
                    ctx.restore();
                }

                // Draw deflection indicator dimension lines
                ctx.save();
                ctx.strokeStyle = '#0284c7';
                ctx.lineWidth = 1.5 * S;
                ctx.fillStyle = '#0284c7';
                ctx.font = 'bold ' + Math.max(8.5, 10 * S) + 'px Inter';

                // Top extension line
                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 30 * S, startY);
                ctx.lineTo(cx + drawD / 2 + 75 * S, startY);
                ctx.stroke();

                // Current bottom extension line
                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 25 * S, endY);
                ctx.lineTo(cx + drawD / 2 + 75 * S, endY);
                ctx.stroke();

                // Dimension line arrows
                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 65 * S, startY);
                ctx.lineTo(cx + drawD / 2 + 65 * S, endY);
                ctx.stroke();

                // Arrowheads
                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 65 * S, startY);
                ctx.lineTo(cx + drawD / 2 + 62 * S, startY + 6 * S);
                ctx.lineTo(cx + drawD / 2 + 68 * S, startY + 6 * S);
                ctx.closePath();
                ctx.fill();

                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 65 * S, endY);
                ctx.lineTo(cx + drawD / 2 + 62 * S, endY - 6 * S);
                ctx.lineTo(cx + drawD / 2 + 68 * S, endY - 6 * S);
                ctx.closePath();
                ctx.fill();

                // Text
                ctx.fillText('Height = ' + (state.freeHeight - state.deflection).toFixed(1) + ' mm', cx + drawD / 2 + 80 * S, cy);
                ctx.restore();

                requestAnimationFrame(animate);
            }
            requestAnimationFrame(animate);

            // Initial calculation
            calculateSpring();

            // 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.__springdeflection_initialized) return;
            window.__springdeflection_initialized = true;

            const selectMaterial = document.getElementById('select-material');
            const inputD = document.getElementById('input-d');
            const sliderD = document.getElementById('slider-d');
            const inputMeanD = document.getElementById('input-mean-d');
            const sliderMeanD = document.getElementById('slider-mean-d');
            const inputN = document.getElementById('input-n');
            const sliderN = document.getElementById('slider-n');
            const inputForce = document.getElementById('input-force');
            const sliderForce = document.getElementById('slider-force');

            const txtDeflection = document.getElementById('txt-deflection');
            const txtStiffness = document.getElementById('txt-stiffness');
            const valIndex = document.getElementById('val-index');
            const valSolidHeight = document.getElementById('val-solid-height');
            const valWahl = document.getElementById('val-wahl');

            const resStiffness = document.getElementById('res-stiffness');
            const resStress = document.getElementById('res-stress');
            const resSafety = document.getElementById('res-safety');
            const overlayRunStatus = document.getElementById('overlay-run-status');

            const state = {
                g: 79.3, // GPa
                d: 4.0,  // mm
                D: 30.0, // mm
                n: 8,    // active coils
                force: 250.0, // N
                deflection: 0.0,
                stiffness: 0.0,
                stress: 0.0,
                safety: 0.0,
                isSolidLock: false
            };

            const ranges = {
                d: { min: 0.5, max: 20.0 },
                D: { min: 5.0, max: 200.0 },
                n: { min: 2.0, max: 30.0 },
                force: { min: 0.0, max: 2000.0 }
            };

            function calculateSpring() {
                // Ensure mean diameter is larger than wire diameter to avoid impossible geometry
                if (state.D < state.d + 1.0) {
                    state.D = state.d + 1.0;
                    inputMeanD.value = state.D.toFixed(1);
                    sliderMeanD.value = Math.round(state.D * 10);
                }

                const G_MPa = state.g * 1000.0;
                
                // Spring constant: k = (G * d^4) / (8 * D^3 * n)
                const d4 = Math.pow(state.d, 4);
                const D3 = Math.pow(state.D, 3);
                const k = (G_MPa * d4) / (8.0 * D3 * state.n);
                state.stiffness = k;

                // Total coils (squared and ground ends has 2 inactive coils)
                const totalCoils = state.n + 2;
                const solidHeight = totalCoils * state.d;
                state.solidHeight = solidHeight;

                // Free Height
                // Make it dynamic: solid height + 60mm so we always have compression room
                const freeHeight = solidHeight + 60.0;
                state.freeHeight = freeHeight;

                // Theoretical deflection
                let def = state.force / k;
                
                // Real physical boundary: cannot compress beyond solid height
                const maxDef = freeHeight - solidHeight - 1.5;
                state.isSolidLock = false;
                if (def > maxDef) {
                    def = maxDef;
                    state.isSolidLock = true;
                }
                state.deflection = def;

                // Spring Index C
                let C = state.D / state.d;
                if (C < 1.1) { C = 1.1; }
                state.springIndex = C;

                // Wahl Correction factor Kw
                const Kw = ((4.0 * C - 1.0) / (4.0 * C - 4.0)) + (0.615 / C);
                state.wahlFactor = Kw;

                // Shear stress
                // To display true stress when spring is bottomed out, compute force at solid height
                const actualForce = state.isSolidLock ? (maxDef * k) : state.force;
                const stress = Kw * (8.0 * actualForce * state.D) / (Math.PI * Math.pow(state.d, 3));
                state.stress = stress;

                // Safety factor against shear yield (assume yield stress is 600 MPa)
                const yieldStress = 600.0;
                state.safety = yieldStress / stress;

                updateUIValues();
            }

            function syncDFromInput() {
                let val = parseFloat(inputD.value);
                if (isNaN(val)) { val = ranges.d.min; }
                if (val < ranges.d.min) { val = ranges.d.min; }
                if (val > ranges.d.max) { val = ranges.d.max; }
                state.d = val;
                inputD.value = val.toFixed(1);
                sliderD.value = Math.round(val * 10);
                calculateSpring();
            }
            function syncDFromSlider() {
                state.d = parseFloat(sliderD.value) / 10;
                inputD.value = state.d.toFixed(1);
                calculateSpring();
            }

            function syncMeanDFromInput() {
                let val = parseFloat(inputMeanD.value);
                if (isNaN(val)) { val = ranges.D.min; }
                if (val < ranges.D.min) { val = ranges.D.min; }
                if (val > ranges.D.max) { val = ranges.D.max; }
                state.D = val;
                inputMeanD.value = val.toFixed(1);
                sliderMeanD.value = Math.round(val * 10);
                calculateSpring();
            }
            function syncMeanDFromSlider() {
                state.D = parseFloat(sliderMeanD.value) / 10;
                inputMeanD.value = state.D.toFixed(1);
                calculateSpring();
            }

            function syncNFromInput() {
                let val = parseFloat(inputN.value);
                if (isNaN(val)) { val = ranges.n.min; }
                if (val < ranges.n.min) { val = ranges.n.min; }
                if (val > ranges.n.max) { val = ranges.n.max; }
                state.n = val;
                inputN.value = val.toFixed(1);
                sliderN.value = Math.round(val);
                calculateSpring();
            }
            function syncNFromSlider() {
                state.n = parseFloat(sliderN.value);
                inputN.value = state.n.toFixed(0);
                calculateSpring();
            }

            function syncForceFromInput() {
                let val = parseFloat(inputForce.value);
                if (isNaN(val)) { val = ranges.force.min; }
                if (val < ranges.force.min) { val = ranges.force.min; }
                if (val > ranges.force.max) { val = ranges.force.max; }
                state.force = val;
                inputForce.value = val.toFixed(0);
                sliderForce.value = Math.round(val);
                calculateSpring();
            }
            function syncForceFromSlider() {
                state.force = parseFloat(sliderForce.value);
                inputForce.value = state.force.toFixed(0);
                calculateSpring();
            }

            function updateUIValues() {
                txtDeflection.innerText = state.deflection.toFixed(2) + ' mm';
                txtStiffness.innerText = '스프링 상수: ' + state.stiffness.toFixed(2) + ' N/mm';

                valIndex.innerText = state.springIndex.toFixed(2);
                valSolidHeight.innerText = state.solidHeight.toFixed(1) + ' mm';
                valWahl.innerText = state.wahlFactor.toFixed(3);

                resStiffness.innerText = state.stiffness.toFixed(2) + ' N/mm';
                resStress.innerText = state.stress.toFixed(0) + ' MPa';
                
                if (state.safety > 10.0) {
                    resSafety.innerText = '10.0+';
                } else {
                    resSafety.innerText = state.safety.toFixed(2);
                }

                if (state.isSolidLock) {
                    overlayRunStatus.innerText = '코일 밀착 상태 (밀착 차단 경보!)';
                    overlayRunStatus.style.color = '#db2777';
                } else {
                    if (state.stress > 600.0) {
                        overlayRunStatus.innerText = '항복 도달 (영구적 소성 처짐 발생)';
                        overlayRunStatus.style.color = '#db2777';
                    } else {
                        if (state.safety < 1.5) {
                            overlayRunStatus.innerText = '위험 (고응력 반복 피로 주의)';
                            overlayRunStatus.style.color = '#f59e0b';
                        } else {
                            overlayRunStatus.innerText = '안전 (적정 탄성 작동 중)';
                            overlayRunStatus.style.color = '#10b981';
                        }
                    }
                }
            }

            // Material bindings
            selectMaterial.addEventListener('change', function() {
                state.g = parseFloat(selectMaterial.value);
                calculateSpring();
            });

            inputD.addEventListener('change', syncDFromInput);
            sliderD.addEventListener('input', syncDFromSlider);
            inputMeanD.addEventListener('change', syncMeanDFromInput);
            sliderMeanD.addEventListener('input', syncMeanDFromSlider);
            inputN.addEventListener('change', syncNFromInput);
            sliderN.addEventListener('input', syncNFromSlider);
            inputForce.addEventListener('change', syncForceFromInput);
            sliderForce.addEventListener('input', syncForceFromSlider);

            // Presets
            document.getElementById('preset-auto').addEventListener('click', function() {
                setActivePreset('preset-auto');
                state.g = 79.3;
                state.d = 12.0;
                state.D = 110.0;
                state.n = 6;
                state.force = 1200;
                syncControlsToState();
                calculateSpring();
            });

            document.getElementById('preset-valve').addEventListener('click', function() {
                setActivePreset('preset-valve');
                state.g = 78.5;
                state.d = 3.5;
                state.D = 24.0;
                state.n = 8;
                state.force = 320;
                syncControlsToState();
                calculateSpring();
            });

            document.getElementById('preset-pen').addEventListener('click', function() {
                setActivePreset('preset-pen');
                state.g = 69.0;
                state.d = 0.6;
                state.D = 4.8;
                state.n = 15;
                state.force = 4;
                syncControlsToState();
                calculateSpring();
            });

            function setActivePreset(id) {
                document.getElementById('preset-auto').classList.remove('active');
                document.getElementById('preset-valve').classList.remove('active');
                document.getElementById('preset-pen').classList.remove('active');
                document.getElementById(id).classList.add('active');
            }

            function syncControlsToState() {
                selectMaterial.value = state.g.toFixed(1);
                inputD.value = state.d.toFixed(1);
                sliderD.value = Math.round(state.d * 10);
                inputMeanD.value = state.D.toFixed(1);
                sliderMeanD.value = Math.round(state.D * 10);
                inputN.value = state.n.toFixed(0);
                sliderN.value = Math.round(state.n);
                inputForce.value = state.force.toFixed(0);
                sliderForce.value = Math.round(state.force);
            }

            // 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();

            function animate(currentTime) {
                const dpr = getDPR();
                const width = canvas.width / dpr;
                const height = canvas.height / dpr;

                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 cy = height / 2 + 15;

                // Scale factor based on standard width 640px
                const S = width / 640;

                // Dynamic auto-zoom: scale spring to fill canvas properly
                const availH = height * 0.62; // use 62% of canvas height for spring
                const drawHeight = Math.max(80, availH);
                const activeCoils = state.n;
                const totalCoils = activeCoils + 2;

                // Current compressed height ratio
                const ratioHeight = (state.freeHeight - state.deflection) / state.freeHeight;
                const currentHeight = drawHeight * ratioHeight;
                const startY = cy - drawHeight / 2;
                const endY = startY + currentHeight;

                // Dynamic diameter: normalize to canvas width (fills ~35% of width)
                const maxDWidth = Math.min(width * 0.35, 160);
                const drawD = (40.0 + (state.D / 200.0) * (maxDWidth - 40.0)) * S;

                // Wire width visual scaling
                const drawWire = Math.min(Math.max(state.d * 1.5 * S, 2.5), 22 * S);

                // Draw Top Support Plate
                ctx.save();
                ctx.fillStyle = '#cbd5e1';
                ctx.strokeStyle = '#475569';
                ctx.lineWidth = 3 * S;
                ctx.beginPath();
                ctx.roundRect(cx - drawD / 2 - 25 * S, startY - 14 * S, drawD + 50 * S, 14 * S, 2 * S);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // Draw Bottom Moving Plate (which pushes or hangs)
                ctx.save();
                ctx.fillStyle = '#94a3b8';
                ctx.strokeStyle = '#334155';
                ctx.lineWidth = 3 * S;
                ctx.beginPath();
                ctx.roundRect(cx - drawD / 2 - 20 * S, endY, drawD + 40 * S, 14 * S, 2 * S);
                ctx.fill();
                ctx.stroke();
                ctx.restore();

                // Draw Helix Coil Spring (Beautiful 3D representation)
                ctx.save();
                ctx.lineJoin = 'round';
                ctx.lineCap = 'round';
                ctx.lineWidth = drawWire;

                // Determine stress factor for segment coloring (grey -> magenta glow)
                const yldStr = 600.0;
                const stressFactor = Math.min(state.stress / yldStr, 1.2);
                let springColor = '#64748b';
                if (stressFactor > 0.05) {
                    const r = Math.round(100 + (219 - 100) * Math.min(stressFactor, 1));
                    const g = Math.round(116 + (39 - 116) * Math.min(stressFactor, 1));
                    const b = Math.round(139 + (119 - 139) * Math.min(stressFactor, 1));
                    springColor = 'rgb(' + r + ',' + g + ',' + b + ')';
                }
                ctx.strokeStyle = springColor;

                if (stressFactor > 0.85) {
                    ctx.shadowBlur = 10 * S;
                    ctx.shadowColor = '#db2777';
                }

                // Parametric helix curve
                ctx.beginPath();
                const segments = totalCoils * 24;
                const stepY = currentHeight / segments;
                const stepAngle = (totalCoils * Math.PI * 2) / segments;

                for (let i = 0; i <= segments; i++) {
                    const angle = i * stepAngle;
                    const x = cx + Math.cos(angle) * (drawD / 2);
                    const y = startY + i * stepY;
                    
                    if (i === 0) {
                        ctx.moveTo(x, y);
                    } else {
                        ctx.lineTo(x, y);
                    }
                }
                ctx.stroke();
                ctx.restore();

                // Draw Force Arrow (Magenta, pushing down)
                if (state.force > 0.0) {
                    ctx.save();
                    ctx.strokeStyle = '#db2777';
                    ctx.fillStyle = '#db2777';
                    ctx.lineWidth = Math.min(state.force * 0.005 + 2, 7) * S;
                    ctx.shadowBlur = 8 * S;
                    ctx.shadowColor = '#db2777';

                    // Draw vertical arrow above top plate pushing down
                    const arrowY = startY - 70 * S;
                    ctx.beginPath();
                    ctx.moveTo(cx, arrowY);
                    ctx.lineTo(cx, startY - 20 * S);
                    ctx.stroke();

                    // Arrowhead
                    ctx.beginPath();
                    ctx.moveTo(cx, startY - 17 * S);
                    ctx.lineTo(cx - 8 * S, startY - 28 * S);
                    ctx.lineTo(cx + 8 * S, startY - 28 * S);
                    ctx.closePath();
                    ctx.fill();

                    // Label text
                    ctx.fillStyle = '#db2777';
                    ctx.font = 'bold ' + Math.max(9, 11 * S) + 'px Inter';
                    ctx.textAlign = 'center';
                    ctx.fillText('Load F = ' + state.force.toFixed(0) + ' N', cx, arrowY - 8 * S);
                    ctx.restore();
                }

                // Draw deflection indicator dimension lines
                ctx.save();
                ctx.strokeStyle = '#0284c7';
                ctx.lineWidth = 1.5 * S;
                ctx.fillStyle = '#0284c7';
                ctx.font = 'bold ' + Math.max(8.5, 10 * S) + 'px Inter';

                // Top extension line
                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 30 * S, startY);
                ctx.lineTo(cx + drawD / 2 + 75 * S, startY);
                ctx.stroke();

                // Current bottom extension line
                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 25 * S, endY);
                ctx.lineTo(cx + drawD / 2 + 75 * S, endY);
                ctx.stroke();

                // Dimension line arrows
                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 65 * S, startY);
                ctx.lineTo(cx + drawD / 2 + 65 * S, endY);
                ctx.stroke();

                // Arrowheads
                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 65 * S, startY);
                ctx.lineTo(cx + drawD / 2 + 62 * S, startY + 6 * S);
                ctx.lineTo(cx + drawD / 2 + 68 * S, startY + 6 * S);
                ctx.closePath();
                ctx.fill();

                ctx.beginPath();
                ctx.moveTo(cx + drawD / 2 + 65 * S, endY);
                ctx.lineTo(cx + drawD / 2 + 62 * S, endY - 6 * S);
                ctx.lineTo(cx + drawD / 2 + 68 * S, endY - 6 * S);
                ctx.closePath();
                ctx.fill();

                // Text
                ctx.fillText('Height = ' + (state.freeHeight - state.deflection).toFixed(1) + ' mm', cx + drawD / 2 + 80 * S, cy);
                ctx.restore();

                requestAnimationFrame(animate);
            }
            requestAnimationFrame(animate);

            // Initial calculation
            calculateSpring();

            // 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>재료 물성 선택: 탄소강, 스테인리스강, 인청동 등 스프링 소재를 선택하여 전단 탄성 계수(G)를 지정합니다.</strong></li>
<li style="margin-bottom: 6px;">기하학적 변수 지정: 스프링 소선 지름(d), 코일 평균 지름(D), 유효 권수(n) 슬라이더를 부품 카탈로그 스펙에 맞춰 제어합니다.</li>
<li style="margin-bottom: 6px;">인가 하중 설정: 스프링에 가하는 외부 힘(F)을 조절하여 압축 하중을 작용시킵니다.</li>
<li style="margin-bottom: 6px;">실시간 압축 변형 관찰: 2D 시뮬레이터 속 스프링이 탄성 공식에 맞춰 실시간으로 압축되며, 전단 응력 부하에 비례하여 헬릭스 코일이 붉은색(Magenta Glow)으로 변하는 시각적 피드백을 확인합니다.</li>
<li style="margin-bottom: 6px;">밀착 상태 및 파손 위험 검토: 스프링이 완전히 닫히는 밀착 높이(Solid Height)에 도달했는지 확인하고, 안전 계수를 통해 전단 항복 강도 이하로 안전하게 운전되고 있는지 최종 판정합니다.</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;" /> 상세 기계공학 해설 및 스프링 설계 규격 (KS/ISO) 확인하기</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. 헬리컬 코일 스프링(Helical Spring)의 강성 공식 유도</h3>
<p>코일 스프링은 선재(Wire)를 나선형으로 감아 비틀림 탄성 변형(Torsional Deflection)을 이용하여 에너지를 저장하고 완충 작용을 하는 대표적인 탄성 기계 기구 요소입니다. 스프링에 압축 하중 <code>F</code>가 가해지면 소선 단면에는 비틀림 모멘트 <code>T = F &middot; D / 2</code>가 균일하게 작용합니다.</p><p>비틀림 변형 에너지를 적분하여 유도되는 코일 스프링의 **스프링 상수(Stiffness / Spring Rate, k)**는 다음과 같이 정의됩니다:</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">k = (G &middot; d<sup>4</sup>) / (8 &middot; D<sup>3</sup> &middot; n) &nbsp;[N/mm]</p><p>여기서 기하 인자들은 다음과 같습니다:</p><ul><li><strong>G: 재료의 전단 탄성 계수 (Shear Modulus, GPa)</strong> &#8211; 철강 소재는 약 79.3 GPa, 스테인리스강은 69 GPa 내외입니다.</li><li><strong>d: 소선의 지름 (Wire Diameter, mm)</strong> &#8211; 힘의 4제곱에 비례하여 스프링의 강성에 지대한 영향을 미칩니다.</li><li><strong>D: 코일 평균 지름 (Mean Coil Diameter, mm)</strong> &#8211; 코일 외경(D<sub>o</sub>)에서 소선 지름(d)을 뺀 치수입니다.</li><li><strong>n: 유효 권수 (Active Coils)</strong> &#8211; 변형에 실제로 참여하는 감김 횟수입니다.</li></ul>
<h3>2. Wahl 보정 계수를 반영한 전단 응력 해석</h3>
<p>스프링 소선 내부에는 단순 비틀림 응력 외에도 소선이 구부러짐으로써 발생하는 직접 전단 응력과 곡률(Curvature)에 의한 응력 집중 현상이 내측 궤도에 강하게 발생합니다. 기계공학자 <em>A. M. Wahl</em>은 이를 보정하기 위해 다음과 같은 <strong>와알 보정 계수 (Wahl Correction Factor, K<sub>W</sub>)</strong>를 제안했습니다:</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">K<sub>W</sub> = (4C &#8211; 1)/(4C &#8211; 4) + 0.615/C &nbsp;&nbsp;(여기서 스프링 지수 C = D / d)</p><p>이를 반영한 볼트 소선의 최대 전단 응력 <code>&tau;</code>는 다음과 같이 계산되며, 이 응력이 스프링 재료의 전단 항복 한계 이하에 머물러야 소성 처짐(Set)이 생기지 않습니다:</p><p style="text-align: center; font-weight: bold; background: #e0f2fe; padding: 16px; border-radius: 8px; font-size: 1.1em; color: #0369a1;">&tau; = K<sub>W</sub> &times; (8 &middot; F &middot; D) / (&pi; &middot; d<sup>3</sup>) &nbsp;[MPa]</p>
<h3>3. 스프링 고유 한계점 및 밀착 차단 (Solid Height)</h3>
<p>스프링이 과도하게 하중을 받으면 코일 소선들이 서로 맞닿는 상태가 되는데, 이 한계 상태의 높이를 <strong>밀착 높이 (Solid Height, h<sub>s</sub>)</strong>라고 합니다. 밀착 조건 하에서는 추가적인 탄성 처짐이 불가능하며 시스템이 딱딱한 강체(Solid Block)로 작동하여 막대한 충격력이 나사면과 프레임에 고스란히 전달됩니다. 따라서 일반 설계 시 최대 작동 변위는 밀착 변형량 대비 80%를 넘지 않도록 제한 안전 여유를 설계 규격(KS B 2400)에서 권장하고 있습니다.</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/spring-deflection-stiffness-calculator-simulator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
