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

<channel>
	<title>벨트풀리 &#8211; MyEngNote</title>
	<atom:link href="https://myengnote.com/tag/%EB%B2%A8%ED%8A%B8%ED%92%80%EB%A6%AC/feed/" rel="self" type="application/rss+xml" />
	<link>https://myengnote.com</link>
	<description></description>
	<lastBuildDate>Thu, 28 May 2026 23:33:35 +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/belt-pulley-speed-calculator-simulator-2/</link>
					<comments>https://myengnote.com/belt-pulley-speed-calculator-simulator-2/#respond</comments>
		
		<dc:creator><![CDATA[동동]]></dc:creator>
		<pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate>
				<category><![CDATA[공학계산기]]></category>
		<category><![CDATA[기계설계]]></category>
		<category><![CDATA[동력전달]]></category>
		<category><![CDATA[물리시뮬레이터]]></category>
		<category><![CDATA[벨트계산기]]></category>
		<category><![CDATA[벨트풀리]]></category>
		<guid isPermaLink="false">https://myengnote.com/belt-pulley-speed-calculator-simulator-2/</guid>

					<description><![CDATA[두 풀리의 지름, 축간거리, 입력 RPM을 조절하여 벨트 길이, 접촉각, 벨트 속도 및 출력 속도를 실시간 계산하고 부드럽게 구동되는 벨트 전동 시스템을 시각화하는 초정밀 2D 시뮬레이터입니다.]]></description>
										<content:encoded><![CDATA[
<h2 style="font-size: 1.6em; font-weight: 800; color: #0c0e25; border-bottom: 2px solid #00f2fe; padding-bottom: 8px; margin-bottom: 20px;"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 벨트-풀리 계산기 &#038; 시뮬레이터</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 beltpulley-calculator-wrapper */

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

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

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

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

        .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.12; }
            100% { transform: scale(1.2) translate(50px, 50px); opacity: 0.22; }
        }

        .app-container {
            width: 100%;
            max-width: 1440px;
            padding: 24px;
            display: flex;
            flex-direction: column;
            gap: 24px;
            container-type: inline-size;
            container-name: beltpulley-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-muted));
            -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: #0284c7; }
        .text-magenta { color: #db2777; }
        .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: #0284c7;
            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;
        }

        /* D1 Webkit Thumb */
        #slider-d1::-webkit-slider-thumb {
            background: var(--color-cyan);
            box-shadow: 0 0 10px var(--color-cyan);
        }
        #slider-d1::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-cyan), 0 0 5px #fff;
        }
        /* D1 Firefox Thumb */
        #slider-d1::-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;
            transition: transform 0.1s ease, box-shadow 0.2s ease;
        }
        #slider-d1::-moz-range-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-cyan), 0 0 5px #fff;
        }

        /* D2 Webkit Thumb */
        #slider-d2::-webkit-slider-thumb {
            background: var(--color-magenta);
            box-shadow: 0 0 10px var(--color-magenta);
        }
        #slider-d2::-webkit-slider-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-magenta), 0 0 5px #fff;
        }
        /* D2 Firefox Thumb */
        #slider-d2::-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;
            transition: transform 0.1s ease, box-shadow 0.2s ease;
        }
        #slider-d2::-moz-range-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-magenta), 0 0 5px #fff;
        }

        /* C (Center Distance) Webkit Thumb */
        #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;
        }
        /* C (Center Distance) Firefox Thumb */
        #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;
            transition: transform 0.1s ease, box-shadow 0.2s ease;
        }
        #slider-c::-moz-range-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-cyan), 0 0 5px #fff;
        }

        /* RPM Webkit Thumb */
        #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;
        }
        /* RPM Firefox Thumb */
        #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;
            transition: transform 0.1s ease, box-shadow 0.2s ease;
        }
        #slider-rpm::-moz-range-thumb:hover {
            transform: scale(1.2);
            box-shadow: 0 0 15px var(--color-purple), 0 0 5px #fff;
        }

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

        .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);
            border-color: var(--color-border);
            transform: translateX(4px);
        }

        .preset-btn.active {
            background: linear-gradient(90deg, var(--color-cyan-glow), var(--color-magenta-glow));
            border-color: #0284c7;
            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: #0284c7;
            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: rgba(0, 0, 0, 0.03);
        }

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

        .ratio-readout-box {
            background: linear-gradient(135deg, rgba(0, 242, 254, 0.08) 0%, rgba(138, 43, 226, 0.03) 100%);
            border: 1px solid rgba(0, 242, 254, 0.25);
            border-radius: 16px;
            padding: 20px;
            text-align: center;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 6px;
            box-shadow: 0 0 15px rgba(0, 242, 254, 0.05);
        }

        .ratio-title {
            font-size: 11px;
            font-weight: 700;
            color: #0284c7;
            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: rgba(0, 242, 254, 0.2);
            background: rgba(0, 242, 254, 0.03);
        }

        .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: #0284c7;
            border-color: rgba(0, 242, 254, 0.3);
            background: rgba(0, 242, 254, 0.1);
        }

        .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: rgba(0, 0, 0, 0.01);
            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: monospace;
            color: #0284c7;
            font-size: 13px;
            background: #f8fafc; border-color: var(--color-border);
            padding: 6px 10px;
            border-radius: 6px;
            border: 1px solid var(--color-border);
            text-align: center;
        }

        /* Container Query for Responsiveness inside WP */
        @container beltpulley-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 beltpulley-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="beltpulley-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-circle-nodes"></i></div>
                <div>
                    <h1>BELT DRIVE</h1>
                    <div class="subtitle">벨트-풀리 속도 계산기 &#038; 2D 시뮬레이터</div>
                </div>
            </div>
            <div class="header-badge">
                <div class="pulse-dot"></div>
                <div class="badge-text">2D PHYSICS ENGINE 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>

                <!-- Pulley 1 Diameter D1 -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label><i class="fa-solid fa-circle text-cyan"></i> 구동 풀리 지름 (D₁)</label>
                        <span class="helper-text">구동측 (50 ~ 600 mm)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-d1" class="custom-number-input" min="50" max="600" value="100">
                        <div class="unit-badge">mm</div>
                    </div>
                    <input type="range" id="slider-d1" class="custom-slider" min="50" max="600" value="100">
                </div>

                <!-- Pulley 2 Diameter D2 -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label><i class="fa-solid fa-circle text-magenta"></i> 피동 풀리 지름 (D₂)</label>
                        <span class="helper-text">피동측 (50 ~ 600 mm)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-d2" class="custom-number-input" min="50" max="600" value="200">
                        <div class="unit-badge">mm</div>
                    </div>
                    <input type="range" id="slider-d2" class="custom-slider" min="50" max="600" value="200">
                </div>

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

                <!-- Input RPM N1 -->
                <div class="input-group">
                    <div class="input-label-row">
                        <label><i class="fa-solid fa-gauge-high text-purple"></i> 입력 회전수 (N₁)</label>
                        <span class="helper-text">입력 속도 (0 ~ 2500 RPM)</span>
                    </div>
                    <div class="number-input-wrapper">
                        <input type="number" id="input-rpm" class="custom-number-input" min="0" max="2500" value="240">
                        <div class="unit-badge">RPM</div>
                    </div>
                    <input type="range" id="slider-rpm" class="custom-slider" min="0" max="2500" value="240">
                </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" data-preset="reduction">
                            <div class="preset-icon"><i class="fa-solid fa-down-long"></i></div>
                            <div class="preset-name">2:1 V벨트 감속 시스템</div>
                        </button>
                        <button class="preset-btn" data-preset="mesh1to1">
                            <div class="preset-icon"><i class="fa-solid fa-left-right"></i></div>
                            <div class="preset-name">1:1 등속 동력 전달</div>
                        </button>
                        <button class="preset-btn" data-preset="increase">
                            <div class="preset-icon"><i class="fa-solid fa-up-long"></i></div>
                            <div class="preset-name">1:3 기계식 증속 전동</div>
                        </button>
                    </div>
                </div>
            </section>

            <!-- Right Panel: Physics Simulator & Integrated Results -->
            <section class="panel simulation-panel">
                <div class="panel-header">
                    <div style="display: flex; align-items: center; gap: 12px;">
                        <i class="fa-solid fa-circle-nodes text-cyan"></i>
                        <h2>실시간 벨트 주행 물리 뷰</h2>
                    </div>
                    <div id="txt-canvas-scale" class="canvas-scale-indicator">스케일 자동 조정</div>
                </div>

                <div class="canvas-wrapper">
                    <canvas id="physics-canvas"></canvas>
                    
                    <!-- Overlay Floating Data -->
                    <div class="canvas-overlay-data">
                        <div class="overlay-item">
                            <span class="label">총 벨트 길이 (L):</span>
                            <span class="value text-cyan" id="txt-belt-length-overlay">0.0 mm</span>
                        </div>
                    </div>
                </div>

                <!-- Mini Metrics -->
                <div class="simulation-metrics-strip">
                    <div class="mini-metric">
                        <div class="label">구동 풀리 접촉각 (θ₁)</div>
                        <div id="txt-lap-angle1" class="value">0.0°</div>
                    </div>
                    <div class="mini-divider"></div>
                    <div class="mini-metric">
                        <div class="label">피동 풀리 접촉각 (θ₂)</div>
                        <div id="txt-lap-angle2" class="value">0.0°</div>
                    </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">최종 풀리 배율 (i)</div>
                            <div id="txt-pulley-ratio" class="ratio-value" style="font-size: 24px;">2.00 : 1</div>
                            <div id="txt-ratio-type" class="ratio-type" style="font-size: 11px;">2.00배 감속 전동</div>
                        </div>
                        <div class="formula-card" style="padding: 12px; font-size: 11px; background: rgba(0, 0, 0, 0.01);">
                            <h4 style="font-size: 12px; margin-bottom: 6px;"><i class="fa-solid fa-circle-info text-cyan"></i> 벨트 전동 공학식</h4>
                            <div class="formula-equation" style="font-size: 11px; padding: 6px; margin-bottom: 6px;">
                                L ≈ 2C + π(D₁+D₂)/2 + (D₂-D₁)²/4C
                            </div>
                            <div class="formula-equation" style="font-size: 11px; padding: 6px;">
                                θ₁ = π &#8211; 2·sin⁻¹((D₂-D₁)/2C)
                            </div>
                        </div>
                    </div>

                    <!-- Right Column: Quantitative Results Cards -->
                    <div class="results-grid" style="display: flex; flex-direction: column; gap: 10px; justify-content: center;">
                        <!-- Belt Pitch Speed -->
                        <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-gauge-simple text-cyan"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">벨트 주속도 (v)</span>
                                <span id="txt-belt-speed" class="card-value" style="font-size: 15px;">0.0 m/s</span>
                            </div>
                        </div>

                        <!-- Pulley 2 Output Speed -->
                        <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-rotate text-magenta"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">피동 풀리 회전수 (N₂)</span>
                                <span id="txt-n2" class="card-value" style="font-size: 15px;">0.0 RPM</span>
                            </div>
                        </div>

                        <!-- Calculated Belt Length -->
                        <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-ruler-horizontal text-purple"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">계산된 표준 벨트 길이 (L)</span>
                                <span id="txt-belt-length" class="card-value" style="font-size: 15px;">0.0 mm</span>
                            </div>
                        </div>

                        <!-- Torque Factor -->
                        <div class="result-card" style="padding: 10px 14px; gap: 12px;">
                            <div class="card-icon" style="width: 32px; height: 32px; font-size: 12px;"><i class="fa-solid fa-bolt text-success"></i></div>
                            <div class="card-content">
                                <span class="card-unit" style="font-size: 10px;">출력 토크 변환율 (T₂/T₁)</span>
                                <span id="txt-torque-mult" class="card-value" style="font-size: 15px;">100%</span>
                            </div>
                        </div>
                    </div>
                </div>
            </section>
        </main>
    </div>

    <!-- Physics & Animation Engine -->
    <script>
        (function() {
        let isBeltInit = false;

        function initBeltSimulator() {
            if (isBeltInit) return;

            const canvas = document.getElementById('physics-canvas');
            if (!canvas) return;
            const ctx = canvas.getContext('2d');
            if (!ctx) return;

            isBeltInit = true;
            window.__beltpulley_initialized = true;

            // DOM Elements
            const sliderD1 = document.getElementById('slider-d1');
            const inputD1 = document.getElementById('input-d1');
            const sliderD2 = document.getElementById('slider-d2');
            const inputD2 = document.getElementById('input-d2');
            const sliderC = document.getElementById('slider-c');
            const inputC = document.getElementById('input-c');
            const sliderRpm = document.getElementById('slider-rpm');
            const inputRpm = document.getElementById('input-rpm');
            
            const btnPlayPause = document.getElementById('btn-play-pause');
            const btnReverse = document.getElementById('btn-reverse');
            const presetButtons = document.querySelectorAll('.preset-btn');
            
            const txtCenterDistOverlay = document.getElementById('txt-belt-length-overlay');
            const txtLapAngle1 = document.getElementById('txt-lap-angle1');
            const txtLapAngle2 = document.getElementById('txt-lap-angle2');
            
            const txtPulleyRatio = document.getElementById('txt-pulley-ratio');
            const txtRatioType = document.getElementById('txt-ratio-type');
            
            const txtBeltSpeed = document.getElementById('txt-belt-speed');
            const txtN2 = document.getElementById('txt-n2');
            const txtBeltLength = document.getElementById('txt-belt-length');
            const txtTorqueMult = document.getElementById('txt-torque-mult');
            
            const wrapper = document.querySelector('.beltpulley-calculator-wrapper') || document.body;

            // State variables
            const state = {
                d1: 100,
                d2: 200,
                c: 450,
                rpm: 240,
                angle1: 0,
                angle2: 0,
                beltOffset: 0,
                isPlaying: true,
                direction: 1,
                lastTime: 0,
                sparks: []
            };

            // Canvas Resizing (Retina support)
            function resizeCanvas() {
                const rect = canvas.getBoundingClientRect();
                const dpr = window.devicePixelRatio || 1;
                canvas.width = rect.width * dpr;
                canvas.height = rect.height * dpr;
                ctx.scale(dpr, dpr);
            }

            resizeCanvas();
            window.addEventListener('resize', resizeCanvas);
            setTimeout(resizeCanvas, 300);

            // Friction sparks at tangent points
            class BeltSpark {
                constructor(x, y, vx, vy) {
                    this.x = x;
                    this.y = y;
                    this.vx = vx;
                    this.vy = vy;
                    this.size = Math.random() * 1.5 + 0.5;
                    this.life = Math.random() * 20 + 8;
                    this.maxLife = this.life;
                    const colors = ['#00f2fe', '#ff007f', '#ffffff'];
                    this.color = colors[Math.floor(Math.random() * colors.length)];
                }
                update() {
                    this.x += this.vx;
                    this.y += this.vy;
                    this.vx *= 0.96;
                    this.vy *= 0.96;
                    this.life--;
                }
                draw(c) {
                    const alpha = this.life / this.maxLife;
                    c.save();
                    c.globalAlpha = alpha;
                    c.beginPath();
                    c.arc(this.x, this.y, this.size, 0, Math.PI * 2);
                    c.fillStyle = this.color;
                    c.fill();
                    c.restore();
                }
            }

            // Sync Inputs and Sliders
            function syncD1FromSlider() {
                const val = parseInt(sliderD1.value);
                inputD1.value = val;
                state.d1 = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }
            function syncD1FromInput() {
                let val = parseInt(inputD1.value);
                if (isNaN(val) || val < 50) val = 50;
                if (val > 600) val = 600;
                sliderD1.value = val;
                state.d1 = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }

            function syncD2FromSlider() {
                const val = parseInt(sliderD2.value);
                inputD2.value = val;
                state.d2 = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }
            function syncD2FromInput() {
                let val = parseInt(inputD2.value);
                if (isNaN(val) || val < 50) val = 50;
                if (val > 600) val = 600;
                sliderD2.value = val;
                state.d2 = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }

            function syncCFromSlider() {
                const val = parseInt(sliderC.value);
                inputC.value = val;
                state.c = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }
            function syncCFromInput() {
                let val = parseInt(inputC.value);
                if (isNaN(val) || val < 200) val = 200;
                if (val > 1200) val = 1200;
                sliderC.value = val;
                state.c = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }

            function syncRpmFromSlider() {
                const val = parseInt(sliderRpm.value);
                inputRpm.value = val;
                state.rpm = val;
                clearPresets();
                updateCalculations();
            }
            function syncRpmFromInput() {
                let val = parseInt(inputRpm.value);
                if (isNaN(val) || val < 0) val = 0;
                if (val > 2500) val = 2500;
                sliderRpm.value = val;
                state.rpm = val;
                clearPresets();
                updateCalculations();
            }

            // Ensure center distance is physically larger than radius1 + radius2 to avoid overlap
            function validateCenterDistance() {
                const minC = (state.d1 + state.d2) / 2 + 30; // 30mm margin
                if (state.c < minC) {
                    state.c = Math.ceil(minC);
                    inputC.value = state.c;
                    sliderC.value = state.c;
                }
                
                // Set slider range dynamically
                sliderC.min = Math.ceil(minC);
                if (parseInt(inputC.value) < minC) {
                    inputC.value = Math.ceil(minC);
                    sliderC.value = Math.ceil(minC);
                    state.c = Math.ceil(minC);
                }
            }

            // Physics Core Calculations
            function updateCalculations() {
                const D1 = state.d1;
                const D2 = state.d2;
                const C = state.c;
                const N1 = state.rpm;

                // 1. Pulley Ratio
                const ratio = D2 / D1;
                txtPulleyRatio.innerText = ratio.toFixed(2) + ' : 1';
                if (ratio > 1) {
                    txtRatioType.innerText = `${ratio.toFixed(2)}배 감속 전동 (출력 토크 ${ratio.toFixed(2)}배)`;
                } else if (ratio < 1) {
                    const gain = 1 / ratio;
                    txtRatioType.innerText = `${gain.toFixed(2)}배 증속 전동 (출력 토크 ${ratio.toFixed(2)}배)`;
                } else {
                    txtRatioType.innerText = '1:1 동속 동력 전동';
                }

                // 2. Output RPM
                const n2 = N1 / ratio;
                txtN2.innerText = n2.toFixed(1) + ' RPM';
                txtTorqueMult.innerText = (ratio * 100).toFixed(0) + '%';

                // 3. Pitch line velocity (Belt Speed in m/s)
                const pitchSpeed = (Math.PI * D1 * N1) / 60000;
                txtBeltSpeed.innerText = pitchSpeed.toFixed(2) + ' m/s';

                // 4. Belt Length calculation
                const term1 = 2 * C;
                const term2 = (Math.PI * (D1 + D2)) / 2;
                const term3 = Math.pow(D2 - D1, 2) / (4 * C);
                const beltLen = term1 + term2 + term3;
                txtBeltLength.innerText = beltLen.toFixed(1) + ' mm';
                txtCenterDistOverlay.innerText = beltLen.toFixed(1) + ' mm';

                // 5. Lap angles
                // sin(alpha) = (D2 - D1) / 2C
                const sinAlpha = (D2 - D1) / (2 * C);
                const alpha = Math.asin(Math.min(Math.max(sinAlpha, -0.99), 0.99));
                
                const angle1Rad = Math.PI - 2 * alpha;
                const angle2Rad = Math.PI + 2 * alpha;

                const angle1Deg = (angle1Rad * 180) / Math.PI;
                const angle2Deg = (angle2Rad * 180) / Math.PI;

                txtLapAngle1.innerText = angle1Deg.toFixed(1) + '°';
                txtLapAngle2.innerText = angle2Deg.toFixed(1) + '°';

                if (angle1Deg < 120) {
                    txtLapAngle1.style.color = '#db2777';
                } else {
                    txtLapAngle1.style.color = '#10b981';
                }
            }

            // Presets Click Handler
            function loadPreset(key) {
                clearPresets();
                presetButtons.forEach(btn => {
                    if (btn.dataset.preset === key) {
                        btn.classList.add('active');
                    }
                });

                if (key === 'reduction') {
                    state.d1 = 120;
                    state.d2 = 240;
                    state.c = 480;
                    state.rpm = 300;
                } else if (key === 'mesh1to1') {
                    state.d1 = 180;
                    state.d2 = 180;
                    state.c = 450;
                    state.rpm = 200;
                } else if (key === 'increase') {
                    state.d1 = 240;
                    state.d2 = 80;
                    state.c = 450;
                    state.rpm = 100;
                }

                // Sync inputs
                inputD1.value = state.d1;
                sliderD1.value = state.d1;
                inputD2.value = state.d2;
                sliderD2.value = state.d2;
                inputC.value = state.c;
                sliderC.value = state.c;
                inputRpm.value = state.rpm;
                sliderRpm.value = state.rpm;

                validateCenterDistance();
                updateCalculations();
            }

            function clearPresets() {
                presetButtons.forEach(btn => btn.classList.remove('active'));
            }

            // Draw Pulley Wheel
            function drawPulley(c, x, y, radius, angle, themeColor) {
                c.save();
                c.translate(x, y);

                // 1. Pulley Outer Body (dark glass fill)
                c.beginPath();
                c.arc(0, 0, radius, 0, Math.PI * 2);
                c.fillStyle = 'rgba(241, 245, 249, 0.85)';
                c.fill();
                c.strokeStyle = themeColor;
                c.lineWidth = 3;
                c.shadowBlur = 8;
                c.shadowColor = themeColor;
                c.stroke();
                c.shadowBlur = 0;

                // 2. Pulley Inner Rim Ring
                c.beginPath();
                c.arc(0, 0, radius - 6, 0, Math.PI * 2);
                c.strokeStyle = 'rgba(15, 23, 42, 0.06)';
                c.lineWidth = 1.5;
                c.stroke();

                // 3. Rotating Spokes
                c.save();
                c.rotate(angle);
                
                // Central hub
                const hubRad = Math.max(radius * 0.25, 12);
                c.beginPath();
                c.arc(0, 0, hubRad, 0, Math.PI * 2);
                c.strokeStyle = 'rgba(15, 23, 42, 0.15)';
                c.lineWidth = 2;
                c.stroke();

                c.beginPath();
                c.arc(0, 0, 5, 0, Math.PI * 2);
                c.fillStyle = '#ffffff';
                c.fill();

                // 5 spokes
                const spokes = 5;
                for (let s = 0; s < spokes; s++) {
                    const spokeAngle = (s * 2 * Math.PI) / spokes;
                    c.save();
                    c.rotate(spokeAngle);
                    c.beginPath();
                    c.moveTo(0, hubRad);
                    c.lineTo(0, radius - 8);
                    c.strokeStyle = 'rgba(15, 23, 42, 0.08)';
                    c.lineWidth = 3;
                    c.stroke();
                    c.restore();
                }
                
                // Keyway marker (to see rotation clearly)
                c.beginPath();
                c.arc(radius - 12, 0, 4, 0, Math.PI * 2);
                c.fillStyle = '#ffffff';
                c.shadowBlur = 4;
                c.shadowColor = '#ffffff';
                c.fill();

                c.restore();
                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 Frame
            function animate(currentTime) {
                requestAnimationFrame(animate);

                let dt = (currentTime - state.lastTime) / 1000;
                if (isNaN(dt) || dt < 0 || dt > 1.0) dt = 0.016;
                state.lastTime = currentTime;

                const width = canvas.width / (window.devicePixelRatio || 1);
                const height = canvas.height / (window.devicePixelRatio || 1);
                const cx = width / 2;
                const cy = height / 2;

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

                // 1. Draw Grid
                ctx.save();
                ctx.strokeStyle = 'rgba(2, 132, 199, 0.06)';
                ctx.lineWidth = 1;
                for (let x = 0; x < width; x += 30) {
                    ctx.beginPath();
                    ctx.moveTo(x, 0);
                    ctx.lineTo(x, height);
                    ctx.stroke();
                }
                for (let y = 0; y < height; y += 30) {
                    ctx.beginPath();
                    ctx.moveTo(0, y);
                    ctx.lineTo(width, y);
                    ctx.stroke();
                }
                ctx.restore();

                // 2. Geometry Scale Calculation
                const phys_r1 = state.d1 / 2;
                const phys_r2 = state.d2 / 2;

                const scaleWidth = (width - 190) / (state.c + phys_r1 + phys_r2);
                const scaleHeight = (height - 120) / Math.max(state.d1, state.d2);
                const scale = Math.min(scaleWidth, scaleHeight);
                
                const txtCanvasScale = document.getElementById('txt-canvas-scale');
                if (txtCanvasScale) {
                    txtCanvasScale.innerText = `스케일 비율: 1:${(1/scale * 100).toFixed(0)}급`;
                }

                const r1 = phys_r1 * scale;
                const r2 = phys_r2 * scale;
                const c_vis = state.c * scale;

                // Position centers to perfectly center the entire bounding box
                const x1 = (width - c_vis + r1 - r2) / 2;
                const y1 = cy;
                const x2 = x1 + c_vis;
                const y2 = cy;

                // Physics Rotation
                const ratio = state.d2 / state.d1;
                const pitchSpeed = (Math.PI * state.d1 * state.rpm) / 60000; // m/s

                if (state.isPlaying) {
                    if (state.rpm > 0) {
                        const omega1 = (2 * Math.PI * state.rpm) / 60; // rad/s
                        
                        // Stroboscopic rendering limit
                        const maxOmega = 6;
                        const renderOmega1 = maxOmega * (1 - Math.exp(-omega1 / maxOmega));
                        
                        state.angle1 += state.direction * renderOmega1 * dt;
                        state.angle2 += state.direction * (renderOmega1 / ratio) * dt;

                        // Linear belt move offset
                        state.beltOffset += state.direction * pitchSpeed * 1000 * scale * dt;
                    }
                }

                // 3. Tangent belt geometry calculations
                const dx = x2 - x1;
                const alpha = Math.asin((r2 - r1) / dx);

                // Tangent angles relative to horizontal
                const theta_top1 = -Math.PI / 2 - alpha;
                const theta_bottom1 = Math.PI / 2 + alpha;
                
                const theta_top2 = -Math.PI / 2 - alpha;
                const theta_bottom2 = Math.PI / 2 + alpha;

                // Tangent Points on Pulley 1
                const tp1_top_x = x1 + r1 * Math.cos(theta_top1);
                const tp1_top_y = y1 + r1 * Math.sin(theta_top1);
                const tp1_bottom_x = x1 + r1 * Math.cos(theta_bottom1);
                const tp1_bottom_y = y1 + r1 * Math.sin(theta_bottom1);

                // Tangent Points on Pulley 2
                const tp2_top_x = x2 + r2 * Math.cos(theta_top2);
                const tp2_top_y = y2 + r2 * Math.sin(theta_top2);
                const tp2_bottom_x = x2 + r2 * Math.cos(theta_bottom2);
                const tp2_bottom_y = y2 + r2 * Math.sin(theta_bottom2);

                // 4. Spawn Friction Sparks at Tangent Points
                if (state.isPlaying) {
                    if (state.rpm > 300) {
                        const sparkEmission = Math.min(Math.floor(state.rpm / 600) + 1, 2);
                        for (let s = 0; s < sparkEmission; s++) {
                            if (Math.random() > 0.6) {
                                // Top tangent left sparks
                                const spVx = -state.direction * (2 + Math.random() * 2);
                                const spVy = (Math.random() - 0.5) * 1.5;
                                state.sparks.push(new BeltSpark(tp1_top_x, tp1_top_y, spVx * scale, spVy * scale));
                            }
                        }
                    }
                }

                // Render Sparks
                state.sparks = state.sparks.filter(s => s.life > 0);
                state.sparks.forEach(s => {
                    s.update();
                    s.draw(ctx);
                });

                // 5. Draw Pulley 1 (Drive - Cyan)
                drawPulley(ctx, x1, y1, r1, state.angle1, '#0284c7');

                // Draw Pulley 2 (Driven - Magenta)
                drawPulley(ctx, x2, y2, r2, state.angle2, '#db2777');

                // 6. Draw Snug Belt Around both pulleys (Moving dashed line)
                ctx.save();
                ctx.beginPath();
                
                // Top tangent line
                ctx.moveTo(tp1_top_x, tp1_top_y);
                ctx.lineTo(tp2_top_x, tp2_top_y);
                
                // Clockwise arc around Pulley 2
                ctx.arc(x2, y2, r2, theta_top2, theta_bottom2, false);
                
                // Bottom tangent line
                ctx.lineTo(tp1_bottom_x, tp1_bottom_y);
                
                // Clockwise arc around Pulley 1
                ctx.arc(x1, y1, r1, theta_bottom1, theta_top1, false);

                ctx.closePath();

                // Belt outer shadow/glow
                ctx.shadowBlur = 12;
                ctx.shadowColor = '#0284c7';
                ctx.strokeStyle = '#0284c7';
                ctx.lineWidth = 7;
                ctx.lineCap = 'round';
                ctx.lineJoin = 'round';
                ctx.stroke();
                ctx.shadowBlur = 0;

                // Moving belt textured interior dashes
                ctx.strokeStyle = '#ffffff';
                ctx.lineWidth = 5;
                ctx.setLineDash([16, 12]);
                ctx.lineDashOffset = -state.beltOffset;
                ctx.stroke();
                ctx.setLineDash([]);
                ctx.restore();

                // 7. Dimension annotations
                ctx.save();
                
                // Draw Diameter dimensions on pulleys with professional CAD leader lines
                drawDiameterLeader(ctx, x1, y1, r1, state.d1.toFixed(1) + ' mm', 'd₁ =', -1);
                drawDiameterLeader(ctx, x2, y2, r2, state.d2.toFixed(1) + ' mm', 'd₂ =', 1);

                // Horizontal Center Distance Dimension Line (C)
                ctx.save();
                ctx.strokeStyle = 'rgba(71, 85, 105, 0.3)';
                ctx.lineWidth = 1;
                
                const dimY = y1 + r2 + 25; // 25px below larger pulley
                
                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();

                // Lap angle highlights (colored arcs representing wrap contact)
                ctx.beginPath();
                ctx.arc(x1, y1, r1 + 10, theta_bottom1, theta_top1, false);
                ctx.strokeStyle = 'rgba(0, 242, 254, 0.35)';
                ctx.lineWidth = 3;
                ctx.stroke();

                ctx.beginPath();
                ctx.arc(x2, y2, r2 + 10, theta_top2, theta_bottom2, false);
                ctx.strokeStyle = 'rgba(255, 0, 127, 0.35)';
                ctx.lineWidth = 3;
                ctx.stroke();
                ctx.restore();
            }

            // Bind Event Listeners
            sliderD1.addEventListener('input', syncD1FromSlider);
            inputD1.addEventListener('change', syncD1FromInput);

            sliderD2.addEventListener('input', syncD2FromSlider);
            inputD2.addEventListener('change', syncD2FromInput);

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

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

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

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

            presetButtons.forEach(btn => {
                btn.addEventListener('click', () => {
                    loadPreset(btn.dataset.preset);
                });
            });

            // Initialize
            loadPreset('reduction');
            requestAnimationFrame(animate);

            // ── 우클릭 & 소스 복사 방지 (전체 문서 레벨 + 알림창 동기화) ──
            document.addEventListener('contextmenu', function(e) {
                e.preventDefault();
                alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                return false;
            }, { capture: true });

            document.addEventListener('selectstart', function(e) {
                e.preventDefault();
                return false;
            }, { capture: true });

            // 개발자도구 단축키 및 복사 단축키 차단 (F12, Ctrl+U, Ctrl+Shift+I/J/C, Ctrl+C, Ctrl+S)
            // 중복 차단 문자를 사용하면 WordPress API에서 오류가 날 수 있으므로 nested if로 구현
            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.key === 'U' || e.key === 'C' || e.key === 'S') {
                        e.preventDefault();
                        alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                        return false;
                    }
                    if (e.shiftKey) {
                        if (['i','I','j','J','c','C'].includes(e.key)) {
                            e.preventDefault();
                            alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                            return false;
                        }
                    }
                }
            }, { capture: true });
        } // <-- Closed the initBeltSimulator function

        // Robust ReadyState hook-up with polling to guarantee execution even if DOM elements load late
        let initAttempts = 0;
        function tryInit() {
            initAttempts++;
            initBeltSimulator();
            if (!window.__beltpulley_initialized) {
                if (initAttempts < 50) {
                    setTimeout(tryInit, 100);
                }
            }
        }

        if (document.readyState === 'complete' || document.readyState === 'interactive') {
            tryInit();
        } else {
            document.addEventListener('DOMContentLoaded', tryInit);
            window.addEventListener('load', tryInit);
        }
        })();
    </script>

    </div>
</div>

<script>
        (function() {
        let isBeltInit = false;

        function initBeltSimulator() {
            if (isBeltInit) return;

            const canvas = document.getElementById('physics-canvas');
            if (!canvas) return;
            const ctx = canvas.getContext('2d');
            if (!ctx) return;

            isBeltInit = true;
            window.__beltpulley_initialized = true;

            // DOM Elements
            const sliderD1 = document.getElementById('slider-d1');
            const inputD1 = document.getElementById('input-d1');
            const sliderD2 = document.getElementById('slider-d2');
            const inputD2 = document.getElementById('input-d2');
            const sliderC = document.getElementById('slider-c');
            const inputC = document.getElementById('input-c');
            const sliderRpm = document.getElementById('slider-rpm');
            const inputRpm = document.getElementById('input-rpm');
            
            const btnPlayPause = document.getElementById('btn-play-pause');
            const btnReverse = document.getElementById('btn-reverse');
            const presetButtons = document.querySelectorAll('.preset-btn');
            
            const txtCenterDistOverlay = document.getElementById('txt-belt-length-overlay');
            const txtLapAngle1 = document.getElementById('txt-lap-angle1');
            const txtLapAngle2 = document.getElementById('txt-lap-angle2');
            
            const txtPulleyRatio = document.getElementById('txt-pulley-ratio');
            const txtRatioType = document.getElementById('txt-ratio-type');
            
            const txtBeltSpeed = document.getElementById('txt-belt-speed');
            const txtN2 = document.getElementById('txt-n2');
            const txtBeltLength = document.getElementById('txt-belt-length');
            const txtTorqueMult = document.getElementById('txt-torque-mult');
            
            const wrapper = document.querySelector('.beltpulley-calculator-wrapper') || document.body;

            // State variables
            const state = {
                d1: 100,
                d2: 200,
                c: 450,
                rpm: 240,
                angle1: 0,
                angle2: 0,
                beltOffset: 0,
                isPlaying: true,
                direction: 1,
                lastTime: 0,
                sparks: []
            };

            // Canvas Resizing (Retina support)
            function resizeCanvas() {
                const rect = canvas.getBoundingClientRect();
                const dpr = window.devicePixelRatio || 1;
                canvas.width = rect.width * dpr;
                canvas.height = rect.height * dpr;
                ctx.scale(dpr, dpr);
            }

            resizeCanvas();
            window.addEventListener('resize', resizeCanvas);
            setTimeout(resizeCanvas, 300);

            // Friction sparks at tangent points
            class BeltSpark {
                constructor(x, y, vx, vy) {
                    this.x = x;
                    this.y = y;
                    this.vx = vx;
                    this.vy = vy;
                    this.size = Math.random() * 1.5 + 0.5;
                    this.life = Math.random() * 20 + 8;
                    this.maxLife = this.life;
                    const colors = ['#00f2fe', '#ff007f', '#ffffff'];
                    this.color = colors[Math.floor(Math.random() * colors.length)];
                }
                update() {
                    this.x += this.vx;
                    this.y += this.vy;
                    this.vx *= 0.96;
                    this.vy *= 0.96;
                    this.life--;
                }
                draw(c) {
                    const alpha = this.life / this.maxLife;
                    c.save();
                    c.globalAlpha = alpha;
                    c.beginPath();
                    c.arc(this.x, this.y, this.size, 0, Math.PI * 2);
                    c.fillStyle = this.color;
                    c.fill();
                    c.restore();
                }
            }

            // Sync Inputs and Sliders
            function syncD1FromSlider() {
                const val = parseInt(sliderD1.value);
                inputD1.value = val;
                state.d1 = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }
            function syncD1FromInput() {
                let val = parseInt(inputD1.value);
                if (isNaN(val) || val < 50) val = 50;
                if (val > 600) val = 600;
                sliderD1.value = val;
                state.d1 = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }

            function syncD2FromSlider() {
                const val = parseInt(sliderD2.value);
                inputD2.value = val;
                state.d2 = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }
            function syncD2FromInput() {
                let val = parseInt(inputD2.value);
                if (isNaN(val) || val < 50) val = 50;
                if (val > 600) val = 600;
                sliderD2.value = val;
                state.d2 = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }

            function syncCFromSlider() {
                const val = parseInt(sliderC.value);
                inputC.value = val;
                state.c = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }
            function syncCFromInput() {
                let val = parseInt(inputC.value);
                if (isNaN(val) || val < 200) val = 200;
                if (val > 1200) val = 1200;
                sliderC.value = val;
                state.c = val;
                validateCenterDistance();
                clearPresets();
                updateCalculations();
            }

            function syncRpmFromSlider() {
                const val = parseInt(sliderRpm.value);
                inputRpm.value = val;
                state.rpm = val;
                clearPresets();
                updateCalculations();
            }
            function syncRpmFromInput() {
                let val = parseInt(inputRpm.value);
                if (isNaN(val) || val < 0) val = 0;
                if (val > 2500) val = 2500;
                sliderRpm.value = val;
                state.rpm = val;
                clearPresets();
                updateCalculations();
            }

            // Ensure center distance is physically larger than radius1 + radius2 to avoid overlap
            function validateCenterDistance() {
                const minC = (state.d1 + state.d2) / 2 + 30; // 30mm margin
                if (state.c < minC) {
                    state.c = Math.ceil(minC);
                    inputC.value = state.c;
                    sliderC.value = state.c;
                }
                
                // Set slider range dynamically
                sliderC.min = Math.ceil(minC);
                if (parseInt(inputC.value) < minC) {
                    inputC.value = Math.ceil(minC);
                    sliderC.value = Math.ceil(minC);
                    state.c = Math.ceil(minC);
                }
            }

            // Physics Core Calculations
            function updateCalculations() {
                const D1 = state.d1;
                const D2 = state.d2;
                const C = state.c;
                const N1 = state.rpm;

                // 1. Pulley Ratio
                const ratio = D2 / D1;
                txtPulleyRatio.innerText = ratio.toFixed(2) + ' : 1';
                if (ratio > 1) {
                    txtRatioType.innerText = `${ratio.toFixed(2)}배 감속 전동 (출력 토크 ${ratio.toFixed(2)}배)`;
                } else if (ratio < 1) {
                    const gain = 1 / ratio;
                    txtRatioType.innerText = `${gain.toFixed(2)}배 증속 전동 (출력 토크 ${ratio.toFixed(2)}배)`;
                } else {
                    txtRatioType.innerText = '1:1 동속 동력 전동';
                }

                // 2. Output RPM
                const n2 = N1 / ratio;
                txtN2.innerText = n2.toFixed(1) + ' RPM';
                txtTorqueMult.innerText = (ratio * 100).toFixed(0) + '%';

                // 3. Pitch line velocity (Belt Speed in m/s)
                const pitchSpeed = (Math.PI * D1 * N1) / 60000;
                txtBeltSpeed.innerText = pitchSpeed.toFixed(2) + ' m/s';

                // 4. Belt Length calculation
                const term1 = 2 * C;
                const term2 = (Math.PI * (D1 + D2)) / 2;
                const term3 = Math.pow(D2 - D1, 2) / (4 * C);
                const beltLen = term1 + term2 + term3;
                txtBeltLength.innerText = beltLen.toFixed(1) + ' mm';
                txtCenterDistOverlay.innerText = beltLen.toFixed(1) + ' mm';

                // 5. Lap angles
                // sin(alpha) = (D2 - D1) / 2C
                const sinAlpha = (D2 - D1) / (2 * C);
                const alpha = Math.asin(Math.min(Math.max(sinAlpha, -0.99), 0.99));
                
                const angle1Rad = Math.PI - 2 * alpha;
                const angle2Rad = Math.PI + 2 * alpha;

                const angle1Deg = (angle1Rad * 180) / Math.PI;
                const angle2Deg = (angle2Rad * 180) / Math.PI;

                txtLapAngle1.innerText = angle1Deg.toFixed(1) + '°';
                txtLapAngle2.innerText = angle2Deg.toFixed(1) + '°';

                if (angle1Deg < 120) {
                    txtLapAngle1.style.color = '#db2777';
                } else {
                    txtLapAngle1.style.color = '#10b981';
                }
            }

            // Presets Click Handler
            function loadPreset(key) {
                clearPresets();
                presetButtons.forEach(btn => {
                    if (btn.dataset.preset === key) {
                        btn.classList.add('active');
                    }
                });

                if (key === 'reduction') {
                    state.d1 = 120;
                    state.d2 = 240;
                    state.c = 480;
                    state.rpm = 300;
                } else if (key === 'mesh1to1') {
                    state.d1 = 180;
                    state.d2 = 180;
                    state.c = 450;
                    state.rpm = 200;
                } else if (key === 'increase') {
                    state.d1 = 240;
                    state.d2 = 80;
                    state.c = 450;
                    state.rpm = 100;
                }

                // Sync inputs
                inputD1.value = state.d1;
                sliderD1.value = state.d1;
                inputD2.value = state.d2;
                sliderD2.value = state.d2;
                inputC.value = state.c;
                sliderC.value = state.c;
                inputRpm.value = state.rpm;
                sliderRpm.value = state.rpm;

                validateCenterDistance();
                updateCalculations();
            }

            function clearPresets() {
                presetButtons.forEach(btn => btn.classList.remove('active'));
            }

            // Draw Pulley Wheel
            function drawPulley(c, x, y, radius, angle, themeColor) {
                c.save();
                c.translate(x, y);

                // 1. Pulley Outer Body (dark glass fill)
                c.beginPath();
                c.arc(0, 0, radius, 0, Math.PI * 2);
                c.fillStyle = 'rgba(241, 245, 249, 0.85)';
                c.fill();
                c.strokeStyle = themeColor;
                c.lineWidth = 3;
                c.shadowBlur = 8;
                c.shadowColor = themeColor;
                c.stroke();
                c.shadowBlur = 0;

                // 2. Pulley Inner Rim Ring
                c.beginPath();
                c.arc(0, 0, radius - 6, 0, Math.PI * 2);
                c.strokeStyle = 'rgba(15, 23, 42, 0.06)';
                c.lineWidth = 1.5;
                c.stroke();

                // 3. Rotating Spokes
                c.save();
                c.rotate(angle);
                
                // Central hub
                const hubRad = Math.max(radius * 0.25, 12);
                c.beginPath();
                c.arc(0, 0, hubRad, 0, Math.PI * 2);
                c.strokeStyle = 'rgba(15, 23, 42, 0.15)';
                c.lineWidth = 2;
                c.stroke();

                c.beginPath();
                c.arc(0, 0, 5, 0, Math.PI * 2);
                c.fillStyle = '#ffffff';
                c.fill();

                // 5 spokes
                const spokes = 5;
                for (let s = 0; s < spokes; s++) {
                    const spokeAngle = (s * 2 * Math.PI) / spokes;
                    c.save();
                    c.rotate(spokeAngle);
                    c.beginPath();
                    c.moveTo(0, hubRad);
                    c.lineTo(0, radius - 8);
                    c.strokeStyle = 'rgba(15, 23, 42, 0.08)';
                    c.lineWidth = 3;
                    c.stroke();
                    c.restore();
                }
                
                // Keyway marker (to see rotation clearly)
                c.beginPath();
                c.arc(radius - 12, 0, 4, 0, Math.PI * 2);
                c.fillStyle = '#ffffff';
                c.shadowBlur = 4;
                c.shadowColor = '#ffffff';
                c.fill();

                c.restore();
                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 Frame
            function animate(currentTime) {
                requestAnimationFrame(animate);

                let dt = (currentTime - state.lastTime) / 1000;
                if (isNaN(dt) || dt < 0 || dt > 1.0) dt = 0.016;
                state.lastTime = currentTime;

                const width = canvas.width / (window.devicePixelRatio || 1);
                const height = canvas.height / (window.devicePixelRatio || 1);
                const cx = width / 2;
                const cy = height / 2;

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

                // 1. Draw Grid
                ctx.save();
                ctx.strokeStyle = 'rgba(2, 132, 199, 0.06)';
                ctx.lineWidth = 1;
                for (let x = 0; x < width; x += 30) {
                    ctx.beginPath();
                    ctx.moveTo(x, 0);
                    ctx.lineTo(x, height);
                    ctx.stroke();
                }
                for (let y = 0; y < height; y += 30) {
                    ctx.beginPath();
                    ctx.moveTo(0, y);
                    ctx.lineTo(width, y);
                    ctx.stroke();
                }
                ctx.restore();

                // 2. Geometry Scale Calculation
                const phys_r1 = state.d1 / 2;
                const phys_r2 = state.d2 / 2;

                const scaleWidth = (width - 190) / (state.c + phys_r1 + phys_r2);
                const scaleHeight = (height - 120) / Math.max(state.d1, state.d2);
                const scale = Math.min(scaleWidth, scaleHeight);
                
                const txtCanvasScale = document.getElementById('txt-canvas-scale');
                if (txtCanvasScale) {
                    txtCanvasScale.innerText = `스케일 비율: 1:${(1/scale * 100).toFixed(0)}급`;
                }

                const r1 = phys_r1 * scale;
                const r2 = phys_r2 * scale;
                const c_vis = state.c * scale;

                // Position centers to perfectly center the entire bounding box
                const x1 = (width - c_vis + r1 - r2) / 2;
                const y1 = cy;
                const x2 = x1 + c_vis;
                const y2 = cy;

                // Physics Rotation
                const ratio = state.d2 / state.d1;
                const pitchSpeed = (Math.PI * state.d1 * state.rpm) / 60000; // m/s

                if (state.isPlaying) {
                    if (state.rpm > 0) {
                        const omega1 = (2 * Math.PI * state.rpm) / 60; // rad/s
                        
                        // Stroboscopic rendering limit
                        const maxOmega = 6;
                        const renderOmega1 = maxOmega * (1 - Math.exp(-omega1 / maxOmega));
                        
                        state.angle1 += state.direction * renderOmega1 * dt;
                        state.angle2 += state.direction * (renderOmega1 / ratio) * dt;

                        // Linear belt move offset
                        state.beltOffset += state.direction * pitchSpeed * 1000 * scale * dt;
                    }
                }

                // 3. Tangent belt geometry calculations
                const dx = x2 - x1;
                const alpha = Math.asin((r2 - r1) / dx);

                // Tangent angles relative to horizontal
                const theta_top1 = -Math.PI / 2 - alpha;
                const theta_bottom1 = Math.PI / 2 + alpha;
                
                const theta_top2 = -Math.PI / 2 - alpha;
                const theta_bottom2 = Math.PI / 2 + alpha;

                // Tangent Points on Pulley 1
                const tp1_top_x = x1 + r1 * Math.cos(theta_top1);
                const tp1_top_y = y1 + r1 * Math.sin(theta_top1);
                const tp1_bottom_x = x1 + r1 * Math.cos(theta_bottom1);
                const tp1_bottom_y = y1 + r1 * Math.sin(theta_bottom1);

                // Tangent Points on Pulley 2
                const tp2_top_x = x2 + r2 * Math.cos(theta_top2);
                const tp2_top_y = y2 + r2 * Math.sin(theta_top2);
                const tp2_bottom_x = x2 + r2 * Math.cos(theta_bottom2);
                const tp2_bottom_y = y2 + r2 * Math.sin(theta_bottom2);

                // 4. Spawn Friction Sparks at Tangent Points
                if (state.isPlaying) {
                    if (state.rpm > 300) {
                        const sparkEmission = Math.min(Math.floor(state.rpm / 600) + 1, 2);
                        for (let s = 0; s < sparkEmission; s++) {
                            if (Math.random() > 0.6) {
                                // Top tangent left sparks
                                const spVx = -state.direction * (2 + Math.random() * 2);
                                const spVy = (Math.random() - 0.5) * 1.5;
                                state.sparks.push(new BeltSpark(tp1_top_x, tp1_top_y, spVx * scale, spVy * scale));
                            }
                        }
                    }
                }

                // Render Sparks
                state.sparks = state.sparks.filter(s => s.life > 0);
                state.sparks.forEach(s => {
                    s.update();
                    s.draw(ctx);
                });

                // 5. Draw Pulley 1 (Drive - Cyan)
                drawPulley(ctx, x1, y1, r1, state.angle1, '#0284c7');

                // Draw Pulley 2 (Driven - Magenta)
                drawPulley(ctx, x2, y2, r2, state.angle2, '#db2777');

                // 6. Draw Snug Belt Around both pulleys (Moving dashed line)
                ctx.save();
                ctx.beginPath();
                
                // Top tangent line
                ctx.moveTo(tp1_top_x, tp1_top_y);
                ctx.lineTo(tp2_top_x, tp2_top_y);
                
                // Clockwise arc around Pulley 2
                ctx.arc(x2, y2, r2, theta_top2, theta_bottom2, false);
                
                // Bottom tangent line
                ctx.lineTo(tp1_bottom_x, tp1_bottom_y);
                
                // Clockwise arc around Pulley 1
                ctx.arc(x1, y1, r1, theta_bottom1, theta_top1, false);

                ctx.closePath();

                // Belt outer shadow/glow
                ctx.shadowBlur = 12;
                ctx.shadowColor = '#0284c7';
                ctx.strokeStyle = '#0284c7';
                ctx.lineWidth = 7;
                ctx.lineCap = 'round';
                ctx.lineJoin = 'round';
                ctx.stroke();
                ctx.shadowBlur = 0;

                // Moving belt textured interior dashes
                ctx.strokeStyle = '#ffffff';
                ctx.lineWidth = 5;
                ctx.setLineDash([16, 12]);
                ctx.lineDashOffset = -state.beltOffset;
                ctx.stroke();
                ctx.setLineDash([]);
                ctx.restore();

                // 7. Dimension annotations
                ctx.save();
                
                // Draw Diameter dimensions on pulleys with professional CAD leader lines
                drawDiameterLeader(ctx, x1, y1, r1, state.d1.toFixed(1) + ' mm', 'd₁ =', -1);
                drawDiameterLeader(ctx, x2, y2, r2, state.d2.toFixed(1) + ' mm', 'd₂ =', 1);

                // Horizontal Center Distance Dimension Line (C)
                ctx.save();
                ctx.strokeStyle = 'rgba(71, 85, 105, 0.3)';
                ctx.lineWidth = 1;
                
                const dimY = y1 + r2 + 25; // 25px below larger pulley
                
                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();

                // Lap angle highlights (colored arcs representing wrap contact)
                ctx.beginPath();
                ctx.arc(x1, y1, r1 + 10, theta_bottom1, theta_top1, false);
                ctx.strokeStyle = 'rgba(0, 242, 254, 0.35)';
                ctx.lineWidth = 3;
                ctx.stroke();

                ctx.beginPath();
                ctx.arc(x2, y2, r2 + 10, theta_top2, theta_bottom2, false);
                ctx.strokeStyle = 'rgba(255, 0, 127, 0.35)';
                ctx.lineWidth = 3;
                ctx.stroke();
                ctx.restore();
            }

            // Bind Event Listeners
            sliderD1.addEventListener('input', syncD1FromSlider);
            inputD1.addEventListener('change', syncD1FromInput);

            sliderD2.addEventListener('input', syncD2FromSlider);
            inputD2.addEventListener('change', syncD2FromInput);

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

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

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

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

            presetButtons.forEach(btn => {
                btn.addEventListener('click', () => {
                    loadPreset(btn.dataset.preset);
                });
            });

            // Initialize
            loadPreset('reduction');
            requestAnimationFrame(animate);

            // ── 우클릭 & 소스 복사 방지 (전체 문서 레벨 + 알림창 동기화) ──
            document.addEventListener('contextmenu', function(e) {
                e.preventDefault();
                alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                return false;
            }, { capture: true });

            document.addEventListener('selectstart', function(e) {
                e.preventDefault();
                return false;
            }, { capture: true });

            // 개발자도구 단축키 및 복사 단축키 차단 (F12, Ctrl+U, Ctrl+Shift+I/J/C, Ctrl+C, Ctrl+S)
            // 중복 차단 문자를 사용하면 WordPress API에서 오류가 날 수 있으므로 nested if로 구현
            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.key === 'U' || e.key === 'C' || e.key === 'S') {
                        e.preventDefault();
                        alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                        return false;
                    }
                    if (e.shiftKey) {
                        if (['i','I','j','J','c','C'].includes(e.key)) {
                            e.preventDefault();
                            alert("이 콘텐츠는 저작권법의 보호를 받습니다. 무단 복제 및 우클릭을 금지합니다.");
                            return false;
                        }
                    }
                }
            }, { capture: true });
        } // <-- Closed the initBeltSimulator function

        // Robust ReadyState hook-up with polling to guarantee execution even if DOM elements load late
        let initAttempts = 0;
        function tryInit() {
            initAttempts++;
            initBeltSimulator();
            if (!window.__beltpulley_initialized) {
                if (initAttempts < 50) {
                    setTimeout(tryInit, 100);
                }
            }
        }

        if (document.readyState === 'complete' || document.readyState === 'interactive') {
            tryInit();
        } else {
            document.addEventListener('DOMContentLoaded', tryInit);
            window.addEventListener('load', tryInit);
        }
        })();
    </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>풀리 지름 설정: 슬라이더나 입력 칸을 이용해 구동 풀리(D₁)와 피동 풀리(D₂)의 외경 지름을 입력합니다.</strong></li>
<li style="margin-bottom: 6px;">축간거리 및 입력 속도 설정: 두 풀리 축 사이의 중심거리(C)와 구동 풀리의 RPM(N₁)을 설정합니다.</li>
<li style="margin-bottom: 6px;">실시간 벨트 구동 관찰: 회전 속도에 맞춰 벨트라인이 부드럽게 움직이는 2D 물리 시뮬레이션을 관찰합니다.</li>
<li style="margin-bottom: 6px;">출력 계측치 분석: 총 벨트 길이(L), 풀리별 접촉각(Lap Angle), 벨트 선속도(m/s) 및 피동 풀리 속도(RPM)를 계측 모니터에서 즉시 분석합니다.</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. 벨트 전동(Belt Drive)의 기본 원리 및 공학적 특징</h3>
<p>벨트 전동은 두 개 이상의 풀리(Pulley)에 유연한 벨트(Belt)를 감아 마찰력 또는 물림력에 의해 동력을 전달하는 대표적인 유연 전동 장치(Flexible Transmitter)입니다. 기어 전동에 비해 축간 거리가 먼 경우에 경제적으로 동력을 전달할 수 있으며, 충격 흡수 성능이 우수하고 운전이 비교적 조용하다는 큰 장점을 지니고 있습니다.</p><ul><li><strong>평벨트 (Flat Belt):</strong> 고속 운전에 적합하며 굴곡성이 좋으나 슬립(Slip)이 발생하기 쉽습니다.</li><li><strong>V벨트 (V-Belt):</strong> 홈과의 쐐기 작용(Wedge Action)에 의한 마찰력 증대로 비교적 큰 토크를 슬립 없이 전달할 수 있어 산업용 동력 전달에 가장 널리 쓰입니다.</li><li><strong>타이밍 벨트 (Timing Belt/정밀 물림 벨트):</strong> 기어처럼 치형이 형성되어 슬립률 0%의 동기 전동(Synchronous Drive)이 가능합니다.</li></ul>
<h3>2. 벨트 길이 및 접촉각 수학적 유도 공식</h3>
<p>벨트 전동 설계의 기본이 되는 기하학적 치수와 역학적 관계식은 다음과 같이 유도됩니다.</p><p><strong>① 평행 벨트 길이 계산 공식 (Open Belt Length):</strong> 두 풀리의 지름을 <code>D1</code>, <code>D2</code>, 축간거리를 <code>C</code>라고 할 때, 기하학적 궤적을 적분하여 얻어지는 표준 근사식입니다.</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">L &approx; 2C + &pi;(D1 + D2)/2 + (D2 - D1)^2 / (4C) &nbsp;[mm]</p><p><strong>② 접촉각 (Lap Angle / 접촉 호의 각도, &theta;):</strong> 동력 전달 용량을 결정하는 가장 중요한 인자 중 하나로, 구동 풀리에서의 접촉각 <code>&theta;1</code>이 작을수록 슬립 현상이 일어나기 쉽습니다.</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">&theta;1 = &pi; - 2 &times; sin^(-1)((D2 - D1) / 2C) &nbsp;[rad]</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">&theta;2 = &pi; + 2 &times; sin^(-1)((D2 - D1) / 2C) &nbsp;[rad]</p><p><strong>③ 벨트의 주속도 (Belt Pitch Line Speed, v):</strong> 동력이 전달되는 벨트의 선형 주행 속도로, 구동 풀리 기준 공식은 다음과 같습니다:</p><p style="text-align: center; font-weight: bold; background: #e0f2fe; padding: 16px; border-radius: 8px; font-size: 1.05em; color: #0369a1;">v = (&pi; &times; D1 &times; N1) / 60,000 &nbsp;[m/s]</p>
<h3>3. 마찰 전동의 오일러 공식 (슬립 및 장력 설계)</h3>
<p>벨트가 풀리에 감겨 회전할 때, 미끄러지기 직전의 긴장측 장력(Tight Side Tension, <code>T_t</code>)과 이완측 장력(Slack Side Tension, <code>T_s</code>)의 비율은 벨트와 풀리 사이의 마찰 계수(<code>&mu;</code>)와 접촉각(<code>&theta;</code>)에 의해 지수적으로 결정됩니다. 이를 **오일러의 벨트 공식(Euler's Belt Formula)**이라고 합니다.</p><p style="text-align: center; font-weight: bold; background: #f3f4f6; padding: 12px; border-radius: 8px;">T_t / T_s = e^(&mu; &times; &theta;)</p><p>이 식은 접촉각 <code>&theta;</code>가 클수록 장력비가 기하급수적으로 증가하여 더 큰 유효 인장력(<code>F_e = T_t - T_s</code>)을 전달할 수 있음을 입증합니다. 따라서 소형 풀리의 접촉각이 120&deg; 이하로 떨어지는 감속 비율이 큰 설계에서는 아이들러 풀리(Idler Pulley)를 추가 장착하여 이완측 접촉각을 강제로 확보하는 보정 설계가 필수로 작용합니다.</p>

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

]]></content:encoded>
					
					<wfw:commentRss>https://myengnote.com/belt-pulley-speed-calculator-simulator-2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
