<?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>2D시뮬레이터 &#8211; MyEngNote</title>
	<atom:link href="https://myengnote.com/tag/2d%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%ED%84%B0/feed/" rel="self" type="application/rss+xml" />
	<link>https://myengnote.com</link>
	<description></description>
	<lastBuildDate>Thu, 28 May 2026 23:33:37 +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/chain-sprocket-speed-calculator-simulator/</link>
					<comments>https://myengnote.com/chain-sprocket-speed-calculator-simulator/#respond</comments>
		
		<dc:creator><![CDATA[동동]]></dc:creator>
		<pubDate>Thu, 28 May 2026 05:32:20 +0000</pubDate>
				<category><![CDATA[공학계산기]]></category>
		<category><![CDATA[2D시뮬레이터]]></category>
		<category><![CDATA[스프로킷피치경]]></category>
		<category><![CDATA[체인스프로킷]]></category>
		<category><![CDATA[체인인장력]]></category>
		<category><![CDATA[체인전동계산]]></category>
		<guid isPermaLink="false">https://myengnote.com/chain-sprocket-speed-calculator-simulator/</guid>

					<description><![CDATA[체인 피치, 스프로킷 잇수(Z), 회전 속도(RPM) 및 전달 동력을 입력하여 체인의 주속도, 피치원 지름(PCD), 작용 인장력을 실시간으로 계산하고 시각화하는 초정밀 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 chainsprocket-calculator-wrapper */

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

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

        .chainsprocket-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: chainsprocket-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(16, 185, 129, 0.1);
            border: 1px solid rgba(16, 185, 129, 0.2);
            padding: 6px 14px;
            border-radius: 20px;
        }

        .pulse-dot {
            width: 8px;
            height: 8px;
            background-color: var(--color-success);
            border-radius: 50%;
            box-shadow: 0 0 10px var(--color-success);
            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-success);
            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: 20px;
            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;
        }

        .input-group {
            display: flex;
            flex-direction: column;
            gap: 12px;
        }

        .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: 15px !important;
            font-weight: 700 !important;
            text-align: right !important;
            padding-right: 60px !important;
            text-shadow: none !important;
            box-shadow: none !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: 8px 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;
        }

        /* Thumbs Styling */
        #slider-z1::-webkit-slider-thumb {
            background: var(--color-cyan);
            box-shadow: 0 0 10px var(--color-cyan);
        }
        #slider-z1::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-cyan), 0 0 5px #fff;
        }
        #slider-z1::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border: none;
            border-radius: 50%;
            background: var(--color-cyan);
            box-shadow: 0 0 10px var(--color-cyan);
            cursor: pointer;
        }

        #slider-z2::-webkit-slider-thumb {
            background: var(--color-magenta);
            box-shadow: 0 0 10px var(--color-magenta);
        }
        #slider-z2::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-magenta), 0 0 5px #fff;
        }
        #slider-z2::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border: none;
            border-radius: 50%;
            background: var(--color-magenta);
            box-shadow: 0 0 10px var(--color-magenta);
            cursor: pointer;
        }

        #slider-c::-webkit-slider-thumb {
            background: #ffffff;
            box-shadow: 0 0 10px var(--color-cyan);
        }
        #slider-c::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-cyan), 0 0 5px #fff;
        }
        #slider-c::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border: none;
            border-radius: 50%;
            background: #ffffff;
            box-shadow: 0 0 10px var(--color-cyan);
            cursor: pointer;
        }

        #slider-rpm::-webkit-slider-thumb {
            background: var(--color-purple);
            box-shadow: 0 0 10px var(--color-purple);
        }
        #slider-rpm::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-purple), 0 0 5px #fff;
        }
        #slider-rpm::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border: none;
            border-radius: 50%;
            background: var(--color-purple);
            box-shadow: 0 0 10px var(--color-purple);
            cursor: pointer;
        }

        .custom-select-wrapper {
            position: relative;
        }

        .custom-select {
            width: 100%;
            background: #f8fafc;
            border: 1px solid var(--color-border);
            border-radius: 8px;
            padding: 10px 14px;
            color: var(--color-text-main);
            font-family: var(--font-body);
            font-size: 14px;
            font-weight: 600;
            outline: none;
            cursor: pointer;
            transition: all 0.2s ease;
        }

        .custom-select:focus {
            border-color: var(--color-cyan);
            box-shadow: 0 0 10px var(--color-cyan-glow);
        }

        .control-actions {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
            margin-top: 10px;
        }

        .action-btn {
            background: rgba(0, 0, 0, 0.02);
            border: 1px solid var(--color-border);
            color: var(--color-text-main);
            padding: 10px;
            border-radius: 10px;
            font-size: 12px;
            font-weight: 600;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            transition: all 0.2s ease;
        }

        .action-btn:hover {
            background: rgba(0, 0, 0, 0.04);
            border-color: var(--color-border-hover);
        }

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

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

        .presets-grid {
            display: grid;
            grid-template-columns: 1fr;
            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.05);
            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);
            transition: all 0.2s ease;
        }

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

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

        .simulation-panel {
            grid-column: 2;
            align-self: stretch;
            justify-content: space-between;
            min-width: 0;
        }

        .simulation-panel .panel-header {
            justify-content: space-between;
        }

        .canvas-scale-indicator {
            font-size: 11px;
            color: var(--color-text-muted);
            background: rgba(0, 0, 0, 0.02);
            padding: 4px 10px;
            border-radius: 6px;
            border: 1px solid var(--color-border);
        }

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

        #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: transparent;
            color: var(--color-text-main);
            padding: 4px 0;
            display: flex;
            align-items: center;
            gap: 6px;
            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: 26px;
            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);
            transition: all 0.2s ease;
        }

        .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;
            border: 1px solid var(--color-border);
            text-align: center;
            font-weight: 700;
        }

        /* Container Query for Responsiveness inside WP */
        @container chainsprocket-container (max-width: 1050px) {
            .app-main-grid {
                grid-template-columns: 1fr 1fr;
                gap: 20px;
            }
            .control-panel { grid-column: 1; }
            .simulation-panel { grid-column: 2; }
            .results-panel { grid-column: 1 / span 2; }
        }
        @container chainsprocket-container (max-width: 580px) {
            .app-main-grid {
                grid-template-columns: 1fr;
            }
            .control-panel, .simulation-panel, .results-panel {
                grid-column: 1;
            }
            .simulation-panel {
                order: -1;
            }
            .simulation-results-section {
                grid-template-columns: 1fr !important;
            }
        }
    
</style>

<div class="chainsprocket-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-link"></i></div>
                <div>
                    <h1>CHAIN DRIVE</h1>
                    <div class="subtitle">체인-스프로킷 주속도 및 장력 계산 시뮬레이터</div>
                </div>
            </div>
            <div class="header-badge">
                <div class="pulse-dot"></div>
                <div class="badge-text">2D MESH 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>

                <!-- Chain Type Selection -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="select-chain-type"><i class="fa-solid fa-gears"></i> 체인 ANSI 호칭 번호</label>
                        <span class="helper-text">규격 선택</span>
                    </div>
                    <div class="custom-select-wrapper">
                        <select id="select-chain-type" class="custom-select" aria-label="체인 규격 규격 선택">
                            <option value="9.525">ANSI 35 (피치 9.525 mm)</option>
                            <option value="12.7" selected>ANSI 40 (피치 12.700 mm)</option>
                            <option value="15.875">ANSI 50 (피치 15.875 mm)</option>
                            <option value="19.05">ANSI 60 (피치 19.050 mm)</option>
                            <option value="25.4">ANSI 80 (피치 25.400 mm)</option>
                            <option value="31.75">ANSI 100 (피치 31.750 mm)</option>
                        </select>
                    </div>
                </div>

                <!-- Sprocket 1 Teeth Z1 -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-z1"><i class="fa-solid fa-circle text-cyan"></i> 구동 스프로킷 잇수 (Z₁)</label>
                        <span class="helper-text">구동측 (9 ~ 80T)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-z1" class="custom-number-input" min="9" max="80" value="17">
                        <div class="unit-badge">T</div>
                    </div>
                    <input type="range" id="slider-z1" class="custom-slider" min="9" max="80" value="17">
                </div>

                <!-- Sprocket 2 Teeth Z2 -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-z2"><i class="fa-solid fa-circle text-magenta"></i> 피동 스프로킷 잇수 (Z₂)</label>
                        <span class="helper-text">피동측 (9 ~ 80T)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-z2" class="custom-number-input" min="9" max="80" value="34">
                        <div class="unit-badge">T</div>
                    </div>
                    <input type="range" id="slider-z2" class="custom-slider" min="9" max="80" value="34">
                </div>

                <!-- Center Distance C -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-c"><i class="fa-solid fa-arrows-left-right"></i> 축간 중심거리 (C)</label>
                        <span class="helper-text">축간거리 (150 ~ 1500 mm)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-c" class="custom-number-input" min="150" max="1500" value="450">
                        <div class="unit-badge">mm</div>
                    </div>
                    <input type="range" id="slider-c" class="custom-slider" min="150" max="1500" value="450">
                </div>

                <!-- Input RPM N1 -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-rpm"><i class="fa-solid fa-gauge-high text-purple"></i> 입력 회전수 (N₁)</label>
                        <span class="helper-text">입력 속도 (0 ~ 3000 RPM)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-rpm" class="custom-number-input" min="0" max="3000" value="360">
                        <div class="unit-badge">RPM</div>
                    </div>
                    <input type="range" id="slider-rpm" class="custom-slider" min="0" max="3000" value="360">
                </div>

                <!-- Transmission Power -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label for="input-power"><i class="fa-solid fa-plug text-cyan"></i> 전달 전동 동력 (P)</label>
                        <span class="helper-text">동력량 (0.1 ~ 150 kW)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-power" class="custom-number-input" min="0.1" max="150" value="7.5" step="any">
                        <div class="unit-badge">kW</div>
                    </div>
                </div>

                <!-- Simulation Controls -->
                <div class="control-actions">
                    <button id="btn-play-pause" class="action-btn">
                        <i class="fa-solid fa-pause"></i> <span>일시정지</span>
                    </button>
                    <button id="btn-reverse" class="action-btn">
                        <i class="fa-solid fa-backward"></i> <span>방향 전환</span>
                    </button>
                </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" data-preset="reduction">
                            <div class="preset-icon"><i class="fa-solid fa-down-long"></i></div>
                            <div class="preset-name">2:1 중속 감속 시스템 (ANSI 40)</div>
                        </button>
                        <button class="preset-btn" data-preset="heavy">
                            <div class="preset-icon"><i class="fa-solid fa-industry"></i></div>
                            <div class="preset-name">1:3 대형 감속 전동 (ANSI 80)</div>
                        </button>
                        <button class="preset-btn" data-preset="highspeed">
                            <div class="preset-icon"><i class="fa-solid fa-wind"></i></div>
                            <div class="preset-name">1.5:1 정밀 고속 전동 (ANSI 35)</div>
                        </button>
                    </div>
                </div>
            </section>

            <!-- Right Panel: Physics Simulator & Integrated Results -->
            <section class="panel simulation-panel">
                <div class="panel-header">
                    <i class="fa-solid fa-dharmachakra text-magenta"></i>
                    <h2>실시간 2D 체인 전동 물리 시뮬레이터</h2>
                    <span class="canvas-scale-indicator" id="btn-reset-pos"><i class="fa-solid fa-arrows-rotate"></i> 정렬</span>
                </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-status" style="color: var(--color-success);">정상 전동 중</span>
                        </div>
                    </div>
                </div>

                <div class="simulation-metrics-strip">
                    <div class="mini-metric">
                        <span class="label">체인 주속도 (Speed)</span>
                        <span class="value" id="val-chain-speed">2.44 m/s</span>
                    </div>
                    <div class="mini-divider"></div>
                    <div class="mini-metric">
                        <span class="label">다각형 효과 변동률</span>
                        <span class="value" id="val-polygon-ratio">1.70%</span>
                    </div>
                    <div class="mini-divider"></div>
                    <div class="mini-metric">
                        <span class="label">체인 물림 주파수</span>
                        <span class="value" id="val-mesh-freq">102.0 Hz</span>
                    </div>
                </div>

                <!-- Integrated Results Analysis Section -->
                <div class="simulation-results-section" style="display: grid; grid-template-columns: 1fr 1.2fr; gap: 20px; border-top: 1px solid var(--color-border); padding-top: 20px; margin-top: 10px;">
                    <!-- Left Column: Primary Readout & Formula -->
                    <div style="display: flex; flex-direction: column; gap: 16px;">
                        <div class="ratio-readout-box" style="padding: 16px; min-height: 100px;">
                            <div class="ratio-title">구동 체인 유효 인장력</div>
                            <div class="ratio-value" id="txt-primary-tension" style="font-size: 24px;">3,073 N</div>
                            <div class="ratio-type" id="txt-secondary-tension" style="font-size: 11px;">313.4 kgf (중력 단위)</div>
                        </div>
                        <div class="formula-card" style="padding: 12px; font-size: 11px;">
                            <h4 style="font-size: 12px; margin-bottom: 6px;"><i class="fa-solid fa-circle-info text-cyan"></i> 스프로킷 피치원 설계 공식</h4>
                            <div class="formula-equation" style="font-size: 12px; padding: 6px; margin-bottom: 6px;">
                                PCD = P / sin(180° / Z)
                            </div>
                            <div class="formula-equation" style="font-size: 12px; padding: 6px;">
                                F [N] = Power [kW] × 1000 / v [m/s]
                            </div>
                        </div>
                    </div>

                    <!-- Right Column: Detailed Metrics Grid -->
                    <div class="results-grid" style="display: flex; flex-direction: column; gap: 10px; justify-content: center;">
                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-link"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">구동측 피치경 (PCD₁)</span>
                                <span class="card-value" id="res-pcd1" style="font-size: 15px;">69.1 mm</span>
                            </div>
                        </div>

                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-ring"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">피동측 피치경 (PCD₂)</span>
                                <span class="card-value" id="res-pcd2" style="font-size: 15px;">137.8 mm</span>
                            </div>
                        </div>

                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-arrows-left-right"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">피동 스프로킷 회전수 (N₂)</span>
                                <span class="card-value" id="res-rpm2" style="font-size: 15px;">180 RPM</span>
                            </div>
                        </div>

                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-circle-nodes"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">윤활 방식 가이드 (KS 기준)</span>
                                <span class="card-value" id="res-lub-type" style="color: var(--color-success); font-size: 12px;">오일 배스 유침 식 윤활</span>
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        </main>
    </div>

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

            // 1. Core DOM queries
            const selectChainType = document.getElementById('select-chain-type');
            
            const inputZ1 = document.getElementById('input-z1');
            const sliderZ1 = document.getElementById('slider-z1');
            const inputZ2 = document.getElementById('input-z2');
            const sliderZ2 = document.getElementById('slider-z2');
            const inputC = document.getElementById('input-c');
            const sliderC = document.getElementById('slider-c');
            const inputRpm = document.getElementById('input-rpm');
            const sliderRpm = document.getElementById('slider-rpm');
            const inputPower = document.getElementById('input-power');

            const txtPrimaryTension = document.getElementById('txt-primary-tension');
            const txtSecondaryTension = document.getElementById('txt-secondary-tension');

            const resPcd1 = document.getElementById('res-pcd1');
            const resPcd2 = document.getElementById('res-pcd2');
            const resRpm2 = document.getElementById('res-rpm2');
            const resLubType = document.getElementById('res-lub-type');

            const valChainSpeed = document.getElementById('val-chain-speed');
            const valPolygonRatio = document.getElementById('val-polygon-ratio');
            const valMeshFreq = document.getElementById('val-mesh-freq');

            const btnPlayPause = document.getElementById('btn-play-pause');
            const btnReverse = document.getElementById('btn-reverse');
            const btnResetPos = document.getElementById('btn-reset-pos');
            const overlayStatus = document.getElementById('overlay-status');

            // 2. Constants & Physics State
            const state = {
                pitch: 12.7, // ANSI 40 basic
                z1: 17,
                z2: 34,
                c: 450,
                rpm1: 360,
                power: 7.5, // kW
                isPlaying: true,
                direction: 1, // 1 (clockwise), -1 (counter-clockwise)
                angle1: 0.0,
                angle2: 0.0,
                chainOffset: 0.0
            };

            const ranges = {
                z1: { min: 9, max: 80 },
                z2: { min: 9, max: 80 },
                c: { min: 150, max: 1500 },
                rpm1: { min: 0, max: 3000 },
                power: { min: 0.1, max: 150.0 }
            };

            // 3. Mathematical Solvers
            function calculateChainSystem() {
                const pitch = state.pitch;
                const z1 = state.z1;
                const z2 = state.z2;
                const rpm1 = state.rpm1;
                const power = state.power;

                // 1. PCD Calculations
                // PCD = P / sin(180 / Z)
                const pcd1 = pitch / Math.sin(Math.PI / z1);
                const pcd2 = pitch / Math.sin(Math.PI / z2);

                // 2. Speed and Ratios
                const rpm2 = rpm1 * (z1 / z2);
                
                // Chain Linear Speed (m/s)
                // v = PCD1 * pi * RPM1 / (60 * 1000)
                const speed = (pcd1 * Math.PI * rpm1) / 60000;

                // 3. Chain tension forces
                // F = P * 1000 / v (only when spinning, else static pretension)
                let tension = 0;
                if (speed > 0.001) {
                    tension = (power * 1000) / speed;
                }
                const tensionKgf = tension / 9.80665;

                // 4. Polygon Speed Variation ratio
                // epsilon = 1 - cos(180 / Z)
                const epsilon = 1.0 - Math.cos(Math.PI / z1);
                const polygonRatio = epsilon * 100;

                // 5. Meshing Frequency
                // f = (Z1 * RPM1) / 60
                const meshFreq = (z1 * rpm1) / 60;

                // 6. Lubrication method recommendation (KS standard based on speed)
                let lubType = '수동 유적 식 (기름 똑똑)';
                if (speed > 1.5) {
                    if (speed < 8.0) {
                        lubType = '오일 배스 유침 식 윤활';
                    } else {
                        lubType = '강제 펌프 송유 강제식';
                    }
                }

                // Render in UI
                resPcd1.innerText = pcd1.toFixed(1) + ' mm';
                resPcd2.innerText = pcd2.toFixed(1) + ' mm';
                resRpm2.innerText = rpm2.toFixed(0) + ' RPM';
                resLubType.innerText = lubType;

                valChainSpeed.innerText = speed.toFixed(2) + ' m/s';
                valPolygonRatio.innerText = polygonRatio.toFixed(2) + '%';
                valMeshFreq.innerText = meshFreq.toFixed(1) + ' Hz';

                txtPrimaryTension.innerText = tension.toLocaleString('ko-KR', { maximumFractionDigits: 0 }) + ' N';
                txtSecondaryTension.innerText = tensionKgf.toFixed(1) + ' kgf';

                // Status Overlay Warnings
                if (z1 < 15) {
                    overlayStatus.innerText = '주의: 스프로킷 잇수 너무 적음 (진동 과다)';
                    overlayStatus.style.color = '#f59e0b';
                } else if (speed > 10.0) {
                    overlayStatus.innerText = '고속 주행 중 (고온 발열 경계)';
                    overlayStatus.style.color = '#f59e0b';
                } else {
                    overlayStatus.innerText = '정상 동력 전동 중';
                    overlayStatus.style.color = '#10b981';
                }
            }

            // Sync methods for Inputs
            function syncZ1FromInput() {
                let val = parseInt(inputZ1.value);
                if (isNaN(val)) val = ranges.z1.min;
                if (val < ranges.z1.min) val = ranges.z1.min;
                if (val > ranges.z1.max) val = ranges.z1.max;
                state.z1 = val;
                inputZ1.value = val;
                sliderZ1.value = val;
                calculateChainSystem();
            }

            function syncZ1FromSlider() {
                state.z1 = parseInt(sliderZ1.value);
                inputZ1.value = state.z1;
                calculateChainSystem();
            }

            function syncZ2FromInput() {
                let val = parseInt(inputZ2.value);
                if (isNaN(val)) val = ranges.z2.min;
                if (val < ranges.z2.min) val = ranges.z2.min;
                if (val > ranges.z2.max) val = ranges.z2.max;
                state.z2 = val;
                inputZ2.value = val;
                sliderZ2.value = val;
                calculateChainSystem();
            }

            function syncZ2FromSlider() {
                state.z2 = parseInt(sliderZ2.value);
                inputZ2.value = state.z2;
                calculateChainSystem();
            }

            function syncCFromInput() {
                let val = parseInt(inputC.value);
                if (isNaN(val)) val = ranges.c.min;
                if (val < ranges.c.min) val = ranges.c.min;
                if (val > ranges.c.max) val = ranges.c.max;
                state.c = val;
                inputC.value = val;
                sliderC.value = val;
                calculateChainSystem();
            }

            function syncCFromSlider() {
                state.c = parseInt(sliderC.value);
                inputC.value = state.c;
                calculateChainSystem();
            }

            function syncRpmFromInput() {
                let val = parseInt(inputRpm.value);
                if (isNaN(val)) val = ranges.rpm1.min;
                if (val < ranges.rpm1.min) val = ranges.rpm1.min;
                if (val > ranges.rpm1.max) val = ranges.rpm1.max;
                state.rpm1 = val;
                inputRpm.value = val;
                sliderRpm.value = val;
                calculateChainSystem();
            }

            function syncRpmFromSlider() {
                state.rpm1 = parseInt(sliderRpm.value);
                inputRpm.value = state.rpm1;
                calculateChainSystem();
            }

            function syncPowerFromInput() {
                let val = parseFloat(inputPower.value);
                if (isNaN(val)) val = ranges.power.min;
                if (val < ranges.power.min) val = ranges.power.min;
                if (val > ranges.power.max) val = ranges.power.max;
                state.power = val;
                inputPower.value = val.toFixed(1);
                calculateChainSystem();
            }

            // Presets implementation
            const presetBtns = document.querySelectorAll('.preset-btn');
            presetBtns.forEach(btn => {
                btn.addEventListener('click', function() {
                    presetBtns.forEach(b => b.classList.remove('active'));
                    this.classList.add('active');

                    const preset = this.getAttribute('data-preset');
                    if (preset === 'reduction') {
                        state.pitch = 12.7; // ANSI 40
                        selectChainType.value = "12.7";
                        state.z1 = 17;
                        state.z2 = 34;
                        state.c = 450;
                        state.rpm1 = 360;
                        state.power = 7.5;
                    } else if (preset === 'heavy') {
                        state.pitch = 25.4; // ANSI 80
                        selectChainType.value = "25.4";
                        state.z1 = 19;
                        state.z2 = 57;
                        state.c = 650;
                        state.rpm1 = 150;
                        state.power = 37.0;
                    } else if (preset === 'highspeed') {
                        state.pitch = 9.525; // ANSI 35
                        selectChainType.value = "9.525";
                        state.z1 = 24;
                        state.z2 = 16;
                        state.c = 300;
                        state.rpm1 = 1200;
                        state.power = 3.5;
                    }

                    // Sync values to DOM inputs
                    inputZ1.value = state.z1;
                    sliderZ1.value = state.z1;
                    inputZ2.value = state.z2;
                    sliderZ2.value = state.z2;
                    inputC.value = state.c;
                    sliderC.value = state.c;
                    inputRpm.value = state.rpm1;
                    sliderRpm.value = state.rpm1;
                    inputPower.value = state.power;

                    calculateChainSystem();
                });
            });

            // 4. Canvas rendering loop
            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();
            // Polling backups to guarantee canvas is sized correctly after WordPress page loads
            setTimeout(initCanvas, 100);
            setTimeout(initCanvas, 300);
            setTimeout(initCanvas, 800);

            // Draw Sprocket teeth & flanges
            function drawSprocketDetails(c, x, y, teeth, pcd, angleOffset, themeColor) {
                const r_pitch = pcd / 2;
                const r_outer = r_pitch + state.pitch * 0.3;
                const r_root = r_pitch - state.pitch * 0.35;
                
                c.save();
                c.translate(x, y);
                c.rotate(angleOffset);

                // 1. Draw Sprocket outline path (teeth shapes)
                c.beginPath();
                c.strokeStyle = themeColor;
                c.lineWidth = 2;
                c.fillStyle = 'rgba(241, 245, 249, 0.85)';
                
                for (let i = 0; i < teeth; i++) {
                    const stepAngle = (i * Math.PI * 2) / teeth;
                    const halfStep = Math.PI / teeth;
                    
                    const tx_out = Math.cos(stepAngle) * r_outer;
                    const ty_out = Math.sin(stepAngle) * r_outer;
                    const tx_root = Math.cos(stepAngle + halfStep) * r_root;
                    const ty_root = Math.sin(stepAngle + halfStep) * r_root;

                    if (i === 0) {
                        c.moveTo(tx_out, ty_out);
                    } else {
                        c.lineTo(tx_out, ty_out);
                    }
                    c.lineTo(tx_root, ty_root);
                }
                c.closePath();
                c.fill();
                c.stroke();

                // 2. Draw Pitch Circle (light dashed line)
                c.beginPath();
                c.arc(0, 0, r_pitch, 0, Math.PI * 2);
                c.strokeStyle = 'rgba(15, 23, 42, 0.15)';
                c.lineWidth = 1;
                c.setLineDash([4, 4]);
                c.stroke();
                c.setLineDash([]);

                // 3. Central Axis Hub
                c.beginPath();
                c.arc(0, 0, Math.max(r_pitch * 0.3, 10), 0, Math.PI * 2);
                c.strokeStyle = 'rgba(15, 23, 42, 0.12)';
                c.lineWidth = 2;
                c.fillStyle = '#ffffff';
                c.fill();
                c.stroke();

                c.beginPath();
                c.arc(0, 0, 4, 0, Math.PI * 2);
                c.fillStyle = '#1e293b';
                c.fill();

                c.restore();
            }

            function drawDiameterLeader(c, x, y, r, valStr, labelPrefix, side) {
                c.save();
                c.strokeStyle = 'rgba(71, 85, 105, 0.4)';
                c.lineWidth = 1.2;
                
                // Angle of leader line (45 degrees up-left or up-right)
                const angle = side === -1 ? -Math.PI * 0.75 : -Math.PI * 0.25;
                const px = x + Math.cos(angle) * r;
                const py = y + Math.sin(angle) * r;
                
                // Draw a beautiful small dot on the circle boundary
                c.beginPath();
                c.arc(px, py, 2.5, 0, Math.PI * 2);
                c.fillStyle = side === -1 ? '#0284c7' : '#db2777';
                c.fill();
                
                // Draw leader lines: from circle boundary diagonal outwards, then horizontal
                const lx1 = px + Math.cos(angle) * 20;
                const ly1 = py + Math.sin(angle) * 20;
                const lx2 = lx1 + side * 45;
                const ly2 = ly1;
                
                c.beginPath();
                c.moveTo(px, py);
                c.lineTo(lx1, ly1);
                c.lineTo(lx2, ly2);
                c.stroke();
                
                // Draw text above the horizontal line
                const text = `${labelPrefix} ${valStr}`;
                c.font = 'bold 10px var(--font-body)';
                
                const tx = side === -1 ? lx1 - 4 : lx1 + 4;
                c.fillStyle = '#1e293b';
                c.textAlign = side === -1 ? 'right' : 'left';
                c.textBaseline = 'bottom';
                c.fillText(text, tx, ly2 - 1);
                c.restore();
            }

            // Main animation loop
            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;

                // Speed calculations
                const rpm1 = state.rpm1;
                const z1 = state.z1;
                const z2 = state.z2;
                
                const omega1 = (2 * Math.PI * rpm1) / 60;
                const omega2 = omega1 * (z1 / z2);

                if (state.isPlaying) {
                    const step1 = state.direction * omega1 * dt;
                    const step2 = state.direction * omega2 * dt;
                    state.angle1 += step1;
                    state.angle2 += step2;
                    
                    // Moving chain loops
                    const pitch = state.pitch;
                    const pcd1 = pitch / Math.sin(Math.PI / z1);
                    const linearSpeed = (pcd1 * Math.PI * rpm1) / 60000;
                    state.chainOffset += state.direction * linearSpeed * dt * 1000; // in mm
                }

                // Clear &#038; draw মোতি프
                ctx.clearRect(0, 0, width, height);

                // 1. Draw 모눈종이 Blueprint Grid
                ctx.strokeStyle = 'rgba(2, 132, 199, 0.06)';
                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();
                }

                // 2. Positions of sprockets
                const pitch = state.pitch;
                const pcd1 = pitch / Math.sin(Math.PI / z1);
                const pcd2 = pitch / Math.sin(Math.PI / z2);
                
                const r1 = pcd1 / 2;
                const r2 = pcd2 / 2;

                // Scale dynamically based on the maximum possible center distance (1500mm)
                // so that the sprockets never go off-screen even at the absolute maximum slider value!
                const scaleWidth = (width - 190) / (state.c + r1 + r2);
                const scaleHeight = (height - 120) / Math.max(pcd1, pcd2);
                const visualScale = Math.min(scaleWidth, scaleHeight);
                
                const R1_vis = r1 * visualScale;
                const R2_vis = r2 * visualScale;
                const C_vis = state.c * visualScale;
                
                // Position centers to perfectly center the entire bounding box
                const x1 = (width - C_vis + R1_vis - R2_vis) / 2;
                const y1 = height / 2;
                const x2 = x1 + C_vis;
                const y2 = y1;

                // 3. Draw Sprocket 1 &#038; 2
                drawSprocketDetails(ctx, x1, y1, z1, pcd1 * visualScale, state.angle1, '#0284c7');
                drawSprocketDetails(ctx, x2, y2, z2, pcd2 * visualScale, state.angle2, '#db2777');

                // 4. Draw Chain loop wrapping them (Tangents outer wrapping)
                // Tangent angle theta
                // Since y1 == y2, it is a horizontal sprocket drive
                // Tangents angle is alpha = asin((r2 - r1)/c)
                const deltaR = R2_vis - R1_vis;
                const alpha = Math.asin(deltaR / C_vis);

                // Tangent points on sprocket 1
                const tp1_x_top = x1 - R1_vis * Math.sin(alpha);
                const tp1_y_top = y1 - R1_vis * Math.cos(alpha);
                const tp1_x_bottom = x1 - R1_vis * Math.sin(alpha);
                const tp1_y_bottom = y1 + R1_vis * Math.cos(alpha);

                // Tangent points on sprocket 2
                const tp2_x_top = x2 - R2_vis * Math.sin(alpha);
                const tp2_y_top = y2 - R2_vis * Math.cos(alpha);
                const tp2_x_bottom = x2 - R2_vis * Math.sin(alpha);
                const tp2_y_bottom = y2 + R2_vis * Math.cos(alpha);

                // 5. Draw the loop paths
                ctx.save();
                ctx.strokeStyle = '#ffffff';
                ctx.lineWidth = 7;
                ctx.lineCap = 'round';
                ctx.lineJoin = 'round';
                
                // Inner belt outline backdrop
                ctx.beginPath();
                ctx.moveTo(tp1_x_top, tp1_y_top);
                ctx.lineTo(tp2_x_top, tp2_y_top);
                ctx.arc(x2, y2, R2_vis, -Math.PI / 2 - alpha, Math.PI / 2 + alpha, false);
                ctx.lineTo(tp1_x_bottom, tp1_y_bottom);
                ctx.arc(x1, y1, R1_vis, Math.PI / 2 + alpha, -Math.PI / 2 - alpha, false);
                ctx.closePath();
                
                ctx.stroke();

                // Chain base dark grey path
                ctx.strokeStyle = '#475569';
                ctx.lineWidth = 5;
                ctx.stroke();

                // 6. Draw moving roller chain links dashes
                // Chain rollers are individual dots along the path
                ctx.strokeStyle = '#ffffff';
                ctx.lineWidth = 3;
                
                const linkStep = pitch * visualScale;
                ctx.setLineDash([4, linkStep - 4]);
                ctx.lineDashOffset = -state.chainOffset * visualScale;
                ctx.stroke();
                ctx.setLineDash([]);
                ctx.restore();

                // 7. Highlight Tension tight side (top tangent path represents load)
                if (state.rpm1 > 0) {
                    ctx.save();
                    const powerLoad = state.power;
                    
                    ctx.strokeStyle = '#db2777';
                    ctx.lineWidth = Math.min(powerLoad * 0.05 + 1.5, 5);
                    ctx.shadowBlur = 8;
                    ctx.shadowColor = '#db2777';
                    
                    ctx.beginPath();
                    ctx.moveTo(tp1_x_top, tp1_y_top);
                    ctx.lineTo(tp2_x_top, tp2_y_top);
                    ctx.stroke();
                    ctx.restore();

                    // Tension arrow
                    ctx.save();
                    ctx.translate((tp1_x_top + tp2_x_top) / 2, (tp1_y_top + tp2_y_top) / 2 - 12);
                    ctx.fillStyle = '#db2777';
                    ctx.font = 'bold 10px Inter';
                    ctx.textAlign = 'center';
                    ctx.fillText('인장 긴장측 (TIGHT SIDE)', 0, 0);
                    ctx.restore();
                }

                // Draw Diameter dimensions on sprockets with professional CAD leader lines
                drawDiameterLeader(ctx, x1, y1, R1_vis, pcd1.toFixed(1) + ' mm', 'PCD₁ =', -1);
                drawDiameterLeader(ctx, x2, y2, R2_vis, pcd2.toFixed(1) + ' mm', 'PCD₂ =', 1);

                // Horizontal Center Distance Dimension Line (C)
                ctx.save();
                ctx.strokeStyle = 'rgba(71, 85, 105, 0.3)';
                ctx.lineWidth = 1;
                
                const dimY = y1 + R2_vis + 25; // 25px below larger sprocket
                
                ctx.beginPath();
                ctx.moveTo(x1, y1 + 10);
                ctx.lineTo(x1, dimY + 5);
                ctx.moveTo(x2, y2 + 10);
                ctx.lineTo(x2, dimY + 5);
                ctx.stroke();
                
                ctx.beginPath();
                ctx.moveTo(x1, dimY);
                ctx.lineTo(x2, dimY);
                ctx.stroke();
                
                function drawHorizontalArrowhead(px, py, direction) {
                    ctx.save();
                    ctx.translate(px, py);
                    ctx.fillStyle = 'rgba(71, 85, 105, 0.5)';
                    ctx.beginPath();
                    ctx.moveTo(0, 0);
                    ctx.lineTo(direction * 6, -3);
                    ctx.lineTo(direction * 6, 3);
                    ctx.closePath();
                    ctx.fill();
                    ctx.restore();
                }
                drawHorizontalArrowhead(x1, dimY, 1);
                drawHorizontalArrowhead(x2, dimY, -1);
                
                const cText = `C = ${state.c} mm`;
                ctx.font = 'bold 9px Inter, sans-serif';
                const cTextWidth = ctx.measureText(cText).width;
                const cTextX = (x1 + x2) / 2;
                // Draw white eraser background so text doesn't overlap dimension line
                ctx.fillStyle = '#f1f5f9';
                ctx.fillRect(cTextX - cTextWidth / 2 - 4, dimY - 6, cTextWidth + 8, 12);
                ctx.fillStyle = '#475569';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText(cText, cTextX, dimY);
                ctx.restore();

                requestAnimationFrame(animate);
            }
            requestAnimationFrame(animate);

            // 5. Events & Inputs Binding
            selectChainType.addEventListener('change', function() {
                state.pitch = parseFloat(this.value);
                calculateChainSystem();
            });

            inputZ1.addEventListener('change', syncZ1FromInput);
            sliderZ1.addEventListener('input', syncZ1FromSlider);

            inputZ2.addEventListener('change', syncZ2FromInput);
            sliderZ2.addEventListener('input', syncZ2FromSlider);

            inputC.addEventListener('change', syncCFromInput);
            sliderC.addEventListener('input', syncCFromSlider);

            inputRpm.addEventListener('change', syncRpmFromInput);
            sliderRpm.addEventListener('input', syncRpmFromSlider);

            inputPower.addEventListener('change', syncPowerFromInput);

            btnPlayPause.addEventListener('click', function() {
                state.isPlaying = !state.isPlaying;
                this.innerHTML = state.isPlaying ? '<i class="fa-solid fa-pause"></i> <span>일시정지</span>' : '<i class="fa-solid fa-play"></i> <span>재생</span>';
            });

            btnReverse.addEventListener('click', function() {
                state.direction *= -1;
            });

            btnResetPos.addEventListener('click', function() {
                state.angle1 = 0;
                state.angle2 = 0;
                state.chainOffset = 0;
            });

            // Set initial state
            calculateChainSystem();

            // 6. Right-click and Devtools block protection nested if style
            (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 });
                    
                    document.addEventListener('keydown', function(e) {
                        if (e.key === 'F12') {
                            e.preventDefault();
                            alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                            return false;
                        }
                        if (e.ctrlKey) {
                            if (e.key === 'u' || e.key === 'c' || e.key === 's') {
                                e.preventDefault();
                                alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                                return false;
                            }
                        }
                        if (e.ctrlKey) {
                            if (e.shiftKey) {
                                if (e.key === 'i' || e.key === 'I' || e.key === 'j' || e.key === 'J' || e.key === 'c' || e.key === 'C') {
                                    e.preventDefault();
                                    alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                                    return false;
                                }
                            }
                        }
                    }, { capture: true });
                }
                
                if (document.readyState === 'complete' || document.readyState === 'interactive') {
                    blockEvents();
                } else {
                    document.addEventListener('DOMContentLoaded', blockEvents);
                    window.addEventListener('load', blockEvents);
                }
            })();

        })();
    </script>

    </div>
</div>

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

            // 1. Core DOM queries
            const selectChainType = document.getElementById('select-chain-type');
            
            const inputZ1 = document.getElementById('input-z1');
            const sliderZ1 = document.getElementById('slider-z1');
            const inputZ2 = document.getElementById('input-z2');
            const sliderZ2 = document.getElementById('slider-z2');
            const inputC = document.getElementById('input-c');
            const sliderC = document.getElementById('slider-c');
            const inputRpm = document.getElementById('input-rpm');
            const sliderRpm = document.getElementById('slider-rpm');
            const inputPower = document.getElementById('input-power');

            const txtPrimaryTension = document.getElementById('txt-primary-tension');
            const txtSecondaryTension = document.getElementById('txt-secondary-tension');

            const resPcd1 = document.getElementById('res-pcd1');
            const resPcd2 = document.getElementById('res-pcd2');
            const resRpm2 = document.getElementById('res-rpm2');
            const resLubType = document.getElementById('res-lub-type');

            const valChainSpeed = document.getElementById('val-chain-speed');
            const valPolygonRatio = document.getElementById('val-polygon-ratio');
            const valMeshFreq = document.getElementById('val-mesh-freq');

            const btnPlayPause = document.getElementById('btn-play-pause');
            const btnReverse = document.getElementById('btn-reverse');
            const btnResetPos = document.getElementById('btn-reset-pos');
            const overlayStatus = document.getElementById('overlay-status');

            // 2. Constants & Physics State
            const state = {
                pitch: 12.7, // ANSI 40 basic
                z1: 17,
                z2: 34,
                c: 450,
                rpm1: 360,
                power: 7.5, // kW
                isPlaying: true,
                direction: 1, // 1 (clockwise), -1 (counter-clockwise)
                angle1: 0.0,
                angle2: 0.0,
                chainOffset: 0.0
            };

            const ranges = {
                z1: { min: 9, max: 80 },
                z2: { min: 9, max: 80 },
                c: { min: 150, max: 1500 },
                rpm1: { min: 0, max: 3000 },
                power: { min: 0.1, max: 150.0 }
            };

            // 3. Mathematical Solvers
            function calculateChainSystem() {
                const pitch = state.pitch;
                const z1 = state.z1;
                const z2 = state.z2;
                const rpm1 = state.rpm1;
                const power = state.power;

                // 1. PCD Calculations
                // PCD = P / sin(180 / Z)
                const pcd1 = pitch / Math.sin(Math.PI / z1);
                const pcd2 = pitch / Math.sin(Math.PI / z2);

                // 2. Speed and Ratios
                const rpm2 = rpm1 * (z1 / z2);
                
                // Chain Linear Speed (m/s)
                // v = PCD1 * pi * RPM1 / (60 * 1000)
                const speed = (pcd1 * Math.PI * rpm1) / 60000;

                // 3. Chain tension forces
                // F = P * 1000 / v (only when spinning, else static pretension)
                let tension = 0;
                if (speed > 0.001) {
                    tension = (power * 1000) / speed;
                }
                const tensionKgf = tension / 9.80665;

                // 4. Polygon Speed Variation ratio
                // epsilon = 1 - cos(180 / Z)
                const epsilon = 1.0 - Math.cos(Math.PI / z1);
                const polygonRatio = epsilon * 100;

                // 5. Meshing Frequency
                // f = (Z1 * RPM1) / 60
                const meshFreq = (z1 * rpm1) / 60;

                // 6. Lubrication method recommendation (KS standard based on speed)
                let lubType = '수동 유적 식 (기름 똑똑)';
                if (speed > 1.5) {
                    if (speed < 8.0) {
                        lubType = '오일 배스 유침 식 윤활';
                    } else {
                        lubType = '강제 펌프 송유 강제식';
                    }
                }

                // Render in UI
                resPcd1.innerText = pcd1.toFixed(1) + ' mm';
                resPcd2.innerText = pcd2.toFixed(1) + ' mm';
                resRpm2.innerText = rpm2.toFixed(0) + ' RPM';
                resLubType.innerText = lubType;

                valChainSpeed.innerText = speed.toFixed(2) + ' m/s';
                valPolygonRatio.innerText = polygonRatio.toFixed(2) + '%';
                valMeshFreq.innerText = meshFreq.toFixed(1) + ' Hz';

                txtPrimaryTension.innerText = tension.toLocaleString('ko-KR', { maximumFractionDigits: 0 }) + ' N';
                txtSecondaryTension.innerText = tensionKgf.toFixed(1) + ' kgf';

                // Status Overlay Warnings
                if (z1 < 15) {
                    overlayStatus.innerText = '주의: 스프로킷 잇수 너무 적음 (진동 과다)';
                    overlayStatus.style.color = '#f59e0b';
                } else if (speed > 10.0) {
                    overlayStatus.innerText = '고속 주행 중 (고온 발열 경계)';
                    overlayStatus.style.color = '#f59e0b';
                } else {
                    overlayStatus.innerText = '정상 동력 전동 중';
                    overlayStatus.style.color = '#10b981';
                }
            }

            // Sync methods for Inputs
            function syncZ1FromInput() {
                let val = parseInt(inputZ1.value);
                if (isNaN(val)) val = ranges.z1.min;
                if (val < ranges.z1.min) val = ranges.z1.min;
                if (val > ranges.z1.max) val = ranges.z1.max;
                state.z1 = val;
                inputZ1.value = val;
                sliderZ1.value = val;
                calculateChainSystem();
            }

            function syncZ1FromSlider() {
                state.z1 = parseInt(sliderZ1.value);
                inputZ1.value = state.z1;
                calculateChainSystem();
            }

            function syncZ2FromInput() {
                let val = parseInt(inputZ2.value);
                if (isNaN(val)) val = ranges.z2.min;
                if (val < ranges.z2.min) val = ranges.z2.min;
                if (val > ranges.z2.max) val = ranges.z2.max;
                state.z2 = val;
                inputZ2.value = val;
                sliderZ2.value = val;
                calculateChainSystem();
            }

            function syncZ2FromSlider() {
                state.z2 = parseInt(sliderZ2.value);
                inputZ2.value = state.z2;
                calculateChainSystem();
            }

            function syncCFromInput() {
                let val = parseInt(inputC.value);
                if (isNaN(val)) val = ranges.c.min;
                if (val < ranges.c.min) val = ranges.c.min;
                if (val > ranges.c.max) val = ranges.c.max;
                state.c = val;
                inputC.value = val;
                sliderC.value = val;
                calculateChainSystem();
            }

            function syncCFromSlider() {
                state.c = parseInt(sliderC.value);
                inputC.value = state.c;
                calculateChainSystem();
            }

            function syncRpmFromInput() {
                let val = parseInt(inputRpm.value);
                if (isNaN(val)) val = ranges.rpm1.min;
                if (val < ranges.rpm1.min) val = ranges.rpm1.min;
                if (val > ranges.rpm1.max) val = ranges.rpm1.max;
                state.rpm1 = val;
                inputRpm.value = val;
                sliderRpm.value = val;
                calculateChainSystem();
            }

            function syncRpmFromSlider() {
                state.rpm1 = parseInt(sliderRpm.value);
                inputRpm.value = state.rpm1;
                calculateChainSystem();
            }

            function syncPowerFromInput() {
                let val = parseFloat(inputPower.value);
                if (isNaN(val)) val = ranges.power.min;
                if (val < ranges.power.min) val = ranges.power.min;
                if (val > ranges.power.max) val = ranges.power.max;
                state.power = val;
                inputPower.value = val.toFixed(1);
                calculateChainSystem();
            }

            // Presets implementation
            const presetBtns = document.querySelectorAll('.preset-btn');
            presetBtns.forEach(btn => {
                btn.addEventListener('click', function() {
                    presetBtns.forEach(b => b.classList.remove('active'));
                    this.classList.add('active');

                    const preset = this.getAttribute('data-preset');
                    if (preset === 'reduction') {
                        state.pitch = 12.7; // ANSI 40
                        selectChainType.value = "12.7";
                        state.z1 = 17;
                        state.z2 = 34;
                        state.c = 450;
                        state.rpm1 = 360;
                        state.power = 7.5;
                    } else if (preset === 'heavy') {
                        state.pitch = 25.4; // ANSI 80
                        selectChainType.value = "25.4";
                        state.z1 = 19;
                        state.z2 = 57;
                        state.c = 650;
                        state.rpm1 = 150;
                        state.power = 37.0;
                    } else if (preset === 'highspeed') {
                        state.pitch = 9.525; // ANSI 35
                        selectChainType.value = "9.525";
                        state.z1 = 24;
                        state.z2 = 16;
                        state.c = 300;
                        state.rpm1 = 1200;
                        state.power = 3.5;
                    }

                    // Sync values to DOM inputs
                    inputZ1.value = state.z1;
                    sliderZ1.value = state.z1;
                    inputZ2.value = state.z2;
                    sliderZ2.value = state.z2;
                    inputC.value = state.c;
                    sliderC.value = state.c;
                    inputRpm.value = state.rpm1;
                    sliderRpm.value = state.rpm1;
                    inputPower.value = state.power;

                    calculateChainSystem();
                });
            });

            // 4. Canvas rendering loop
            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();
            // Polling backups to guarantee canvas is sized correctly after WordPress page loads
            setTimeout(initCanvas, 100);
            setTimeout(initCanvas, 300);
            setTimeout(initCanvas, 800);

            // Draw Sprocket teeth & flanges
            function drawSprocketDetails(c, x, y, teeth, pcd, angleOffset, themeColor) {
                const r_pitch = pcd / 2;
                const r_outer = r_pitch + state.pitch * 0.3;
                const r_root = r_pitch - state.pitch * 0.35;
                
                c.save();
                c.translate(x, y);
                c.rotate(angleOffset);

                // 1. Draw Sprocket outline path (teeth shapes)
                c.beginPath();
                c.strokeStyle = themeColor;
                c.lineWidth = 2;
                c.fillStyle = 'rgba(241, 245, 249, 0.85)';
                
                for (let i = 0; i < teeth; i++) {
                    const stepAngle = (i * Math.PI * 2) / teeth;
                    const halfStep = Math.PI / teeth;
                    
                    const tx_out = Math.cos(stepAngle) * r_outer;
                    const ty_out = Math.sin(stepAngle) * r_outer;
                    const tx_root = Math.cos(stepAngle + halfStep) * r_root;
                    const ty_root = Math.sin(stepAngle + halfStep) * r_root;

                    if (i === 0) {
                        c.moveTo(tx_out, ty_out);
                    } else {
                        c.lineTo(tx_out, ty_out);
                    }
                    c.lineTo(tx_root, ty_root);
                }
                c.closePath();
                c.fill();
                c.stroke();

                // 2. Draw Pitch Circle (light dashed line)
                c.beginPath();
                c.arc(0, 0, r_pitch, 0, Math.PI * 2);
                c.strokeStyle = 'rgba(15, 23, 42, 0.15)';
                c.lineWidth = 1;
                c.setLineDash([4, 4]);
                c.stroke();
                c.setLineDash([]);

                // 3. Central Axis Hub
                c.beginPath();
                c.arc(0, 0, Math.max(r_pitch * 0.3, 10), 0, Math.PI * 2);
                c.strokeStyle = 'rgba(15, 23, 42, 0.12)';
                c.lineWidth = 2;
                c.fillStyle = '#ffffff';
                c.fill();
                c.stroke();

                c.beginPath();
                c.arc(0, 0, 4, 0, Math.PI * 2);
                c.fillStyle = '#1e293b';
                c.fill();

                c.restore();
            }

            function drawDiameterLeader(c, x, y, r, valStr, labelPrefix, side) {
                c.save();
                c.strokeStyle = 'rgba(71, 85, 105, 0.4)';
                c.lineWidth = 1.2;
                
                // Angle of leader line (45 degrees up-left or up-right)
                const angle = side === -1 ? -Math.PI * 0.75 : -Math.PI * 0.25;
                const px = x + Math.cos(angle) * r;
                const py = y + Math.sin(angle) * r;
                
                // Draw a beautiful small dot on the circle boundary
                c.beginPath();
                c.arc(px, py, 2.5, 0, Math.PI * 2);
                c.fillStyle = side === -1 ? '#0284c7' : '#db2777';
                c.fill();
                
                // Draw leader lines: from circle boundary diagonal outwards, then horizontal
                const lx1 = px + Math.cos(angle) * 20;
                const ly1 = py + Math.sin(angle) * 20;
                const lx2 = lx1 + side * 45;
                const ly2 = ly1;
                
                c.beginPath();
                c.moveTo(px, py);
                c.lineTo(lx1, ly1);
                c.lineTo(lx2, ly2);
                c.stroke();
                
                // Draw text above the horizontal line
                const text = `${labelPrefix} ${valStr}`;
                c.font = 'bold 10px var(--font-body)';
                
                const tx = side === -1 ? lx1 - 4 : lx1 + 4;
                c.fillStyle = '#1e293b';
                c.textAlign = side === -1 ? 'right' : 'left';
                c.textBaseline = 'bottom';
                c.fillText(text, tx, ly2 - 1);
                c.restore();
            }

            // Main animation loop
            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;

                // Speed calculations
                const rpm1 = state.rpm1;
                const z1 = state.z1;
                const z2 = state.z2;
                
                const omega1 = (2 * Math.PI * rpm1) / 60;
                const omega2 = omega1 * (z1 / z2);

                if (state.isPlaying) {
                    const step1 = state.direction * omega1 * dt;
                    const step2 = state.direction * omega2 * dt;
                    state.angle1 += step1;
                    state.angle2 += step2;
                    
                    // Moving chain loops
                    const pitch = state.pitch;
                    const pcd1 = pitch / Math.sin(Math.PI / z1);
                    const linearSpeed = (pcd1 * Math.PI * rpm1) / 60000;
                    state.chainOffset += state.direction * linearSpeed * dt * 1000; // in mm
                }

                // Clear &#038; draw মোতি프
                ctx.clearRect(0, 0, width, height);

                // 1. Draw 모눈종이 Blueprint Grid
                ctx.strokeStyle = 'rgba(2, 132, 199, 0.06)';
                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();
                }

                // 2. Positions of sprockets
                const pitch = state.pitch;
                const pcd1 = pitch / Math.sin(Math.PI / z1);
                const pcd2 = pitch / Math.sin(Math.PI / z2);
                
                const r1 = pcd1 / 2;
                const r2 = pcd2 / 2;

                // Scale dynamically based on the maximum possible center distance (1500mm)
                // so that the sprockets never go off-screen even at the absolute maximum slider value!
                const scaleWidth = (width - 190) / (state.c + r1 + r2);
                const scaleHeight = (height - 120) / Math.max(pcd1, pcd2);
                const visualScale = Math.min(scaleWidth, scaleHeight);
                
                const R1_vis = r1 * visualScale;
                const R2_vis = r2 * visualScale;
                const C_vis = state.c * visualScale;
                
                // Position centers to perfectly center the entire bounding box
                const x1 = (width - C_vis + R1_vis - R2_vis) / 2;
                const y1 = height / 2;
                const x2 = x1 + C_vis;
                const y2 = y1;

                // 3. Draw Sprocket 1 &#038; 2
                drawSprocketDetails(ctx, x1, y1, z1, pcd1 * visualScale, state.angle1, '#0284c7');
                drawSprocketDetails(ctx, x2, y2, z2, pcd2 * visualScale, state.angle2, '#db2777');

                // 4. Draw Chain loop wrapping them (Tangents outer wrapping)
                // Tangent angle theta
                // Since y1 == y2, it is a horizontal sprocket drive
                // Tangents angle is alpha = asin((r2 - r1)/c)
                const deltaR = R2_vis - R1_vis;
                const alpha = Math.asin(deltaR / C_vis);

                // Tangent points on sprocket 1
                const tp1_x_top = x1 - R1_vis * Math.sin(alpha);
                const tp1_y_top = y1 - R1_vis * Math.cos(alpha);
                const tp1_x_bottom = x1 - R1_vis * Math.sin(alpha);
                const tp1_y_bottom = y1 + R1_vis * Math.cos(alpha);

                // Tangent points on sprocket 2
                const tp2_x_top = x2 - R2_vis * Math.sin(alpha);
                const tp2_y_top = y2 - R2_vis * Math.cos(alpha);
                const tp2_x_bottom = x2 - R2_vis * Math.sin(alpha);
                const tp2_y_bottom = y2 + R2_vis * Math.cos(alpha);

                // 5. Draw the loop paths
                ctx.save();
                ctx.strokeStyle = '#ffffff';
                ctx.lineWidth = 7;
                ctx.lineCap = 'round';
                ctx.lineJoin = 'round';
                
                // Inner belt outline backdrop
                ctx.beginPath();
                ctx.moveTo(tp1_x_top, tp1_y_top);
                ctx.lineTo(tp2_x_top, tp2_y_top);
                ctx.arc(x2, y2, R2_vis, -Math.PI / 2 - alpha, Math.PI / 2 + alpha, false);
                ctx.lineTo(tp1_x_bottom, tp1_y_bottom);
                ctx.arc(x1, y1, R1_vis, Math.PI / 2 + alpha, -Math.PI / 2 - alpha, false);
                ctx.closePath();
                
                ctx.stroke();

                // Chain base dark grey path
                ctx.strokeStyle = '#475569';
                ctx.lineWidth = 5;
                ctx.stroke();

                // 6. Draw moving roller chain links dashes
                // Chain rollers are individual dots along the path
                ctx.strokeStyle = '#ffffff';
                ctx.lineWidth = 3;
                
                const linkStep = pitch * visualScale;
                ctx.setLineDash([4, linkStep - 4]);
                ctx.lineDashOffset = -state.chainOffset * visualScale;
                ctx.stroke();
                ctx.setLineDash([]);
                ctx.restore();

                // 7. Highlight Tension tight side (top tangent path represents load)
                if (state.rpm1 > 0) {
                    ctx.save();
                    const powerLoad = state.power;
                    
                    ctx.strokeStyle = '#db2777';
                    ctx.lineWidth = Math.min(powerLoad * 0.05 + 1.5, 5);
                    ctx.shadowBlur = 8;
                    ctx.shadowColor = '#db2777';
                    
                    ctx.beginPath();
                    ctx.moveTo(tp1_x_top, tp1_y_top);
                    ctx.lineTo(tp2_x_top, tp2_y_top);
                    ctx.stroke();
                    ctx.restore();

                    // Tension arrow
                    ctx.save();
                    ctx.translate((tp1_x_top + tp2_x_top) / 2, (tp1_y_top + tp2_y_top) / 2 - 12);
                    ctx.fillStyle = '#db2777';
                    ctx.font = 'bold 10px Inter';
                    ctx.textAlign = 'center';
                    ctx.fillText('인장 긴장측 (TIGHT SIDE)', 0, 0);
                    ctx.restore();
                }

                // Draw Diameter dimensions on sprockets with professional CAD leader lines
                drawDiameterLeader(ctx, x1, y1, R1_vis, pcd1.toFixed(1) + ' mm', 'PCD₁ =', -1);
                drawDiameterLeader(ctx, x2, y2, R2_vis, pcd2.toFixed(1) + ' mm', 'PCD₂ =', 1);

                // Horizontal Center Distance Dimension Line (C)
                ctx.save();
                ctx.strokeStyle = 'rgba(71, 85, 105, 0.3)';
                ctx.lineWidth = 1;
                
                const dimY = y1 + R2_vis + 25; // 25px below larger sprocket
                
                ctx.beginPath();
                ctx.moveTo(x1, y1 + 10);
                ctx.lineTo(x1, dimY + 5);
                ctx.moveTo(x2, y2 + 10);
                ctx.lineTo(x2, dimY + 5);
                ctx.stroke();
                
                ctx.beginPath();
                ctx.moveTo(x1, dimY);
                ctx.lineTo(x2, dimY);
                ctx.stroke();
                
                function drawHorizontalArrowhead(px, py, direction) {
                    ctx.save();
                    ctx.translate(px, py);
                    ctx.fillStyle = 'rgba(71, 85, 105, 0.5)';
                    ctx.beginPath();
                    ctx.moveTo(0, 0);
                    ctx.lineTo(direction * 6, -3);
                    ctx.lineTo(direction * 6, 3);
                    ctx.closePath();
                    ctx.fill();
                    ctx.restore();
                }
                drawHorizontalArrowhead(x1, dimY, 1);
                drawHorizontalArrowhead(x2, dimY, -1);
                
                const cText = `C = ${state.c} mm`;
                ctx.font = 'bold 9px Inter, sans-serif';
                const cTextWidth = ctx.measureText(cText).width;
                const cTextX = (x1 + x2) / 2;
                // Draw white eraser background so text doesn't overlap dimension line
                ctx.fillStyle = '#f1f5f9';
                ctx.fillRect(cTextX - cTextWidth / 2 - 4, dimY - 6, cTextWidth + 8, 12);
                ctx.fillStyle = '#475569';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText(cText, cTextX, dimY);
                ctx.restore();

                requestAnimationFrame(animate);
            }
            requestAnimationFrame(animate);

            // 5. Events & Inputs Binding
            selectChainType.addEventListener('change', function() {
                state.pitch = parseFloat(this.value);
                calculateChainSystem();
            });

            inputZ1.addEventListener('change', syncZ1FromInput);
            sliderZ1.addEventListener('input', syncZ1FromSlider);

            inputZ2.addEventListener('change', syncZ2FromInput);
            sliderZ2.addEventListener('input', syncZ2FromSlider);

            inputC.addEventListener('change', syncCFromInput);
            sliderC.addEventListener('input', syncCFromSlider);

            inputRpm.addEventListener('change', syncRpmFromInput);
            sliderRpm.addEventListener('input', syncRpmFromSlider);

            inputPower.addEventListener('change', syncPowerFromInput);

            btnPlayPause.addEventListener('click', function() {
                state.isPlaying = !state.isPlaying;
                this.innerHTML = state.isPlaying ? '<i class="fa-solid fa-pause"></i> <span>일시정지</span>' : '<i class="fa-solid fa-play"></i> <span>재생</span>';
            });

            btnReverse.addEventListener('click', function() {
                state.direction *= -1;
            });

            btnResetPos.addEventListener('click', function() {
                state.angle1 = 0;
                state.angle2 = 0;
                state.chainOffset = 0;
            });

            // Set initial state
            calculateChainSystem();

            // 6. Right-click and Devtools block protection nested if style
            (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 });
                    
                    document.addEventListener('keydown', function(e) {
                        if (e.key === 'F12') {
                            e.preventDefault();
                            alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                            return false;
                        }
                        if (e.ctrlKey) {
                            if (e.key === 'u' || e.key === 'c' || e.key === 's') {
                                e.preventDefault();
                                alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                                return false;
                            }
                        }
                        if (e.ctrlKey) {
                            if (e.shiftKey) {
                                if (e.key === 'i' || e.key === 'I' || e.key === 'j' || e.key === 'J' || e.key === 'c' || e.key === 'C') {
                                    e.preventDefault();
                                    alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                                    return false;
                                }
                            }
                        }
                    }, { capture: true });
                }
                
                if (document.readyState === 'complete' || document.readyState === 'interactive') {
                    blockEvents();
                } else {
                    document.addEventListener('DOMContentLoaded', blockEvents);
                    window.addEventListener('load', 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>체인 호칭/규격(피치) 선택: 표준 ANSI 규격체인(호칭 35번 ~ 100번)을 선택하여 표준 피치 길이를 적용하거나, 수동으로 피치를 조절합니다. (Min 9.525mm ~ Max 31.75mm)</strong></li>
<li style="margin-bottom: 6px;">스프로킷 잇수(Z) 조절: 구동측 스프로킷 잇수(Z₁)와 피동측 스프로킷 잇수(Z₂)를 슬라이더나 수치 창을 통해 설정합니다. (Min 9 ~ Max 80)</li>
<li style="margin-bottom: 6px;">입력 속도(RPM) 및 동력(kW) 조절: 샤프트의 회전수와 전달할 동력을 슬라이더를 통해 변경해 보며 작동 속도와 인장 응력을 변경합니다.</li>
<li style="margin-bottom: 6px;">실시간 시각 효과 확인: 회전 속도에 맞춰 기하학적으로 연동된 두 스프로킷과 움직이는 체인 링크 루프가 렌더링되며, 텐션 작용 유무 및 하중에 따라 타이트 사이드(Tension side)의 장력 화살표 크기가 역동적으로 묘사됩니다.</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. 체인 전동(Chain Drive)의 원리 및 벨트 전동과의 기계적 비교</h3>
<p>동력 전달 요소 설계에서 <strong>체인 전동(Chain Drive)</strong>은 벨트 및 기어 전동의 장점을 융합한 핵심적인 물림 전동 장치입니다. 축간거리가 비교적 멀리 떨어진 상태에서도 미끄럼(Slip)이 없는 확실한 일정 속비 전동을 실현합니다. 벨트 전동과 비교할 때, 마찰력을 이용하지 않는 기하학적 물림 전동이므로 축에 미치는 초기 장력이 매우 작아 샤프트 및 베어링에 가해지는 레이디얼 부하(Radial Load)를 대폭 경감시킬 수 있습니다.</p><p>체인 전동은 기어에 비해 축간거리 조절이 매우 자유롭고, 다축 전동이 용이하며, 열악한 고온 및 먼지 환경에서도 원활하게 작동한다는 극강의 장점을 지닙니다. 다만, 스프로킷 이빨과 체인 링크가 맞물릴 때 발생하는 기하학적 다각형 효과(Polygonal Effect)로 인해 속도 변동 및 미세한 소음/진동이 발생할 수 있어 정밀 초고속 동력전달 시 스프로킷의 최소 잇수 관리가 대단히 중요합니다.</p>
<h3>2. 피치원 지름(PCD) 공식 유도 및 장력 계산 수학적 기초</h3>
<p>체인 링크는 현(Chord)의 형태로 스프로킷에 감기기 때문에, 피치원 지름(Pitch Circle Diameter, PCD)은 일반 원주의 관계인 <code>&pi; &times; D</code>로 계산할 수 없으며 삼각형 기하학 관계를 이용해 유도됩니다. 체인 피치를 <code>P</code>, 스프로킷 잇수를 <code>Z</code>라고 하면, 한 잇수당 사이각은 <code>360&deg; / Z</code>가 되며 그 절반 각도는 <code>180&deg; / Z</code>가 됩니다. 이에 따라 스프로킷 중심에서 링크 핀까지의 반경 <code>R</code>에 관한 공식은 다음과 같이 유도됩니다:</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">sin(180&deg; / Z) = (P / 2) / R &nbsp;=&gt;&nbsp; R = P / [2 &times; sin(180&deg; / Z)]</p><p>따라서, 스프로킷의 <strong>피치원 지름(D_p, PCD)</strong> 정밀 공식은 다음과 같이 최종 확립됩니다:</p><p style="text-align: center; font-weight: bold; background: #e0f2fe; padding: 16px; border-radius: 8px; font-size: 1.1em; color: #0369a1;">D_p = P / sin(180&deg; / Z) &nbsp;[mm]</p><p>도출된 PCD를 기반으로 구동측 체인의 평균 주속도 <code>v [m/s]</code>를 계산하고, 모터의 전달 동력 <code>P_w [kW]</code>로부터 발생하는 체인 텐션 사이드의 <strong>작용 장력(Tension force, F)</strong>을 유도합니다:</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">v = (D_p &times; &pi; &times; N_1) / 60,000 &nbsp;[m/s] &nbsp; (N_1 : 구동 RPM)</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">F = (P_w &times; 1000) / v &nbsp;[N] &nbsp; (1kgf = 9.80665N 단위 환산 지원)</p>
<h3>3. 롤러 체인 선정 시 고려사항 및 윤활 방법 (KS B 1407 규격 표준)</h3>
<p>롤러 체인 설계 수명을 보장하기 위해서는 동적 마찰 마멸 및 다각형 효과에 의한 충격을 관리해야 합니다. 특히 구동 스프로킷 잇수가 너무 적으면(15T 미만), 체인의 속도 변동률이 급상승하고 스레딩 굴곡 피로 파손이 급증하므로 권장 최소 잇수 <code>Z_min = 17</code>(충격 부하 시 21T 이상)을 준수해야 합니다.</p><ul><li><strong>속도 변동률 계산:</strong> 스프로킷의 속도 변동률 <code>&epsilon;</code>은 <code>&epsilon; = (v_max - v_min)/v_max = 1 - cos(180&deg;/Z)</code>로 표현되며, 잇수가 증가할수록 급격히 감소합니다.</li><li><strong>윤활 방식의 선택:</strong> 체인 속도에 따라 윤활 방식을 규정합니다. <code>v &lt; 1.5m/s</code>는 수동 혹은 적하 윤활(유적식), <code>1.5m/s &lt; v &lt; 8.0m/s</code>는 오일 배스(Bath) 내 유침 윤활, <code>v &gt; 8.0m/s</code> 영역에서는 고압 노즐을 이용한 강제 송유 순환 윤활 방식을 채택하여 링크 핀 내부에 윤활막을 영구 공급해야 합니다 (KS B 1407 기준).</li></ul>

        </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/chain-sprocket-speed-calculator-simulator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>토크 &#038; 동력 계산기 &#038; 시뮬레이터</title>
		<link>https://myengnote.com/torque-power-converter-simulator/</link>
					<comments>https://myengnote.com/torque-power-converter-simulator/#respond</comments>
		
		<dc:creator><![CDATA[동동]]></dc:creator>
		<pubDate>Thu, 28 May 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[공학계산기]]></category>
		<category><![CDATA[2D시뮬레이터]]></category>
		<category><![CDATA[마력변환]]></category>
		<category><![CDATA[모터출력계산]]></category>
		<category><![CDATA[토크계산기]]></category>
		<category><![CDATA[토크동력변환]]></category>
		<guid isPermaLink="false">https://myengnote.com/torque-power-converter-simulator/</guid>

					<description><![CDATA[동력(kW, HP), 토크(N·m, kgf·m), 회전 속도(RPM) 간의 상호 변환을 계산하고, 부하에 따른 모터의 회전 및 발열 상호작용을 시각화하는 초정밀 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; 동력 계산기 &#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 torquepower-calculator-wrapper */

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

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

        .torquepower-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: torquepower-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(16, 185, 129, 0.1);
            border: 1px solid rgba(16, 185, 129, 0.2);
            padding: 6px 14px;
            border-radius: 20px;
        }

        .pulse-dot {
            width: 8px;
            height: 8px;
            background-color: var(--color-success);
            border-radius: 50%;
            box-shadow: 0 0 10px var(--color-success);
            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-success);
            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: 20px;
            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: color: var(--color-purple); }

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

        /* Mode Select Segmented Button */
        .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(3, 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 4px;
            border-radius: 8px;
            font-size: 11px;
            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: 12px;
            transition: all 0.3s ease;
        }

        .input-group.disabled {
            opacity: 0.45;
            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: 15px !important;
            font-weight: 700 !important;
            text-align: right !important;
            padding-right: 60px !important;
            text-shadow: none !important;
            box-shadow: none !important;
            box-shadow: none !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: 8px 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;
        }

        /* Thumbs Styling */
        #slider-power::-webkit-slider-thumb {
            background: var(--color-cyan);
            box-shadow: 0 0 10px var(--color-cyan);
        }
        #slider-power::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-cyan), 0 0 5px #fff;
        }
        #slider-power::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border: none;
            border-radius: 50%;
            background: var(--color-cyan);
            box-shadow: 0 0 10px var(--color-cyan);
            cursor: pointer;
        }

        #slider-torque::-webkit-slider-thumb {
            background: var(--color-magenta);
            box-shadow: 0 0 10px var(--color-magenta);
        }
        #slider-torque::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-magenta), 0 0 5px #fff;
        }
        #slider-torque::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border: none;
            border-radius: 50%;
            background: var(--color-magenta);
            box-shadow: 0 0 10px var(--color-magenta);
            cursor: pointer;
        }

        #slider-rpm::-webkit-slider-thumb {
            background: var(--color-purple);
            box-shadow: 0 0 10px var(--color-purple);
        }
        #slider-rpm::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-purple), 0 0 5px #fff;
        }
        #slider-rpm::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border: none;
            border-radius: 50%;
            background: var(--color-purple);
            box-shadow: 0 0 10px var(--color-purple);
            cursor: pointer;
        }

        .control-actions {
            display: grid;
            grid-template-columns: 1fr;
            gap: 10px;
            margin-top: 10px;
        }

        .action-btn {
            background: rgba(0, 0, 0, 0.02);
            border: 1px solid var(--color-border);
            color: var(--color-text-main);
            padding: 10px;
            border-radius: 10px;
            font-size: 12px;
            font-weight: 600;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            transition: all 0.2s ease;
        }

        .action-btn:hover {
            background: rgba(0, 0, 0, 0.04);
            border-color: var(--color-border-hover);
        }

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

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

        .presets-grid {
            display: grid;
            grid-template-columns: 1fr;
            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.05);
            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);
            transition: all 0.2s ease;
        }

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

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

        .simulation-panel {
            grid-column: 2;
            align-self: stretch;
            justify-content: space-between;
            min-width: 0;
        }

        .simulation-panel .panel-header {
            justify-content: space-between;
        }

        .canvas-scale-indicator {
            font-size: 11px;
            color: var(--color-text-muted);
            background: rgba(0, 0, 0, 0.02);
            padding: 4px 10px;
            border-radius: 6px;
            border: 1px solid var(--color-border);
        }

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

        #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: transparent;
            color: var(--color-text-main);
            padding: 4px 0;
            display: flex;
            align-items: center;
            gap: 6px;
            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: 26px;
            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);
            transition: all 0.2s ease;
        }

        .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;
            border: 1px solid var(--color-border);
            text-align: center;
            font-weight: 700;
        }

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

<div class="torquepower-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-bolt-lightning"></i></div>
                <div>
                    <h1>TORQUE &#038; POWER</h1>
                    <div class="subtitle">토크 &#038; 동력 변환 계산기 및 2D 모터 시뮬레이터</div>
                </div>
            </div>
            <div class="header-badge">
                <div class="pulse-dot"></div>
                <div class="badge-text">MOTOR PHYSICS 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>

                <!-- Mode Selection -->
                <div class="mode-select-container">
                    <span class="mode-label">계산 모드 선택</span>
                    <div class="segmented-control">
                        <button class="segmented-btn active" id="btn-mode-p" title="동력 계산 모드">동력(P) 계산</button>
                        <button class="segmented-btn" id="btn-mode-t" title="토크 계산 모드">토크(T) 계산</button>
                        <button class="segmented-btn" id="btn-mode-n" title="회전수 계산 모드">회전수(N) 계산</button>
                    </div>
                </div>

                <!-- Power input (kW) -->
                <div class="input-group" id="group-power">
                    <div class="input-label-row">
                        <label for="input-power"><i class="fa-solid fa-plug text-cyan"></i> 전동기 동력 (P)</label>
                        <span class="helper-text" id="power-range-label">입력 (0.1 ~ 250 kW)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-power" class="custom-number-input" min="0.1" max="250" value="11" step="any">
                        <div class="unit-badge">kW</div>
                    </div>
                    <input type="range" id="slider-power" class="custom-slider" min="1" max="250" value="11">
                </div>

                <!-- Torque input (N·m) -->
                <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 ~ 1000 N·m)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-torque" class="custom-number-input" min="1" max="1000" value="70" step="any">
                        <div class="unit-badge">N·m</div>
                    </div>
                    <input type="range" id="slider-torque" class="custom-slider" min="1" max="1000" value="70">
                </div>

                <!-- Speed input (RPM) -->
                <div class="input-group" id="group-rpm">
                    <div class="input-label-row">
                        <label for="input-rpm"><i class="fa-solid fa-gauge text-purple"></i> 회전 속도 (N)</label>
                        <span class="helper-text" id="rpm-range-label">입력 (10 ~ 5000 RPM)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-rpm" class="custom-number-input" min="10" max="5000" value="1500" step="any">
                        <div class="unit-badge">RPM</div>
                    </div>
                    <input type="range" id="slider-rpm" class="custom-slider" min="10" max="5000" value="1500">
                </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" data-preset="standard">
                            <div class="preset-icon"><i class="fa-solid fa-industry"></i></div>
                            <div class="preset-name">표준 산업용 삼상모터 (11kW, 1500RPM)</div>
                        </button>
                        <button class="preset-btn" data-preset="crane">
                            <div class="preset-icon"><i class="fa-solid fa-anchor"></i></div>
                            <div class="preset-name">고하중 건설 호이스트 크레인 (저속 고토크)</div>
                        </button>
                        <button class="preset-btn" data-preset="spindle">
                            <div class="preset-icon"><i class="fa-solid fa-compass"></i></div>
                            <div class="preset-name">고속가공용 정밀 CNC 스핀들 (고속 저토크)</div>
                        </button>
                    </div>
                </div>
            </section>

            <!-- Center Panel: Physics Simulator & Integrated Results -->
            <section class="panel simulation-panel">
                <div class="panel-header">
                    <i class="fa-solid fa-dharmachakra text-magenta"></i>
                    <h2>실시간 2D 모터 &#038; 윈치 윈드 시뮬레이션</h2>
                    <span class="canvas-scale-indicator" id="btn-reset-pos"><i class="fa-solid fa-arrows-rotate"></i> 초기화</span>
                </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" style="color: var(--color-success);">정상 운전 중</span>
                        </div>
                    </div>
                </div>

                <div class="simulation-metrics-strip">
                    <div class="mini-metric">
                        <span class="label">각속도 (Angular Velocity)</span>
                        <span class="value" id="val-omega">157.1 rad/s</span>
                    </div>
                    <div class="mini-divider"></div>
                    <div class="mini-metric">
                        <span class="label">초당 마력 회전량 (Watts)</span>
                        <span class="value" id="val-watts">11,000 W</span>
                    </div>
                    <div class="mini-divider"></div>
                    <div class="mini-metric">
                        <span class="label">로프 리프팅 속도 (Speed)</span>
                        <span class="value" id="val-rope-speed">7.85 m/s</span>
                    </div>
                </div>

                <!-- Integrated Results Analysis Section -->
                <div class="simulation-results-section" style="display: grid; grid-template-columns: 1fr 1.2fr; gap: 20px; border-top: 1px solid var(--color-border); padding-top: 20px; margin-top: 10px;">
                    <!-- Left Column: Primary Readout & Formula -->
                    <div style="display: flex; flex-direction: column; gap: 16px;">
                        <div class="ratio-readout-box" style="padding: 16px; min-height: 100px;">
                            <div class="ratio-title">계산된 전동기 출력</div>
                            <div class="ratio-value" id="txt-primary-output" style="font-size: 24px;">11.00 kW</div>
                            <div class="ratio-type" id="txt-secondary-output" style="font-size: 11px;">14.75 마력 (HP)</div>
                        </div>
                        <div class="formula-card" style="padding: 12px; font-size: 11px; background: rgba(0, 0, 0, 0.01);">
                            <h4 style="font-size: 12px; margin-bottom: 6px;"><i class="fa-solid fa-info-circle text-cyan"></i> 토크-동력 핵심 관계식</h4>
                            <div class="formula-equation" style="font-size: 11px; padding: 6px; margin-bottom: 6px;">
                                P [kW] = T [N·m] × N [RPM] / 9549.3
                            </div>
                            <div class="formula-equation" style="font-size: 11px; padding: 6px;">
                                T [kgf·m] = T [N·m] / 9.80665
                            </div>
                        </div>
                    </div>

                    <!-- Right Column: Quantitative Results Cards -->
                    <div class="results-grid" style="display: flex; flex-direction: column; gap: 10px; justify-content: center;">
                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-bolt"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">전동기 정격 동력 (P)</span>
                                <span class="card-value" id="res-power" style="font-size: 15px;">11.00 kW</span>
                            </div>
                        </div>

                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-horse"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">미터법 마력 출력 (HP)</span>
                                <span class="card-value" id="res-hp" style="font-size: 15px;">14.96 HP</span>
                            </div>
                        </div>

                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-gears"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">발생 물리 토크 (T)</span>
                                <span class="card-value" id="res-torque-nm" style="font-size: 15px;">70.03 N·m</span>
                            </div>
                        </div>

                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-wrench"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">중력 단위 토크 (kgf·m)</span>
                                <span class="card-value" id="res-torque-kgm" style="font-size: 15px;">7.14 kgf·m</span>
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        </main>
    </div>

    <!-- CORE INTERACTIVE ENGINE -->
    <script>
        (function() {
            // Safe guard against duplicate initiation
            if (window.__torquepower_initialized) return;
            window.__torquepower_initialized = true;

            // 1. Core DOM queries
            const btnModeP = document.getElementById('btn-mode-p');
            const btnModeT = document.getElementById('btn-mode-t');
            const btnModeN = document.getElementById('btn-mode-n');

            const groupPower = document.getElementById('group-power');
            const groupTorque = document.getElementById('group-torque');
            const groupRpm = document.getElementById('group-rpm');

            const inputPower = document.getElementById('input-power');
            const sliderPower = document.getElementById('slider-power');
            const inputTorque = document.getElementById('input-torque');
            const sliderTorque = document.getElementById('slider-torque');
            const inputRpm = document.getElementById('input-rpm');
            const sliderRpm = document.getElementById('slider-rpm');

            const txtPrimaryOutput = document.getElementById('txt-primary-output');
            const txtSecondaryOutput = document.getElementById('txt-secondary-output');

            const resPower = document.getElementById('res-power');
            const resHp = document.getElementById('res-hp');
            const resTorqueNm = document.getElementById('res-torque-nm');
            const resTorqueKgm = document.getElementById('res-torque-kgm');

            const valOmega = document.getElementById('val-omega');
            const valWatts = document.getElementById('val-watts');
            const valRopeSpeed = document.getElementById('val-rope-speed');

            const overlayRunStatus = document.getElementById('overlay-run-status');
            const btnResetPos = document.getElementById('btn-reset-pos');

            // 2. Constants & Physics State
            const state = {
                mode: 'P', // 'P' (calc Power), 'T' (calc Torque), 'N' (calc RPM)
                power: 11.0,  // kW
                torque: 70.0, // N·m
                rpm: 1500.0,  // RPM
                angle: 0.0,
                ropeY: 180.0,
                ropeDir: 1.0,
                sparks: []
            };

            // Range definition (Min / Max)
            const ranges = {
                power: { min: 0.1, max: 250.0 },
                torque: { min: 1.0, max: 1000.0 },
                rpm: { min: 10.0, max: 5000.0 }
            };

            // 3. Mathematical Solvers
            function calculateThirdVariable() {
                if (state.mode === 'P') {
                    // P = T * N / 9549.3
                    state.power = (state.torque * state.rpm) / 9549.3;
                    // Apply bounds check
                    if (state.power < ranges.power.min) state.power = ranges.power.min;
                    if (state.power > ranges.power.max) state.power = ranges.power.max;
                } else if (state.mode === 'T') {
                    // T = 9549.3 * P / N
                    if (state.rpm > 0) {
                        state.torque = (9549.3 * state.power) / state.rpm;
                    } else {
                        state.torque = ranges.torque.min;
                    }
                    if (state.torque < ranges.torque.min) state.torque = ranges.torque.min;
                    if (state.torque > ranges.torque.max) state.torque = ranges.torque.max;
                } else if (state.mode === 'N') {
                    // N = 9549.3 * P / T
                    if (state.torque > 0) {
                        state.rpm = (9549.3 * state.power) / state.torque;
                    } else {
                        state.rpm = ranges.rpm.min;
                    }
                    if (state.rpm < ranges.rpm.min) state.rpm = ranges.rpm.min;
                    if (state.rpm > ranges.rpm.max) state.rpm = ranges.rpm.max;
                }
                updateUIValues();
            }

            // Sync text inputs and range sliders without aggressive clamping
            function syncPowerFromInput() {
                let val = parseFloat(inputPower.value);
                if (isNaN(val)) val = ranges.power.min;
                if (val < ranges.power.min) val = ranges.power.min;
                if (val > ranges.power.max) val = ranges.power.max;
                state.power = val;
                inputPower.value = val.toFixed(2);
                sliderPower.value = Math.round(val);
                calculateThirdVariable();
            }

            function syncPowerFromSlider() {
                state.power = parseFloat(sliderPower.value);
                inputPower.value = state.power.toFixed(1);
                calculateThirdVariable();
            }

            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);
                calculateThirdVariable();
            }

            function syncTorqueFromSlider() {
                state.torque = parseFloat(sliderTorque.value);
                inputTorque.value = state.torque.toFixed(0);
                calculateThirdVariable();
            }

            function syncRpmFromInput() {
                let val = parseFloat(inputRpm.value);
                if (isNaN(val)) val = ranges.rpm.min;
                if (val < ranges.rpm.min) val = ranges.rpm.min;
                if (val > ranges.rpm.max) val = ranges.rpm.max;
                state.rpm = val;
                inputRpm.value = val.toFixed(0);
                sliderRpm.value = Math.round(val);
                calculateThirdVariable();
            }

            function syncRpmFromSlider() {
                state.rpm = parseFloat(sliderRpm.value);
                inputRpm.value = state.rpm.toFixed(0);
                calculateThirdVariable();
            }

            // 4. Update UI Outputs
            function updateUIValues() {
                // Update inputs
                if (state.mode !== 'P') {
                    inputPower.value = state.power.toFixed(2);
                    sliderPower.value = Math.round(state.power);
                }
                if (state.mode !== 'T') {
                    inputTorque.value = state.torque.toFixed(1);
                    sliderTorque.value = Math.round(state.torque);
                }
                if (state.mode !== 'N') {
                    inputRpm.value = state.rpm.toFixed(0);
                    sliderRpm.value = Math.round(state.rpm);
                }

                // Primary Output Readout Box
                if (state.mode === 'P') {
                    txtPrimaryOutput.innerText = state.power.toFixed(2) + ' kW';
                    const hp = state.power * 1.35962;
                    txtSecondaryOutput.innerText = hp.toFixed(2) + ' HP (마력)';
                } else if (state.mode === 'T') {
                    txtPrimaryOutput.innerText = state.torque.toFixed(1) + ' N·m';
                    const kgm = state.torque / 9.80665;
                    txtSecondaryOutput.innerText = kgm.toFixed(2) + ' kgf·m (킬로그램토크)';
                } else if (state.mode === 'N') {
                    txtPrimaryOutput.innerText = state.rpm.toFixed(0) + ' RPM';
                    const hz = state.rpm / 60;
                    txtSecondaryOutput.innerText = hz.toFixed(1) + ' Hz (초당 회전수)';
                }

                // Results Grid Card Values
                resPower.innerText = state.power.toFixed(2) + ' kW';
                resHp.innerText = (state.power * 1.35962).toFixed(2) + ' HP';
                resTorqueNm.innerText = state.torque.toFixed(1) + ' N·m';
                resTorqueKgm.innerText = (state.torque / 9.80665).toFixed(2) + ' kgf·m';

                // Mini metrics strip at the bottom
                const omega = (2 * Math.PI * state.rpm) / 60;
                valOmega.innerText = omega.toFixed(1) + ' rad/s';
                valWatts.innerText = (state.power * 1000).toLocaleString('ko-KR', { maximumFractionDigits: 0 }) + ' W';
                
                // Winch drum radius is 50mm (0.05m). Speed = r * omega
                const ropeSpeed = 0.05 * omega;
                valRopeSpeed.innerText = ropeSpeed.toFixed(2) + ' m/s';

                // Thermal Warning status
                if (state.power > 150) {
                    overlayRunStatus.innerText = '고부하 운전 (과열 주의)';
                    overlayRunStatus.style.color = '#f59e0b';
                } else {
                    overlayRunStatus.innerText = '정상 운전 중';
                    overlayRunStatus.style.color = '#10b981';
                }
            }

            // 5. Calculate mode toggle
            function setMode(newMode) {
                state.mode = newMode;
                
                // Active button styling
                btnModeP.classList.remove('active');
                btnModeT.classList.remove('active');
                btnModeN.classList.remove('active');

                // Enable/disable slider blocks
                groupPower.classList.remove('disabled');
                groupTorque.classList.remove('disabled');
                groupRpm.classList.remove('disabled');

                if (newMode === 'P') {
                    btnModeP.classList.add('active');
                    groupPower.classList.add('disabled');
                } else if (newMode === 'T') {
                    btnModeT.classList.add('active');
                    groupTorque.classList.add('disabled');
                } else if (newMode === 'N') {
                    btnModeN.classList.add('active');
                    groupRpm.classList.add('disabled');
                }
                calculateThirdVariable();
            }

            // Presets selector
            const presetBtns = document.querySelectorAll('.preset-btn');
            presetBtns.forEach(btn => {
                btn.addEventListener('click', function() {
                    presetBtns.forEach(b => b.classList.remove('active'));
                    this.classList.add('active');

                    const preset = this.getAttribute('data-preset');
                    if (preset === 'standard') {
                        state.torque = 70.0;
                        state.rpm = 1500.0;
                        setMode('P');
                    } else if (preset === 'crane') {
                        state.torque = 650.0;
                        state.rpm = 160.0;
                        setMode('P');
                    } else if (preset === 'spindle') {
                        state.torque = 8.5;
                        state.rpm = 4500.0;
                        setMode('P');
                    }
                });
            });

            // 6. Interactive Canvas Physics Simulation
            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();
            // Polling backups to guarantee canvas is sized correctly after WordPress page loads
            setTimeout(initCanvas, 100);
            setTimeout(initCanvas, 300);
            setTimeout(initCanvas, 800);

            // Winch Rope limits will be calculated dynamically inside animation loop to prevent counterweight overlapping

            // Spark Particle Generator
            function addSparks(x, y, count) {
                for (let i = 0; i < count; i++) {
                    state.sparks.push({
                        x: x,
                        y: y,
                        vx: (Math.random() - 0.5) * 8 + (state.rpm > 0 ? 3 : -3),
                        vy: (Math.random() - 0.7) * 6 - 2,
                        life: 1.0,
                        decay: Math.random() * 0.05 + 0.03,
                        color: Math.random() > 0.4 ? '#0284c7' : '#db2777'
                    });
                }
            }

            function updateSparks() {
                for (let i = state.sparks.length - 1; i >= 0; i--) {
                    const p = state.sparks[i];
                    p.x += p.vx;
                    p.y += p.vy;
                    p.vy += 0.15; // gravity
                    p.life -= p.decay;
                    if (p.life <= 0) {
                        state.sparks.splice(i, 1);
                    }
                }
            }

            function drawDiameterLeader(c, x, y, r, valStr, labelPrefix, side) {
                c.save();
                c.strokeStyle = 'rgba(71, 85, 105, 0.4)';
                c.lineWidth = 1.2;
                
                // Angle of leader line (45 degrees up-left or up-right)
                const angle = side === -1 ? -Math.PI * 0.75 : -Math.PI * 0.25;
                const px = x + Math.cos(angle) * r;
                const py = y + Math.sin(angle) * r;
                
                // Draw a beautiful small dot on the circle boundary
                c.beginPath();
                c.arc(px, py, 2.5, 0, Math.PI * 2);
                c.fillStyle = side === -1 ? '#0284c7' : '#db2777';
                c.fill();
                
                // Draw leader lines: from circle boundary diagonal outwards, then horizontal
                const lx1 = px + Math.cos(angle) * 20;
                const ly1 = py + Math.sin(angle) * 20;
                const lx2 = lx1 + side * 45;
                const ly2 = ly1;
                
                c.beginPath();
                c.moveTo(px, py);
                c.lineTo(lx1, ly1);
                c.lineTo(lx2, ly2);
                c.stroke();
                
                // Draw text above the horizontal line
                const text = `${labelPrefix} ${valStr}`;
                c.font = 'bold 10px var(--font-body)';
                
                const tx = side === -1 ? lx1 - 4 : lx1 + 4;
                c.fillStyle = '#1e293b';
                c.textAlign = side === -1 ? 'right' : 'left';
                c.textBaseline = 'bottom';
                c.fillText(text, tx, ly2 - 1);
                c.restore();
            }

            // Main Animation loop
            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;

                // Dynamically center the motor-coupling-winch assembly to fill the canvas beautifully
                const totalSystemWidth = 410;
                const startX = (width - totalSystemWidth) / 2;
                
                const mx = startX + 70;
                const my = height / 2;
                const cpx = mx + 120;
                const wx = cpx + 140;

                // Physics state updates
                // Angular step = omega * dt
                const omega = (2 * Math.PI * state.rpm) / 60;
                state.angle += omega * dt;

                // Winch lifting ropes simulation
                // Dynamic Y boundaries to prevent counterweight overlapping with the winch drum!
                // The top of the counterweight box (ropeY + 12) should stop at the bottom of the drum cylinder (my + 32).
                const minRopeY = my + 20; // safe clearance limit
                const maxRopeY = my + 130;
                
                // Rope Y coordinate moves in proportion to speed
                const speedCoeff = 0.015;
                state.ropeY += state.ropeDir * omega * speedCoeff;
                if (state.ropeY > maxRopeY) {
                    state.ropeY = maxRopeY;
                    state.ropeDir = -1.0;
                }
                if (state.ropeY < minRopeY) {
                    state.ropeY = minRopeY;
                    state.ropeDir = 1.0;
                }
                // Clamp Y bounds dynamically on screen resize or initial load
                if (state.ropeY < minRopeY) state.ropeY = minRopeY;
                if (state.ropeY > maxRopeY) state.ropeY = maxRopeY;

                // Sparks fly on coupling joint if spinning
                if (state.rpm > 50) {
                    const sparkRate = Math.min(state.power * 0.1 + 1, 6);
                    if (Math.random() < sparkRate * dt * 25) {
                        // Position of coupling joint is dynamic (cpx, my)
                        addSparks(cpx, my, 2);
                    }
                }
                updateSparks();

                // Clear &#038; draw
                ctx.clearRect(0, 0, width, height);

                // 1. Draw 모눈종이 Blueprint Grid
                ctx.strokeStyle = 'rgba(2, 132, 199, 0.06)';
                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();
                }

                // 2. Draw Motor Housing (Left side centered dynamically)
                ctx.save();
                ctx.shadowBlur = 10;
                ctx.shadowColor = 'rgba(0, 0, 0, 0.05)';
                ctx.fillStyle = '#e2e8f0';
                ctx.strokeStyle = '#94a3b8';
                ctx.lineWidth = 4;
                ctx.beginPath();
                ctx.roundRect(mx - 70, my - 60, 140, 120, 16);
                ctx.fill();
                ctx.stroke();

                // Draw heat cooling fins on motor
                ctx.fillStyle = '#cbd5e1';
                for (let fx = mx - 50; fx <= mx + 30; fx += 20) {
                    ctx.fillRect(fx, my - 68, 10, 8);
                    ctx.fillRect(fx, my + 60, 10, 8);
                }

                // Draw Electric Flow inside Motor Housing
                if (state.power > 1.0) {
                    ctx.save();
                    ctx.strokeStyle = '#0284c7'; // Cobalt blue
                    ctx.lineWidth = 2.5;
                    ctx.shadowBlur = 8;
                    ctx.shadowColor = '#0284c7';
                    
                    const flowRate = (state.rpm / 60) * 8;
                    ctx.setLineDash([12, 18]);
                    ctx.lineDashOffset = -flowRate * (currentTime / 100);
                    
                    ctx.beginPath();
                    ctx.roundRect(mx - 62, my - 52, 124, 104, 12);
                    ctx.stroke();
                    ctx.restore();
                }
                ctx.restore();

                // 2b. Draw Motor Text Label
                ctx.save();
                ctx.fillStyle = '#64748b'; // Slate grey
                ctx.font = 'bold 10px var(--font-body)';
                ctx.textAlign = 'center';
                ctx.fillText('구동 모터 (MOTOR)', mx, my - 86);
                ctx.restore();

                // 3. Draw Rotating Coupling Shaft (Middle connection)
                ctx.save();
                ctx.fillStyle = '#475569';
                ctx.fillRect(mx + 70, my - 10, 130, 20); // steel shaft
                ctx.restore();

                // 3b. Draw Rotating Shaft Text Label
                ctx.save();
                ctx.fillStyle = '#64748b';
                ctx.font = 'bold 10px var(--font-body)';
                ctx.textAlign = 'center';
                ctx.fillText('회전 커플링 (COUPLING)', cpx, my - 86);
                ctx.restore();

                // 4. Draw Coupling Joint (Center at 240, 200)
                ctx.save();
                ctx.translate(cpx, my);
                ctx.rotate(state.angle);
                
                // Coupling Flange Disc (Gray plate)
                ctx.fillStyle = '#475569';
                ctx.strokeStyle = '#334155';
                ctx.lineWidth = 2;
                ctx.beginPath();
                ctx.arc(0, 0, 26, 0, Math.PI * 2);
                ctx.fill();
                ctx.stroke();

                // Center Sleeve Hub (Darker gray)
                ctx.fillStyle = '#1e293b';
                ctx.beginPath();
                ctx.arc(0, 0, 12, 0, Math.PI * 2);
                ctx.fill();

                // 4 Bolt Holes / Pins (rotating with the flange)
                ctx.fillStyle = '#94a3b8';
                for (let i = 0; i < 4; i++) {
                    const bAngle = (i * Math.PI) / 2;
                    ctx.beginPath();
                    ctx.arc(Math.cos(bAngle) * 18, Math.sin(bAngle) * 18, 3.5, 0, Math.PI * 2);
                    ctx.fill();
                }
                ctx.restore();

                // 5. Draw Winch Drum &#038; Frame (Centered dynamically)
                ctx.save();
                ctx.translate(wx, my);
                
                // Winch base bracket
                ctx.fillStyle = '#94a3b8';
                ctx.fillRect(-10, 48, 80, 16);
                ctx.fillRect(60, -60, 10, 120); // vertical pillar
                ctx.restore();

                // 5b. Draw Winch Text Label
                ctx.save();
                ctx.fillStyle = '#64748b';
                ctx.font = 'bold 10px var(--font-body)';
                ctx.textAlign = 'center';
                ctx.fillText('윈치 드럼 (WINCH DRUM)', wx, my - 86);
                ctx.restore();

                // Rotating Winch Drum &#038; Flanges
                ctx.save();
                ctx.translate(wx, my);
                ctx.rotate(state.angle);

                // Winch drum cylinder
                ctx.fillStyle = '#4b5563';
                ctx.beginPath();
                ctx.arc(0, 0, 32, 0, Math.PI * 2);
                ctx.fill();

                // Side flanges
                ctx.fillStyle = '#1f2937';
                ctx.fillRect(-8, -42, 16, 84);
                
                // Spoke lines on drum to visualize speed
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
                ctx.lineWidth = 3;
                for (let i = 0; i < 4; i++) {
                    const spAngle = (i * Math.PI) / 2;
                    ctx.beginPath();
                    ctx.moveTo(Math.cos(spAngle) * 8, Math.sin(spAngle) * 8);
                    ctx.lineTo(Math.cos(spAngle) * 28, Math.sin(spAngle) * 28);
                    ctx.stroke();
                }
                ctx.restore();

                // Draw Winch Drum Diameter Dimension Ø = 100 mm with professional CAD leader line
                // side=-1: leader goes upper-left to avoid overlap with LOAD text on right
                drawDiameterLeader(ctx, wx, my, 32, '100 mm', 'Ø =', -1);

                // 6. Draw Winch Rope &#038; Hanging Load Weight
                ctx.save();
                const ropeX = wx - 32;
                const ropeStartY = my; // rope exits from drum left side at center height
                
                // Rope line
                ctx.strokeStyle = state.torque > 350 ? '#db2777' : '#475569';
                ctx.lineWidth = state.torque > 350 ? 4 : 2.5;
                if (state.torque > 350) {
                    ctx.shadowBlur = 8;
                    ctx.shadowColor = '#db2777';
                }
                ctx.beginPath();
                ctx.moveTo(ropeX, ropeStartY);
                ctx.lineTo(ropeX, state.ropeY);
                ctx.stroke();
                ctx.shadowBlur = 0;

                // Hanging hook & mass weight block
                ctx.translate(ropeX, state.ropeY);
                
                // Load Mass Box
                const massWidth = 56;
                const massHeight = 44;
                ctx.fillStyle = '#334155';
                ctx.strokeStyle = '#1e293b';
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.roundRect(-massWidth / 2, 12, massWidth, massHeight, 6);
                ctx.fill();
                ctx.stroke();

                // Mass Text Label (Representing Torque)
                ctx.fillStyle = '#ffffff';
                ctx.font = 'bold 11px Inter';
                ctx.textAlign = 'center';
                const loadWeightKg = (state.torque / (9.80665 * 0.05)).toFixed(0); // T = F * r = m*g*r => m = T/(g*r)
                ctx.fillText(loadWeightKg + " kg", 0, 38);

                // Load Text Label
                ctx.fillStyle = '#64748b';
                ctx.font = 'bold 10px var(--font-body)';
                ctx.textAlign = 'left';
                ctx.fillText('부하 중량 (LOAD)', massWidth / 2 + 8, 38);

                // Rope hooks/eyelet
                ctx.strokeStyle = '#1e293b';
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.arc(0, 4, 6, 0, Math.PI * 2);
                ctx.stroke();
                ctx.restore();

                // 7. Draw Torque Vector & Speed Vector (Arrows on Drum)
                ctx.save();
                ctx.translate(wx, my);
                
                // Torque vector (glowing circle arrow, representing loading torque)
                if (state.torque > 5.0) {
                    ctx.save();
                    ctx.strokeStyle = '#db2777'; // Pink/magenta
                    ctx.lineWidth = Math.min(state.torque * 0.015 + 2, 8);
                    ctx.shadowBlur = 10;
                    ctx.shadowColor = '#db2777';
                    
                    ctx.beginPath();
                    ctx.arc(0, 0, 56, 0.1, Math.PI * 1.5);
                    ctx.stroke();

                    // Arrowhead
                    ctx.fillStyle = '#db2777';
                    ctx.beginPath();
                    ctx.moveTo(0, -56 - 6);
                    ctx.lineTo(12, -56);
                    ctx.lineTo(0, -56 + 6);
                    ctx.closePath();
                    ctx.fill();
                    ctx.restore();

                    // Vector text label
                    ctx.fillStyle = '#db2777';
                    ctx.font = 'bold 10px Inter';
                    ctx.textAlign = 'center';
                    ctx.fillText(`토크 T = ${state.torque.toFixed(1)} N·m`, 0, -68);
                }
                ctx.restore();

                // 8. Draw Spark particles
                ctx.save();
                for (let i = 0; i < state.sparks.length; i++) {
                    const p = state.sparks[i];
                    ctx.fillStyle = p.color;
                    ctx.globalAlpha = p.life;
                    ctx.shadowBlur = 6;
                    ctx.shadowColor = p.color;
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, 2.5 * p.life, 0, Math.PI * 2);
                    ctx.fill();
                }
                ctx.restore();

                requestAnimationFrame(animate);
            }
            requestAnimationFrame(animate);

            // 7. Events &#038; Initialization
            btnModeP.addEventListener('click', () => setMode('P'));
            btnModeT.addEventListener('click', () => setMode('T'));
            btnModeN.addEventListener('click', () => setMode('N'));

            // Slider & Text inputs binding
            inputPower.addEventListener('change', syncPowerFromInput);
            sliderPower.addEventListener('input', syncPowerFromSlider);

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

            inputRpm.addEventListener('change', syncRpmFromInput);
            sliderRpm.addEventListener('input', syncRpmFromSlider);

            // Manual position reset
            btnResetPos.addEventListener('click', () => {
                state.angle = 0;
                const dpr = getDPR();
                const my = (canvas.height / dpr) / 2;
                state.ropeY = my + 60; // Safe middle position between my+20 and my+130
                state.ropeDir = 1.0;
                state.sparks = [];
            });

            // Set initial state
            setMode('P');

            // 8. Copy and Right-Click Protection Document level
            (function() {
                function blockEvents() {
                    // Block right click on document and show custom alert
                    document.addEventListener('contextmenu', function(e) {
                        e.preventDefault();
                        alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                        return false;
                    }, { capture: true });
                    
                    // Block text selection on document
                    document.addEventListener('selectstart', function(e) {
                        e.preventDefault();
                        return false;
                    }, { capture: true });
                    
                    // Block developer tools and copy hotkeys
                    document.addEventListener('keydown', function(e) {
                        // nested if statements to avoid ampersands conversions in WordPress REST API
                        if (e.key === 'F12') {
                            e.preventDefault();
                            alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                            return false;
                        }
                        if (e.ctrlKey) {
                            if (e.key === 'u' || e.key === 'c' || e.key === 's') {
                                e.preventDefault();
                                alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                                return false;
                            }
                        }
                        if (e.ctrlKey) {
                            if (e.shiftKey) {
                                if (e.key === 'i' || e.key === 'I' || e.key === 'j' || e.key === 'J' || e.key === 'c' || e.key === 'C') {
                                    e.preventDefault();
                                    alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                                    return false;
                                }
                            }
                        }
                    }, { capture: true });
                }
                
                if (document.readyState === 'complete' || document.readyState === 'interactive') {
                    blockEvents();
                } else {
                    document.addEventListener('DOMContentLoaded', blockEvents);
                    window.addEventListener('load', blockEvents);
                }
            })();

        })();
    </script>

    </div>
</div>

<script>
        (function() {
            // Safe guard against duplicate initiation
            if (window.__torquepower_initialized) return;
            window.__torquepower_initialized = true;

            // 1. Core DOM queries
            const btnModeP = document.getElementById('btn-mode-p');
            const btnModeT = document.getElementById('btn-mode-t');
            const btnModeN = document.getElementById('btn-mode-n');

            const groupPower = document.getElementById('group-power');
            const groupTorque = document.getElementById('group-torque');
            const groupRpm = document.getElementById('group-rpm');

            const inputPower = document.getElementById('input-power');
            const sliderPower = document.getElementById('slider-power');
            const inputTorque = document.getElementById('input-torque');
            const sliderTorque = document.getElementById('slider-torque');
            const inputRpm = document.getElementById('input-rpm');
            const sliderRpm = document.getElementById('slider-rpm');

            const txtPrimaryOutput = document.getElementById('txt-primary-output');
            const txtSecondaryOutput = document.getElementById('txt-secondary-output');

            const resPower = document.getElementById('res-power');
            const resHp = document.getElementById('res-hp');
            const resTorqueNm = document.getElementById('res-torque-nm');
            const resTorqueKgm = document.getElementById('res-torque-kgm');

            const valOmega = document.getElementById('val-omega');
            const valWatts = document.getElementById('val-watts');
            const valRopeSpeed = document.getElementById('val-rope-speed');

            const overlayRunStatus = document.getElementById('overlay-run-status');
            const btnResetPos = document.getElementById('btn-reset-pos');

            // 2. Constants & Physics State
            const state = {
                mode: 'P', // 'P' (calc Power), 'T' (calc Torque), 'N' (calc RPM)
                power: 11.0,  // kW
                torque: 70.0, // N·m
                rpm: 1500.0,  // RPM
                angle: 0.0,
                ropeY: 180.0,
                ropeDir: 1.0,
                sparks: []
            };

            // Range definition (Min / Max)
            const ranges = {
                power: { min: 0.1, max: 250.0 },
                torque: { min: 1.0, max: 1000.0 },
                rpm: { min: 10.0, max: 5000.0 }
            };

            // 3. Mathematical Solvers
            function calculateThirdVariable() {
                if (state.mode === 'P') {
                    // P = T * N / 9549.3
                    state.power = (state.torque * state.rpm) / 9549.3;
                    // Apply bounds check
                    if (state.power < ranges.power.min) state.power = ranges.power.min;
                    if (state.power > ranges.power.max) state.power = ranges.power.max;
                } else if (state.mode === 'T') {
                    // T = 9549.3 * P / N
                    if (state.rpm > 0) {
                        state.torque = (9549.3 * state.power) / state.rpm;
                    } else {
                        state.torque = ranges.torque.min;
                    }
                    if (state.torque < ranges.torque.min) state.torque = ranges.torque.min;
                    if (state.torque > ranges.torque.max) state.torque = ranges.torque.max;
                } else if (state.mode === 'N') {
                    // N = 9549.3 * P / T
                    if (state.torque > 0) {
                        state.rpm = (9549.3 * state.power) / state.torque;
                    } else {
                        state.rpm = ranges.rpm.min;
                    }
                    if (state.rpm < ranges.rpm.min) state.rpm = ranges.rpm.min;
                    if (state.rpm > ranges.rpm.max) state.rpm = ranges.rpm.max;
                }
                updateUIValues();
            }

            // Sync text inputs and range sliders without aggressive clamping
            function syncPowerFromInput() {
                let val = parseFloat(inputPower.value);
                if (isNaN(val)) val = ranges.power.min;
                if (val < ranges.power.min) val = ranges.power.min;
                if (val > ranges.power.max) val = ranges.power.max;
                state.power = val;
                inputPower.value = val.toFixed(2);
                sliderPower.value = Math.round(val);
                calculateThirdVariable();
            }

            function syncPowerFromSlider() {
                state.power = parseFloat(sliderPower.value);
                inputPower.value = state.power.toFixed(1);
                calculateThirdVariable();
            }

            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);
                calculateThirdVariable();
            }

            function syncTorqueFromSlider() {
                state.torque = parseFloat(sliderTorque.value);
                inputTorque.value = state.torque.toFixed(0);
                calculateThirdVariable();
            }

            function syncRpmFromInput() {
                let val = parseFloat(inputRpm.value);
                if (isNaN(val)) val = ranges.rpm.min;
                if (val < ranges.rpm.min) val = ranges.rpm.min;
                if (val > ranges.rpm.max) val = ranges.rpm.max;
                state.rpm = val;
                inputRpm.value = val.toFixed(0);
                sliderRpm.value = Math.round(val);
                calculateThirdVariable();
            }

            function syncRpmFromSlider() {
                state.rpm = parseFloat(sliderRpm.value);
                inputRpm.value = state.rpm.toFixed(0);
                calculateThirdVariable();
            }

            // 4. Update UI Outputs
            function updateUIValues() {
                // Update inputs
                if (state.mode !== 'P') {
                    inputPower.value = state.power.toFixed(2);
                    sliderPower.value = Math.round(state.power);
                }
                if (state.mode !== 'T') {
                    inputTorque.value = state.torque.toFixed(1);
                    sliderTorque.value = Math.round(state.torque);
                }
                if (state.mode !== 'N') {
                    inputRpm.value = state.rpm.toFixed(0);
                    sliderRpm.value = Math.round(state.rpm);
                }

                // Primary Output Readout Box
                if (state.mode === 'P') {
                    txtPrimaryOutput.innerText = state.power.toFixed(2) + ' kW';
                    const hp = state.power * 1.35962;
                    txtSecondaryOutput.innerText = hp.toFixed(2) + ' HP (마력)';
                } else if (state.mode === 'T') {
                    txtPrimaryOutput.innerText = state.torque.toFixed(1) + ' N·m';
                    const kgm = state.torque / 9.80665;
                    txtSecondaryOutput.innerText = kgm.toFixed(2) + ' kgf·m (킬로그램토크)';
                } else if (state.mode === 'N') {
                    txtPrimaryOutput.innerText = state.rpm.toFixed(0) + ' RPM';
                    const hz = state.rpm / 60;
                    txtSecondaryOutput.innerText = hz.toFixed(1) + ' Hz (초당 회전수)';
                }

                // Results Grid Card Values
                resPower.innerText = state.power.toFixed(2) + ' kW';
                resHp.innerText = (state.power * 1.35962).toFixed(2) + ' HP';
                resTorqueNm.innerText = state.torque.toFixed(1) + ' N·m';
                resTorqueKgm.innerText = (state.torque / 9.80665).toFixed(2) + ' kgf·m';

                // Mini metrics strip at the bottom
                const omega = (2 * Math.PI * state.rpm) / 60;
                valOmega.innerText = omega.toFixed(1) + ' rad/s';
                valWatts.innerText = (state.power * 1000).toLocaleString('ko-KR', { maximumFractionDigits: 0 }) + ' W';
                
                // Winch drum radius is 50mm (0.05m). Speed = r * omega
                const ropeSpeed = 0.05 * omega;
                valRopeSpeed.innerText = ropeSpeed.toFixed(2) + ' m/s';

                // Thermal Warning status
                if (state.power > 150) {
                    overlayRunStatus.innerText = '고부하 운전 (과열 주의)';
                    overlayRunStatus.style.color = '#f59e0b';
                } else {
                    overlayRunStatus.innerText = '정상 운전 중';
                    overlayRunStatus.style.color = '#10b981';
                }
            }

            // 5. Calculate mode toggle
            function setMode(newMode) {
                state.mode = newMode;
                
                // Active button styling
                btnModeP.classList.remove('active');
                btnModeT.classList.remove('active');
                btnModeN.classList.remove('active');

                // Enable/disable slider blocks
                groupPower.classList.remove('disabled');
                groupTorque.classList.remove('disabled');
                groupRpm.classList.remove('disabled');

                if (newMode === 'P') {
                    btnModeP.classList.add('active');
                    groupPower.classList.add('disabled');
                } else if (newMode === 'T') {
                    btnModeT.classList.add('active');
                    groupTorque.classList.add('disabled');
                } else if (newMode === 'N') {
                    btnModeN.classList.add('active');
                    groupRpm.classList.add('disabled');
                }
                calculateThirdVariable();
            }

            // Presets selector
            const presetBtns = document.querySelectorAll('.preset-btn');
            presetBtns.forEach(btn => {
                btn.addEventListener('click', function() {
                    presetBtns.forEach(b => b.classList.remove('active'));
                    this.classList.add('active');

                    const preset = this.getAttribute('data-preset');
                    if (preset === 'standard') {
                        state.torque = 70.0;
                        state.rpm = 1500.0;
                        setMode('P');
                    } else if (preset === 'crane') {
                        state.torque = 650.0;
                        state.rpm = 160.0;
                        setMode('P');
                    } else if (preset === 'spindle') {
                        state.torque = 8.5;
                        state.rpm = 4500.0;
                        setMode('P');
                    }
                });
            });

            // 6. Interactive Canvas Physics Simulation
            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();
            // Polling backups to guarantee canvas is sized correctly after WordPress page loads
            setTimeout(initCanvas, 100);
            setTimeout(initCanvas, 300);
            setTimeout(initCanvas, 800);

            // Winch Rope limits will be calculated dynamically inside animation loop to prevent counterweight overlapping

            // Spark Particle Generator
            function addSparks(x, y, count) {
                for (let i = 0; i < count; i++) {
                    state.sparks.push({
                        x: x,
                        y: y,
                        vx: (Math.random() - 0.5) * 8 + (state.rpm > 0 ? 3 : -3),
                        vy: (Math.random() - 0.7) * 6 - 2,
                        life: 1.0,
                        decay: Math.random() * 0.05 + 0.03,
                        color: Math.random() > 0.4 ? '#0284c7' : '#db2777'
                    });
                }
            }

            function updateSparks() {
                for (let i = state.sparks.length - 1; i >= 0; i--) {
                    const p = state.sparks[i];
                    p.x += p.vx;
                    p.y += p.vy;
                    p.vy += 0.15; // gravity
                    p.life -= p.decay;
                    if (p.life <= 0) {
                        state.sparks.splice(i, 1);
                    }
                }
            }

            function drawDiameterLeader(c, x, y, r, valStr, labelPrefix, side) {
                c.save();
                c.strokeStyle = 'rgba(71, 85, 105, 0.4)';
                c.lineWidth = 1.2;
                
                // Angle of leader line (45 degrees up-left or up-right)
                const angle = side === -1 ? -Math.PI * 0.75 : -Math.PI * 0.25;
                const px = x + Math.cos(angle) * r;
                const py = y + Math.sin(angle) * r;
                
                // Draw a beautiful small dot on the circle boundary
                c.beginPath();
                c.arc(px, py, 2.5, 0, Math.PI * 2);
                c.fillStyle = side === -1 ? '#0284c7' : '#db2777';
                c.fill();
                
                // Draw leader lines: from circle boundary diagonal outwards, then horizontal
                const lx1 = px + Math.cos(angle) * 20;
                const ly1 = py + Math.sin(angle) * 20;
                const lx2 = lx1 + side * 45;
                const ly2 = ly1;
                
                c.beginPath();
                c.moveTo(px, py);
                c.lineTo(lx1, ly1);
                c.lineTo(lx2, ly2);
                c.stroke();
                
                // Draw text above the horizontal line
                const text = `${labelPrefix} ${valStr}`;
                c.font = 'bold 10px var(--font-body)';
                
                const tx = side === -1 ? lx1 - 4 : lx1 + 4;
                c.fillStyle = '#1e293b';
                c.textAlign = side === -1 ? 'right' : 'left';
                c.textBaseline = 'bottom';
                c.fillText(text, tx, ly2 - 1);
                c.restore();
            }

            // Main Animation loop
            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;

                // Dynamically center the motor-coupling-winch assembly to fill the canvas beautifully
                const totalSystemWidth = 410;
                const startX = (width - totalSystemWidth) / 2;
                
                const mx = startX + 70;
                const my = height / 2;
                const cpx = mx + 120;
                const wx = cpx + 140;

                // Physics state updates
                // Angular step = omega * dt
                const omega = (2 * Math.PI * state.rpm) / 60;
                state.angle += omega * dt;

                // Winch lifting ropes simulation
                // Dynamic Y boundaries to prevent counterweight overlapping with the winch drum!
                // The top of the counterweight box (ropeY + 12) should stop at the bottom of the drum cylinder (my + 32).
                const minRopeY = my + 20; // safe clearance limit
                const maxRopeY = my + 130;
                
                // Rope Y coordinate moves in proportion to speed
                const speedCoeff = 0.015;
                state.ropeY += state.ropeDir * omega * speedCoeff;
                if (state.ropeY > maxRopeY) {
                    state.ropeY = maxRopeY;
                    state.ropeDir = -1.0;
                }
                if (state.ropeY < minRopeY) {
                    state.ropeY = minRopeY;
                    state.ropeDir = 1.0;
                }
                // Clamp Y bounds dynamically on screen resize or initial load
                if (state.ropeY < minRopeY) state.ropeY = minRopeY;
                if (state.ropeY > maxRopeY) state.ropeY = maxRopeY;

                // Sparks fly on coupling joint if spinning
                if (state.rpm > 50) {
                    const sparkRate = Math.min(state.power * 0.1 + 1, 6);
                    if (Math.random() < sparkRate * dt * 25) {
                        // Position of coupling joint is dynamic (cpx, my)
                        addSparks(cpx, my, 2);
                    }
                }
                updateSparks();

                // Clear &#038; draw
                ctx.clearRect(0, 0, width, height);

                // 1. Draw 모눈종이 Blueprint Grid
                ctx.strokeStyle = 'rgba(2, 132, 199, 0.06)';
                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();
                }

                // 2. Draw Motor Housing (Left side centered dynamically)
                ctx.save();
                ctx.shadowBlur = 10;
                ctx.shadowColor = 'rgba(0, 0, 0, 0.05)';
                ctx.fillStyle = '#e2e8f0';
                ctx.strokeStyle = '#94a3b8';
                ctx.lineWidth = 4;
                ctx.beginPath();
                ctx.roundRect(mx - 70, my - 60, 140, 120, 16);
                ctx.fill();
                ctx.stroke();

                // Draw heat cooling fins on motor
                ctx.fillStyle = '#cbd5e1';
                for (let fx = mx - 50; fx <= mx + 30; fx += 20) {
                    ctx.fillRect(fx, my - 68, 10, 8);
                    ctx.fillRect(fx, my + 60, 10, 8);
                }

                // Draw Electric Flow inside Motor Housing
                if (state.power > 1.0) {
                    ctx.save();
                    ctx.strokeStyle = '#0284c7'; // Cobalt blue
                    ctx.lineWidth = 2.5;
                    ctx.shadowBlur = 8;
                    ctx.shadowColor = '#0284c7';
                    
                    const flowRate = (state.rpm / 60) * 8;
                    ctx.setLineDash([12, 18]);
                    ctx.lineDashOffset = -flowRate * (currentTime / 100);
                    
                    ctx.beginPath();
                    ctx.roundRect(mx - 62, my - 52, 124, 104, 12);
                    ctx.stroke();
                    ctx.restore();
                }
                ctx.restore();

                // 2b. Draw Motor Text Label
                ctx.save();
                ctx.fillStyle = '#64748b'; // Slate grey
                ctx.font = 'bold 10px var(--font-body)';
                ctx.textAlign = 'center';
                ctx.fillText('구동 모터 (MOTOR)', mx, my - 86);
                ctx.restore();

                // 3. Draw Rotating Coupling Shaft (Middle connection)
                ctx.save();
                ctx.fillStyle = '#475569';
                ctx.fillRect(mx + 70, my - 10, 130, 20); // steel shaft
                ctx.restore();

                // 3b. Draw Rotating Shaft Text Label
                ctx.save();
                ctx.fillStyle = '#64748b';
                ctx.font = 'bold 10px var(--font-body)';
                ctx.textAlign = 'center';
                ctx.fillText('회전 커플링 (COUPLING)', cpx, my - 86);
                ctx.restore();

                // 4. Draw Coupling Joint (Center at 240, 200)
                ctx.save();
                ctx.translate(cpx, my);
                ctx.rotate(state.angle);
                
                // Coupling Flange Disc (Gray plate)
                ctx.fillStyle = '#475569';
                ctx.strokeStyle = '#334155';
                ctx.lineWidth = 2;
                ctx.beginPath();
                ctx.arc(0, 0, 26, 0, Math.PI * 2);
                ctx.fill();
                ctx.stroke();

                // Center Sleeve Hub (Darker gray)
                ctx.fillStyle = '#1e293b';
                ctx.beginPath();
                ctx.arc(0, 0, 12, 0, Math.PI * 2);
                ctx.fill();

                // 4 Bolt Holes / Pins (rotating with the flange)
                ctx.fillStyle = '#94a3b8';
                for (let i = 0; i < 4; i++) {
                    const bAngle = (i * Math.PI) / 2;
                    ctx.beginPath();
                    ctx.arc(Math.cos(bAngle) * 18, Math.sin(bAngle) * 18, 3.5, 0, Math.PI * 2);
                    ctx.fill();
                }
                ctx.restore();

                // 5. Draw Winch Drum &#038; Frame (Centered dynamically)
                ctx.save();
                ctx.translate(wx, my);
                
                // Winch base bracket
                ctx.fillStyle = '#94a3b8';
                ctx.fillRect(-10, 48, 80, 16);
                ctx.fillRect(60, -60, 10, 120); // vertical pillar
                ctx.restore();

                // 5b. Draw Winch Text Label
                ctx.save();
                ctx.fillStyle = '#64748b';
                ctx.font = 'bold 10px var(--font-body)';
                ctx.textAlign = 'center';
                ctx.fillText('윈치 드럼 (WINCH DRUM)', wx, my - 86);
                ctx.restore();

                // Rotating Winch Drum &#038; Flanges
                ctx.save();
                ctx.translate(wx, my);
                ctx.rotate(state.angle);

                // Winch drum cylinder
                ctx.fillStyle = '#4b5563';
                ctx.beginPath();
                ctx.arc(0, 0, 32, 0, Math.PI * 2);
                ctx.fill();

                // Side flanges
                ctx.fillStyle = '#1f2937';
                ctx.fillRect(-8, -42, 16, 84);
                
                // Spoke lines on drum to visualize speed
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
                ctx.lineWidth = 3;
                for (let i = 0; i < 4; i++) {
                    const spAngle = (i * Math.PI) / 2;
                    ctx.beginPath();
                    ctx.moveTo(Math.cos(spAngle) * 8, Math.sin(spAngle) * 8);
                    ctx.lineTo(Math.cos(spAngle) * 28, Math.sin(spAngle) * 28);
                    ctx.stroke();
                }
                ctx.restore();

                // Draw Winch Drum Diameter Dimension Ø = 100 mm with professional CAD leader line
                // side=-1: leader goes upper-left to avoid overlap with LOAD text on right
                drawDiameterLeader(ctx, wx, my, 32, '100 mm', 'Ø =', -1);

                // 6. Draw Winch Rope &#038; Hanging Load Weight
                ctx.save();
                const ropeX = wx - 32;
                const ropeStartY = my; // rope exits from drum left side at center height
                
                // Rope line
                ctx.strokeStyle = state.torque > 350 ? '#db2777' : '#475569';
                ctx.lineWidth = state.torque > 350 ? 4 : 2.5;
                if (state.torque > 350) {
                    ctx.shadowBlur = 8;
                    ctx.shadowColor = '#db2777';
                }
                ctx.beginPath();
                ctx.moveTo(ropeX, ropeStartY);
                ctx.lineTo(ropeX, state.ropeY);
                ctx.stroke();
                ctx.shadowBlur = 0;

                // Hanging hook & mass weight block
                ctx.translate(ropeX, state.ropeY);
                
                // Load Mass Box
                const massWidth = 56;
                const massHeight = 44;
                ctx.fillStyle = '#334155';
                ctx.strokeStyle = '#1e293b';
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.roundRect(-massWidth / 2, 12, massWidth, massHeight, 6);
                ctx.fill();
                ctx.stroke();

                // Mass Text Label (Representing Torque)
                ctx.fillStyle = '#ffffff';
                ctx.font = 'bold 11px Inter';
                ctx.textAlign = 'center';
                const loadWeightKg = (state.torque / (9.80665 * 0.05)).toFixed(0); // T = F * r = m*g*r => m = T/(g*r)
                ctx.fillText(loadWeightKg + " kg", 0, 38);

                // Load Text Label
                ctx.fillStyle = '#64748b';
                ctx.font = 'bold 10px var(--font-body)';
                ctx.textAlign = 'left';
                ctx.fillText('부하 중량 (LOAD)', massWidth / 2 + 8, 38);

                // Rope hooks/eyelet
                ctx.strokeStyle = '#1e293b';
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.arc(0, 4, 6, 0, Math.PI * 2);
                ctx.stroke();
                ctx.restore();

                // 7. Draw Torque Vector & Speed Vector (Arrows on Drum)
                ctx.save();
                ctx.translate(wx, my);
                
                // Torque vector (glowing circle arrow, representing loading torque)
                if (state.torque > 5.0) {
                    ctx.save();
                    ctx.strokeStyle = '#db2777'; // Pink/magenta
                    ctx.lineWidth = Math.min(state.torque * 0.015 + 2, 8);
                    ctx.shadowBlur = 10;
                    ctx.shadowColor = '#db2777';
                    
                    ctx.beginPath();
                    ctx.arc(0, 0, 56, 0.1, Math.PI * 1.5);
                    ctx.stroke();

                    // Arrowhead
                    ctx.fillStyle = '#db2777';
                    ctx.beginPath();
                    ctx.moveTo(0, -56 - 6);
                    ctx.lineTo(12, -56);
                    ctx.lineTo(0, -56 + 6);
                    ctx.closePath();
                    ctx.fill();
                    ctx.restore();

                    // Vector text label
                    ctx.fillStyle = '#db2777';
                    ctx.font = 'bold 10px Inter';
                    ctx.textAlign = 'center';
                    ctx.fillText(`토크 T = ${state.torque.toFixed(1)} N·m`, 0, -68);
                }
                ctx.restore();

                // 8. Draw Spark particles
                ctx.save();
                for (let i = 0; i < state.sparks.length; i++) {
                    const p = state.sparks[i];
                    ctx.fillStyle = p.color;
                    ctx.globalAlpha = p.life;
                    ctx.shadowBlur = 6;
                    ctx.shadowColor = p.color;
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, 2.5 * p.life, 0, Math.PI * 2);
                    ctx.fill();
                }
                ctx.restore();

                requestAnimationFrame(animate);
            }
            requestAnimationFrame(animate);

            // 7. Events &#038; Initialization
            btnModeP.addEventListener('click', () => setMode('P'));
            btnModeT.addEventListener('click', () => setMode('T'));
            btnModeN.addEventListener('click', () => setMode('N'));

            // Slider & Text inputs binding
            inputPower.addEventListener('change', syncPowerFromInput);
            sliderPower.addEventListener('input', syncPowerFromSlider);

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

            inputRpm.addEventListener('change', syncRpmFromInput);
            sliderRpm.addEventListener('input', syncRpmFromSlider);

            // Manual position reset
            btnResetPos.addEventListener('click', () => {
                state.angle = 0;
                const dpr = getDPR();
                const my = (canvas.height / dpr) / 2;
                state.ropeY = my + 60; // Safe middle position between my+20 and my+130
                state.ropeDir = 1.0;
                state.sparks = [];
            });

            // Set initial state
            setMode('P');

            // 8. Copy and Right-Click Protection Document level
            (function() {
                function blockEvents() {
                    // Block right click on document and show custom alert
                    document.addEventListener('contextmenu', function(e) {
                        e.preventDefault();
                        alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                        return false;
                    }, { capture: true });
                    
                    // Block text selection on document
                    document.addEventListener('selectstart', function(e) {
                        e.preventDefault();
                        return false;
                    }, { capture: true });
                    
                    // Block developer tools and copy hotkeys
                    document.addEventListener('keydown', function(e) {
                        // nested if statements to avoid ampersands conversions in WordPress REST API
                        if (e.key === 'F12') {
                            e.preventDefault();
                            alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                            return false;
                        }
                        if (e.ctrlKey) {
                            if (e.key === 'u' || e.key === 'c' || e.key === 's') {
                                e.preventDefault();
                                alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                                return false;
                            }
                        }
                        if (e.ctrlKey) {
                            if (e.shiftKey) {
                                if (e.key === 'i' || e.key === 'I' || e.key === 'j' || e.key === 'J' || e.key === 'c' || e.key === 'C') {
                                    e.preventDefault();
                                    alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                                    return false;
                                }
                            }
                        }
                    }, { capture: true });
                }
                
                if (document.readyState === 'complete' || document.readyState === 'interactive') {
                    blockEvents();
                } else {
                    document.addEventListener('DOMContentLoaded', blockEvents);
                    window.addEventListener('load', 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>입력 단위 설정: 동력(kW, HP), 토크(N·m, kgf·m, kgf·cm), 회전 속도(RPM) 중 기준이 될 두 가지 변수를 활성화하여 입력합니다.</strong></li>
<li style="margin-bottom: 6px;">슬라이더 또는 직접 입력: 활성화된 두 변수의 슬라이더를 조절하거나 직접 값을 입력합니다. (실시간으로 나머지 한 변수가 계산됩니다.)</li>
<li style="margin-bottom: 6px;">실시간 물리 작용 확인: 입력한 토크와 속도에 맞춰 2D 모터 샤프트와 드럼이 회전하며, 토크가 높을수록 드럼에 감기는 로프의 장력과 모터 하우징의 동력 플로우(Glowing power flux)가 강하게 시각화됩니다.</li>
<li style="margin-bottom: 6px;">사전 정의 프리셋 활용: 고토크-저속(크레인), 저토크-고속(스핀들), 표준 산업용 모터 프리셋을 선택하여 동작 차이를 비교해 볼 수 있습니다.</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. 회전 기계설계에서 토크(Torque)와 동력(Power)의 관계</h3>
<p>기계설계 및 산업 현장에서 <strong>동력(Power, P)</strong>과 <strong>토크(Torque, T)</strong>는 회전 운동 시스템을 정의하는 핵심 물리량입니다. 토크는 물체를 회전시키려는 물리적인 힘의 모멘트(Moment of Force)를 뜻하며, 동력은 단위 시간당 모터가 수행할 수 있는 일의 양을 나타냅니다. 따라서 동력은 단순히 힘(토크)의 크기뿐만 아니라 그 힘이 얼마나 빠른 속도(각속도, &omega;)로 작용하고 있는지를 종합적으로 정의하는 물리량입니다.</p><p>축 설계(Shaft Design) 및 감속기(Gearbox) 선정 시 이 둘의 유기적인 관계를 파악하는 것이 필수적입니다. 감속기를 사용하여 회전 속도를 낮추면, 동력은 마찰 손실을 제외하고 일정하게 유지되지만 토크는 감속비에 비례하여 증대됩니다. 이를 통해 작은 용량의 모터로도 무거운 하중을 들어 올리거나 기계를 구동하는 시스템 설계가 가능해집니다.</p>
<h3>2. 핵심 변환 공식 유도 및 단위 환산</h3>
<p>물리학적으로 동력 <code>P</code>는 토크 <code>T</code>와 각속도 <code>&omega;</code>의 곱으로 정의됩니다:</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">P = T &times; &omega; &nbsp;[W]</p><p>여기서 각속도 <code>&omega;</code>를 실무에서 널리 사용하는 분당 회전수 <code>N [RPM]</code>으로 변환하면 <code>&omega; = (2&pi; &times; N) / 60</code>이 됩니다. 이를 대입하면 다음과 같습니다:</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">P = T &times; (2&pi; &times; N) / 60 &nbsp;[W]</p><p>와트(W) 단위를 킬로와트(kW) 단위로 환산(1kW = 1000W)하고 정밀 상수를 정리하면 실무에서 가장 빈번하게 사용되는 유명한 <strong>9549 공학 상수 공식</strong>이 유도됩니다:</p><p style="text-align: center; font-weight: bold; background: #e0f2fe; padding: 16px; border-radius: 8px; font-size: 1.1em; color: #0369a1;">P [kW] = (T [N&middot;m] &times; N [RPM]) / 9,549.3 &nbsp; 또는 &nbsp; T [N&middot;m] = 9,549.3 &times; P [kW] / N [RPM]</p><p>또한 마력(HP) 단위를 환산할 때 주로 사용되는 공학 상수는 716.2와 726입니다. 영국식 마력(HP)과 미터법 토크(kgf&middot;m) 단위 환산 관계는 다음과 같습니다:</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">T [kgf&middot;m] = 716.2 &times; P [HP] / N [RPM]</p>
<h3>3. 모터 용량 선정 가이드 및 안전 계수 (KS B 6310 규격 표준)</h3>
<p>산업용 회전 전동기 선정 시, 필요한 기계적 소요 동력에 적절한 안전율과 환경 계수를 가산하여 모터 정격 출력을 최종 산정해야 합니다. 모터의 시동 시에는 정격 토크의 1.5 ~ 2.5배에 달하는 시동 토크(Starting Torque)가 요구되므로 극심한 시동 부하 또는 정지 마찰을 이겨낼 수 있는 부하 검토가 병행되어야 합니다.</p><ul><li><strong>관성 부하 검토 (GD²):</strong> 대형 팬, 블로워, 플라이휠 등 부하측의 회전 관성 모멘트가 큰 기계는 모터가 정격 속도에 도달하기까지의 시동 시간 동안 과도한 발열이 누적되므로 정격 토크 대비 2배 이상의 기동 안전율을 반영합니다.</li><li><strong>과부하 내량 (Overload Capacity):</strong> 급격한 충격 부하가 빈번한 파쇄기나 절단기 등은 정상 운전 상태의 소요 토크 대비 부하 안전계수 <code>S_f = 1.5 ~ 2.0</code>을 적용하여 샤프트 및 키 홈의 전단 응력을 안전하게 검토해야 합니다 (KS B ISO 1940 참조).</li></ul>

        </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/torque-power-converter-simulator/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
