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

<channel>
	<title>체인전동계산 &#8211; MyEngNote</title>
	<atom:link href="https://myengnote.com/tag/%ec%b2%b4%ec%9d%b8%ec%a0%84%eb%8f%99%ea%b3%84%ec%82%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>
	</channel>
</rss>
