<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Widgets on Ted Factory</title><link>https://tedfactory.com/widgets/</link><description>Recent content in Widgets on Ted Factory</description><generator>Hugo</generator><language>ko</language><lastBuildDate>Tue, 21 Apr 2026 13:14:49 +0900</lastBuildDate><atom:link href="https://tedfactory.com/widgets/index.xml" rel="self" type="application/rss+xml"/><item><title>Typing Sky</title><link>https://tedfactory.com/widgets/typing-sky/</link><pubDate>Fri, 06 Mar 2026 00:00:00 +0900</pubDate><guid>https://tedfactory.com/widgets/typing-sky/</guid><description>&lt;h1 id="typing-sky"&gt;Typing Sky&lt;a class="anchor" href="#typing-sky"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;하늘을 가로질러 날아오는 단어들을 타이핑으로 격추하는 게임입니다. 아래에 수록된 유명한 시와 소설의 단어들이 오른쪽에서 왼쪽으로 흘러갑니다. 단어가 왼쪽 끝에 닿기 전에 정확히 입력하면 점수를 얻습니다. 단어의 글자 수만큼 점수가 올라가며, 성공할수록 점점 빨라집니다.&lt;/p&gt;

&lt;style&gt;
 .typing-game-wrap {
 position: relative;
 width: 100%;
 max-width: 900px;
 margin: 24px auto;
 font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
 user-select: none;
 }
 .typing-game-wrap *,
 .typing-game-wrap *::before,
 .typing-game-wrap *::after {
 box-sizing: border-box;
 }

 /* HUD */
 .tg-hud {
 display: flex;
 justify-content: space-between;
 align-items: center;
 padding: 8px 16px;
 background: rgba(30, 40, 70, 0.85);
 border-radius: 12px 12px 0 0;
 color: #e8ecf4;
 font-size: 15px;
 gap: 12px;
 flex-wrap: wrap;
 }
 .tg-hud-item { display: flex; align-items: center; gap: 6px; }
 .tg-hud-label { opacity: 0.7; font-size: 13px; }
 .tg-hud-value { font-weight: 700; font-size: 18px; min-width: 32px; text-align: right; }
 #tg-lives-display .tg-heart { color: #ff4d6a; margin-right: 1px; }

 /* Play area */
 .tg-arena {
 position: relative;
 width: 100%;
 height: 340px;
 background: url('/images/widgets/typing-sky-bg.png') center/cover no-repeat;
 overflow: hidden;
 border-left: 4px solid rgba(255, 70, 90, 0.7);
 }
 .tg-lane-line {
 position: absolute;
 left: 0; right: 0;
 height: 1px;
 background: rgba(255,255,255,0.13);
 pointer-events: none;
 }

 /* Words */
 .tg-word {
 position: absolute;
 padding: 4px 14px;
 border-radius: 8px;
 font-size: 18px;
 font-weight: 600;
 letter-spacing: 0.5px;
 white-space: nowrap;
 transition: opacity 0.15s, transform 0.15s;
 text-shadow: 0 1px 3px rgba(0,0,0,0.35);
 pointer-events: none;
 }
 .tg-word.matched {
 opacity: 0;
 transform: scale(1.5);
 }
 .tg-word.highlight {
 outline: 2px solid #ffe066;
 outline-offset: 2px;
 }

 /* Input area */
 .tg-input-bar {
 display: flex;
 gap: 8px;
 padding: 10px 16px;
 background: rgba(30, 40, 70, 0.85);
 border-radius: 0 0 12px 12px;
 }
 #tg-input {
 flex: 1;
 padding: 10px 16px;
 font-size: 20px;
 font-weight: 600;
 border: 2px solid rgba(255,255,255,0.25);
 border-radius: 8px;
 background: rgba(255,255,255,0.12);
 color: #fff;
 outline: none;
 letter-spacing: 1px;
 }
 #tg-input:focus { border-color: rgba(100,180,255,0.7); }
 #tg-input::placeholder { color: rgba(255,255,255,0.35); }
 #tg-input:disabled {
 opacity: 0.4;
 cursor: not-allowed;
 }

 .tg-btn {
 padding: 10px 24px;
 border: none;
 border-radius: 8px;
 font-size: 15px;
 font-weight: 700;
 cursor: pointer;
 transition: background 0.2s, transform 0.1s;
 }
 .tg-btn:active { transform: scale(0.96); }
 .tg-btn-start {
 background: linear-gradient(135deg, #4facfe, #00f2fe);
 color: #fff;
 }
 .tg-btn-start:hover { background: linear-gradient(135deg, #3a9aee, #00d4e0); }

 /* Overlay */
 .tg-overlay {
 position: absolute;
 inset: 0;
 display: flex;
 flex-direction: column;
 align-items: center;
 justify-content: center;
 background: rgba(15, 20, 40, 0.82);
 z-index: 10;
 text-align: center;
 padding: 24px;
 }
 .tg-overlay h2 {
 color: #fff;
 margin: 0 0 8px;
 font-size: 28px;
 }
 .tg-overlay p {
 color: rgba(255,255,255,0.7);
 margin: 4px 0;
 font-size: 15px;
 line-height: 1.6;
 }
 .tg-overlay .tg-final-score {
 font-size: 48px;
 font-weight: 800;
 color: #ffe066;
 margin: 12px 0;
 }
 .tg-overlay .tg-btn {
 margin-top: 18px;
 }

 /* Combo */
 .tg-combo-popup {
 position: absolute;
 top: 50%;
 left: 50%;
 transform: translate(-50%, -50%) scale(0);
 font-size: 32px;
 font-weight: 800;
 color: #ffe066;
 text-shadow: 0 2px 8px rgba(0,0,0,0.5);
 pointer-events: none;
 opacity: 0;
 z-index: 5;
 }
 .tg-combo-popup.show {
 animation: tgComboAnim 0.7s ease-out forwards;
 }
 @keyframes tgComboAnim {
 0% { opacity: 1; transform: translate(-50%, -50%) scale(0.5); }
 40% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
 100% { opacity: 0; transform: translate(-50%, -70%) scale(1); }
 }

 /* Score popup */
 .tg-score-popup {
 position: absolute;
 font-size: 22px;
 font-weight: 700;
 color: #7fff7f;
 text-shadow: 0 1px 4px rgba(0,0,0,0.5);
 pointer-events: none;
 opacity: 0;
 z-index: 5;
 }
 .tg-score-popup.show {
 animation: tgScoreAnim 0.6s ease-out forwards;
 }
 @keyframes tgScoreAnim {
 0% { opacity: 1; transform: translateY(0); }
 100% { opacity: 0; transform: translateY(-30px); }
 }
&lt;/style&gt;

&lt;div class="typing-game-wrap" id="typingGame"&gt;
 &lt;!-- HUD --&gt;
 &lt;div class="tg-hud"&gt;
 &lt;div class="tg-hud-item"&gt;
 &lt;span class="tg-hud-label"&gt;SCORE&lt;/span&gt;
 &lt;span class="tg-hud-value" id="tg-score"&gt;0&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="tg-hud-item"&gt;
 &lt;span class="tg-hud-label"&gt;COMBO&lt;/span&gt;
 &lt;span class="tg-hud-value" id="tg-combo"&gt;0&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="tg-hud-item"&gt;
 &lt;span class="tg-hud-label"&gt;SPEED&lt;/span&gt;
 &lt;span class="tg-hud-value" id="tg-speed"&gt;1.0x&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="tg-hud-item" id="tg-lives-display"&gt;
 &lt;span class="tg-hud-label"&gt;LIVES&lt;/span&gt;
 &lt;span class="tg-hud-value" id="tg-lives"&gt;&lt;/span&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;!-- Arena --&gt;
 &lt;div class="tg-arena" id="tg-arena"&gt;
 &lt;div class="tg-overlay" id="tg-overlay-start"&gt;
 &lt;h2&gt;Typing Sky&lt;/h2&gt;
 &lt;p&gt;하늘을 가로지르는 단어를 타이핑하세요!&lt;/p&gt;</description></item><item><title>3D Tetris Box</title><link>https://tedfactory.com/widgets/3d-tetris-box/</link><pubDate>Sat, 07 Mar 2026 00:00:00 +0900</pubDate><guid>https://tedfactory.com/widgets/3d-tetris-box/</guid><description>&lt;h1 id="3d-tetris-box"&gt;3D Tetris Box&lt;a class="anchor" href="#3d-tetris-box"&gt;#&lt;/a&gt;&lt;/h1&gt;

&lt;style&gt;
 .tt3-wrap {
 position: relative;
 width: 100%;
 max-width: 1100px;
 margin: 28px auto;
 font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
 color: #10131b;
 }

 .tt3-wrap *,
 .tt3-wrap *::before,
 .tt3-wrap *::after {
 box-sizing: border-box;
 }

 .tt3-hero {
 display: grid;
 grid-template-columns: minmax(0, 1.2fr) minmax(260px, 0.8fr);
 align-items: stretch;
 gap: 12px;
 margin: 0 0 14px;
 padding: 12px;
 border: 1px solid rgba(43, 52, 69, 0.12);
 border-radius: 18px;
 background: linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(238, 243, 250, 0.86));
 }

 .tt3-hero-copy {
 display: flex;
 flex-direction: column;
 justify-content: center;
 gap: 10px;
 padding: 8px 6px;
 }

 .tt3-hero-text {
 margin: 0;
 font-size: 14px;
 line-height: 1.65;
 color: #38465e;
 }

 .tt3-hero-text-mobile {
 display: none;
 }

 .tt3-hero-image-wrap {
 border-radius: 12px;
 overflow: hidden;
 border: 1px solid rgba(43, 52, 69, 0.12);
 background: rgba(230, 236, 246, 0.7);
 max-height: 180px;
 }

 .tt3-hero-image {
 display: block;
 width: 100%;
 height: 100%;
 object-fit: cover;
 }

 .tt3-shell {
 background: linear-gradient(180deg, #f5f7fb 0%, #e9edf5 100%);
 border: 1px solid rgba(43, 52, 69, 0.12);
 border-radius: 22px;
 box-shadow:
 0 24px 60px rgba(28, 36, 51, 0.18),
 inset 0 1px 0 rgba(255, 255, 255, 0.8);
 overflow: hidden;
 }

 .tt3-hud {
 display: grid;
 grid-template-columns: repeat(4, minmax(0, 1fr));
 gap: 12px;
 padding: 16px 18px;
 background:
 linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(239, 243, 250, 0.92));
 border-bottom: 1px solid rgba(43, 52, 69, 0.1);
 }

 .tt3-card {
 padding: 12px 14px;
 border-radius: 14px;
 background: rgba(255, 255, 255, 0.74);
 border: 1px solid rgba(43, 52, 69, 0.1);
 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7);
 }

 .tt3-label {
 display: block;
 font-size: 12px;
 letter-spacing: 0.08em;
 color: #697589;
 margin-bottom: 6px;
 }

 .tt3-value {
 font-size: 28px;
 font-weight: 800;
 line-height: 1;
 color: #10131b;
 }

 .tt3-layout {
 display: grid;
 grid-template-columns: minmax(0, 720px) 270px;
 justify-content: center;
 align-items: stretch;
 gap: 18px;
 padding: 18px;
 }

 .tt3-stage {
 position: relative;
 width: 100%;
 align-self: start;
 min-width: 0;
 border-radius: 18px;
 overflow: hidden;
 background: linear-gradient(180deg, #dfe4ee 0%, #cfd7e5 100%);
 border: 1px solid rgba(43, 52, 69, 0.12);
 box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.34);
 }

 .tt3-canvas {
 display: block;
 width: 100%;
 aspect-ratio: 1 / 1;
 cursor: pointer;
 }

 .tt3-overlay {
 position: absolute;
 inset: 0;
 display: flex;
 align-items: center;
 justify-content: center;
 padding: 24px;
 background: rgba(18, 23, 34, 0.34);
 backdrop-filter: blur(3px);
 }

 .tt3-panel {
 width: min(100%, 460px);
 padding: 24px 26px;
 border-radius: 20px;
 background: rgba(252, 253, 255, 0.92);
 border: 1px solid rgba(43, 52, 69, 0.12);
 box-shadow: 0 26px 60px rgba(7, 12, 22, 0.28);
 text-align: center;
 }

 .tt3-panel h2 {
 margin: 0 0 10px;
 font-size: 30px;
 color: #10131b;
 }

 .tt3-panel p {
 margin: 8px 0;
 color: #4a5568;
 line-height: 1.6;
 font-size: 15px;
 }

 .tt3-btn-row {
 display: flex;
 justify-content: center;
 gap: 10px;
 margin-top: 18px;
 flex-wrap: wrap;
 }

 .tt3-btn {
 border: none;
 border-radius: 999px;
 padding: 12px 20px;
 font-size: 14px;
 font-weight: 800;
 cursor: pointer;
 transition: transform 0.12s ease, box-shadow 0.18s ease, background 0.18s ease;
 }

 .tt3-btn:hover {
 transform: translateY(-1px);
 }

 .tt3-btn:active {
 transform: translateY(0);
 }

 .tt3-btn-primary {
 color: #fff;
 background: linear-gradient(135deg, #4f46e5, #06b6d4);
 box-shadow: 0 12px 24px rgba(79, 70, 229, 0.28);
 }

 .tt3-btn-secondary {
 color: #1d2738;
 background: rgba(230, 235, 244, 0.95);
 box-shadow: 0 10px 20px rgba(43, 52, 69, 0.1);
 }

 .tt3-flash {
 position: absolute;
 left: 50%;
 top: 10%;
 transform: translateX(-50%);
 padding: 10px 18px;
 border-radius: 999px;
 background: rgba(16, 19, 27, 0.84);
 color: #fff;
 font-size: 14px;
 font-weight: 800;
 letter-spacing: 0.04em;
 pointer-events: none;
 opacity: 0;
 transition: opacity 0.2s ease, transform 0.2s ease;
 }

 .tt3-flash.show {
 opacity: 1;
 transform: translateX(-50%) translateY(10px);
 }

 .tt3-side {
 align-self: stretch;
 display: grid;
 grid-template-rows: minmax(165px, 0.72fr) minmax(320px, 1.28fr);
 gap: 14px;
 min-height: 0;
 }

 .tt3-side-card {
 display: flex;
 flex-direction: column;
 padding: 16px;
 border-radius: 16px;
 background: rgba(255, 255, 255, 0.8);
 border: 1px solid rgba(43, 52, 69, 0.1);
 min-height: 0;
 }

 .tt3-side-card h3 {
 margin: 0 0 12px;
 font-size: 15px;
 color: #10131b;
 }

 .tt3-preview {
 display: block;
 width: 100%;
 height: 100%;
 flex: 1 1 auto;
 min-height: 140px;
 border-radius: 14px;
 background: linear-gradient(180deg, #eff3f9 0%, #dde4f0 100%);
 border: 1px solid rgba(43, 52, 69, 0.08);
 }

 .tt3-controls {
 display: grid;
 grid-template-columns: repeat(2, minmax(0, 1fr));
 gap: 10px;
 align-content: start;
 }

 .tt3-control-group {
 display: flex;
 flex-direction: column;
 gap: 10px;
 }

 .tt3-key {
 display: flex;
 align-items: center;
 justify-content: space-between;
 gap: 10px;
 padding: 10px 12px;
 border-radius: 12px;
 background: rgba(241, 244, 250, 0.94);
 color: #273244;
 font-size: 13px;
 }

 .tt3-key code {
 min-width: 28px;
 padding: 4px 8px;
 border-radius: 8px;
 background: #10131b;
 color: #fff;
 text-align: center;
 font-weight: 700;
 }

 .tt3-note {
 margin-top: auto;
 padding-top: 12px;
 font-size: 13px;
 color: #586579;
 line-height: 1.6;
 }

 .tt3-note-mobile {
 display: none;
 }

 .tt3-mobile-controls {
 display: none;
 }

 .tt3-mobile-column {
 display: flex;
 flex-direction: column;
 gap: 10px;
 }

 .tt3-mobile-rotate {
 display: grid;
 grid-template-columns: 1fr;
 gap: 10px;
 }

 .tt3-mobile-move {
 display: grid;
 grid-template-columns: repeat(3, minmax(0, 1fr));
 gap: 10px;
 align-items: center;
 }

 .tt3-mobile-spacer {
 min-height: 44px;
 }

 .tt3-mobile-btn {
 display: flex;
 align-items: center;
 justify-content: center;
 min-height: 52px;
 border: none;
 border-radius: 14px;
 background: linear-gradient(180deg, #f4f7fb 0%, #e8edf6 100%);
 color: #111827;
 font-size: 20px;
 font-weight: 800;
 box-shadow:
 inset 0 1px 0 rgba(255, 255, 255, 0.85),
 0 8px 18px rgba(43, 52, 69, 0.08);
 cursor: pointer;
 touch-action: manipulation;
 }

 .tt3-mobile-btn:active {
 transform: translateY(1px) scale(0.98);
 }

 .tt3-mobile-btn-rotate {
 font-size: 18px;
 letter-spacing: 0.04em;
 }

 .tt3-next-card {
 gap: 12px;
 }

 .tt3-next-title-mobile {
 display: none;
 }

 .tt3-footer {
 padding: 0 18px 18px;
 color: #586579;
 font-size: 14px;
 line-height: 1.7;
 }

 .tt3-footer-desktop {
 display: none;
 }

 .tt3-footer-mobile {
 display: none;
 }

 .tt3-footer p {
 margin: 0;
 }

 .tt3-footer p + p {
 margin-top: 10px;
 }

 @media (max-width: 980px) {
 .tt3-hud {
 grid-template-columns: repeat(3, minmax(0, 1fr));
 }

 .tt3-layout {
 grid-template-columns: 1fr;
 }

 .tt3-side {
 grid-template-rows: auto auto;
 }
 }

 @media (max-width: 720px) {
 .tt3-hero {
 grid-template-columns: 1fr;
 margin-bottom: 10px;
 padding: 10px;
 gap: 10px;
 }

 .tt3-hero-text-desktop {
 display: none;
 }

 .tt3-hero-text-mobile {
 display: block;
 }

 .tt3-hero-image-wrap {
 max-height: 120px;
 }

 .tt3-hud {
 grid-template-columns: repeat(2, minmax(0, 1fr));
 }

 .tt3-canvas {
 aspect-ratio: 5 / 4;
 }

 .tt3-layout,
 .tt3-hud,
 .tt3-footer {
 padding-left: 14px;
 padding-right: 14px;
 }

 .tt3-layout {
 padding-top: 14px;
 padding-bottom: 14px;
 }

 .tt3-panel {
 padding: 20px 18px;
 }

 .tt3-footer-desktop {
 display: none;
 }

 .tt3-footer-mobile {
 display: block;
 }

 .tt3-side {
 gap: 12px;
 }

 .tt3-preview {
 height: 98px;
 min-height: 98px;
 flex: none;
 }

 .tt3-next-card {
 flex-direction: row;
 align-items: stretch;
 gap: 12px;
 }

 .tt3-next-card h3 {
 flex: 0 0 84px;
 margin: 0;
 display: flex;
 align-items: center;
 font-size: 17px;
 font-weight: 800;
 line-height: 1.05;
 }

 .tt3-next-title-desktop {
 display: none;
 }

 .tt3-next-title-mobile {
 display: inline;
 }

 .tt3-next-card .tt3-preview {
 width: auto;
 flex: 1 1 auto;
 }

 .tt3-control-card h3,
 .tt3-control-card .tt3-controls,
 .tt3-control-card .tt3-note-desktop {
 display: none;
 }

 .tt3-control-card {
 padding: 14px;
 }

 .tt3-mobile-controls {
 display: grid;
 grid-template-columns: minmax(0, 1fr) minmax(0, 1.45fr);
 gap: 12px;
 align-items: stretch;
 }

 .tt3-note-mobile {
 display: block;
 margin-top: 12px;
 padding-top: 0;
 }
 }
&lt;/style&gt;

&lt;div class="tt3-wrap" id="tt3-game"&gt;
 &lt;div class="tt3-hero"&gt;
 &lt;div class="tt3-hero-copy"&gt;
 &lt;p class="tt3-hero-text tt3-hero-text-desktop"&gt;눈앞의 &lt;code&gt;6 x 6 x 15&lt;/code&gt; 상자 안으로 3차원 블록이 천천히 밀려 들어갑니다. 데스크톱에서는 &lt;code&gt;I / J / K / L&lt;/code&gt;로 평면 이동, &lt;code&gt;F / D / S&lt;/code&gt;로 회전, &lt;code&gt;A&lt;/code&gt;로 빠른 내리기를 사용합니다.&lt;/p&gt;</description></item><item><title>세븐 포커 시뮬레이터</title><link>https://tedfactory.com/widgets/seven-poker/</link><pubDate>Mon, 09 Mar 2026 00:00:00 +0900</pubDate><guid>https://tedfactory.com/widgets/seven-poker/</guid><description>&lt;h1 id="세븐-포커-시뮬레이터"&gt;세븐 포커 시뮬레이터&lt;a class="anchor" href="#%ec%84%b8%eb%b8%90-%ed%8f%ac%ec%bb%a4-%ec%8b%9c%eb%ae%ac%eb%a0%88%ec%9d%b4%ed%84%b0"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;세븐 포커(Seven-Card Stud)는 각 플레이어에게 7장의 카드가 순차적으로 배분되며, 그 중 최적의 5장 조합으로 족보를 만들어 승부하는 카드 게임입니다. 일부 카드는 뒤집어진 채로, 일부는 공개된 채로 진행되기 때문에 상대의 패를 추론하며 베팅하는 심리전이 핵심입니다.&lt;/p&gt;
&lt;p&gt;친한 친구들과 가끔 모여 세븐 포커를 즐기는데, 매번 털리기만 해서는 안 되겠다 싶어 혼자서도 훈련(?)할 수 있는 시뮬레이터를 만들어 봤습니다. 5명의 AI 플레이어가 각자 다른 스타일로 플레이하니, 실전 감각을 익히는 데 도움이 되길 바랍니다.&lt;/p&gt;</description></item><item><title>암산 트랙</title><link>https://tedfactory.com/widgets/math-track/</link><pubDate>Tue, 21 Apr 2026 00:00:00 +0900</pubDate><guid>https://tedfactory.com/widgets/math-track/</guid><description>&lt;h1 id="암산-트랙"&gt;암산 트랙&lt;a class="anchor" href="#%ec%95%94%ec%82%b0-%ed%8a%b8%eb%9e%99"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://tedfactory.com/images/widgets/math-track-cover.png" alt="사각 트랙 보드 위에 다양한 연산 칸과 빨간 폰, 흰색 주사위가 놓인 암산 트랙 게임 커버 이미지" /&gt;&lt;/p&gt;
&lt;p&gt;주사위를 굴려 사각 트랙 위의 연산 칸들을 돌며 암산을 누적하는 싱글플레이 게임입니다. &lt;strong&gt;도착한 칸의 연산만&lt;/strong&gt; 현재 값에 적용되고, 말이 출발지로 돌아왔을 때 최종 값을 입력합니다. 정답을 맞히면 다음 단계(Level)로 올라가며, 단계가 올라갈수록 연산 종류와 피연산자 범위가 넓어집니다.&lt;/p&gt;

&lt;style&gt;
 .mt-wrap {
 max-width: 640px;
 margin: 24px auto;
 font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
 color: #e8ecf4;
 user-select: none;
 }
 .mt-wrap *, .mt-wrap *::before, .mt-wrap *::after { box-sizing: border-box; }

 /* HUD */
 .mt-hud {
 display: flex;
 justify-content: space-around;
 align-items: center;
 padding: 10px 16px;
 background: rgba(30, 40, 70, 0.85);
 border-radius: 12px 12px 0 0;
 gap: 10px;
 flex-wrap: wrap;
 }
 .mt-hud-item { display: flex; align-items: center; gap: 6px; }
 .mt-hud-label { opacity: 0.7; font-size: 12px; letter-spacing: 1px; }
 .mt-hud-value { font-weight: 700; font-size: 18px; min-width: 28px; text-align: center; }

 /* Board */
 .mt-board-wrap {
 position: relative;
 background: rgba(20, 28, 52, 0.9);
 padding: 18px;
 }
 .mt-board {
 position: relative;
 width: 100%;
 aspect-ratio: 1 / 1;
 background: rgba(15, 22, 42, 0.8);
 border: 2px solid rgba(120, 160, 220, 0.3);
 border-radius: 8px;
 overflow: hidden;
 }
 .mt-lines {
 position: absolute;
 inset: 0;
 width: 100%;
 height: 100%;
 pointer-events: none;
 }
 .mt-line {
 stroke: rgba(120, 160, 220, 0.28);
 stroke-width: 0.35;
 fill: none;
 }
 .mt-line.diag {
 stroke: rgba(255, 220, 100, 0.22);
 stroke-width: 0.3;
 stroke-dasharray: 1.2 1.4;
 }

 .mt-cell {
 position: absolute;
 width: 13%;
 height: 13%;
 transform: translate(-50%, -50%);
 border-radius: 10px;
 background: rgba(50, 70, 110, 0.9);
 border: 2px solid rgba(120, 160, 220, 0.4);
 display: flex;
 align-items: center;
 justify-content: center;
 font-weight: 700;
 font-size: clamp(10px, 2.1vw, 15px);
 color: #eef3ff;
 transition: background 0.2s, box-shadow 0.2s, filter 0.2s;
 text-align: center;
 line-height: 1.1;
 letter-spacing: 0.3px;
 }
 .mt-cell.corner {
 background: rgba(80, 100, 150, 0.95);
 border-color: rgba(255, 220, 100, 0.55);
 width: 15%;
 height: 15%;
 }
 .mt-cell.start {
 background: linear-gradient(135deg, #3a7cc9, #5fa3d9);
 border-color: #9ad2ff;
 color: #fff;
 font-size: clamp(10px, 2.0vw, 13px);
 }
 .mt-cell.center {
 background: rgba(100, 120, 180, 0.95);
 border-color: rgba(255, 220, 100, 0.55);
 width: 15%;
 height: 15%;
 }
 .mt-cell.diag {
 background: rgba(65, 85, 130, 0.9);
 border-style: dashed;
 border-color: rgba(255, 220, 100, 0.4);
 }
 .mt-cell.visited {
 opacity: 0.55;
 filter: grayscale(0.25);
 }
 .mt-cell.current {
 box-shadow: 0 0 0 3px #ffe066, 0 0 18px rgba(255,224,102,0.65);
 z-index: 3;
 }
 .mt-cell.just-applied {
 animation: mtPulse 0.55s ease-out;
 }
 @keyframes mtPulse {
 0% { transform: translate(-50%, -50%) scale(1); }
 40% { transform: translate(-50%, -50%) scale(1.22); background: rgba(255, 224, 102, 0.85); color: #111; }
 100% { transform: translate(-50%, -50%) scale(1); }
 }

 /* Pawn — placed at the top-right corner of a cell so the centered
 operation text below remains fully visible. */
 .mt-pawn {
 position: absolute;
 width: 5%;
 height: 5%;
 border-radius: 50%;
 background: radial-gradient(circle at 30% 30%, #ffb3bb, #d13b4e 70%, #7a1d2c);
 border: 2px solid #fff;
 box-shadow: 0 2px 6px rgba(0,0,0,0.55), inset 0 0 6px rgba(255,255,255,0.35);
 transform: translate(-50%, -50%);
 transition: left 0.22s ease-in-out, top 0.22s ease-in-out;
 z-index: 10;
 pointer-events: none;
 }

 /* Controls */
 .mt-controls {
 display: flex;
 gap: 10px;
 padding: 12px 16px;
 background: rgba(30, 40, 70, 0.85);
 flex-wrap: wrap;
 align-items: center;
 }
 .mt-btn {
 padding: 10px 18px;
 border: none;
 border-radius: 8px;
 font-size: 14px;
 font-weight: 700;
 cursor: pointer;
 transition: background 0.15s, transform 0.1s, opacity 0.15s, filter 0.15s;
 font-family: inherit;
 }
 .mt-btn:active:not(:disabled) { transform: scale(0.97); }
 .mt-btn:disabled { opacity: 0.45; cursor: not-allowed; }
 .mt-btn-primary {
 background: linear-gradient(135deg, #4facfe, #00d2d6);
 color: #fff;
 flex: 1;
 min-width: 160px;
 }
 .mt-btn-primary:hover:not(:disabled) { filter: brightness(1.08); }
 .mt-btn-ghost {
 background: rgba(255,255,255,0.12);
 color: #e8ecf4;
 }
 .mt-btn-ghost:hover:not(:disabled) { background: rgba(255,255,255,0.22); }

 /* Dice (graphical SVG) */
 .mt-dice {
 width: 60px;
 height: 60px;
 flex: 0 0 60px;
 border-radius: 12px;
 cursor: pointer;
 background: transparent;
 border: none;
 padding: 0;
 filter: drop-shadow(0 3px 8px rgba(0,0,0,0.5));
 transition: transform 0.15s, filter 0.15s;
 }
 .mt-dice:hover:not(:disabled) { filter: drop-shadow(0 4px 12px rgba(255,224,102,0.5)); }
 .mt-dice:active:not(:disabled) { transform: scale(0.94); }
 .mt-dice:disabled { cursor: not-allowed; opacity: 0.85; }
 .mt-dice svg { width: 100%; height: 100%; display: block; }
 .mt-dice-bg {
 fill: url(#mtDiceGrad);
 stroke: #b8c5dc;
 stroke-width: 1.6;
 }
 .mt-dice-pip { fill: #1a1f2e; }
 .mt-dice-q {
 fill: #6b7a95;
 font-size: 34px;
 font-weight: 700;
 font-family: 'Segoe UI', system-ui, sans-serif;
 }
 .mt-dice.rolling {
 animation: mtDiceRoll 0.45s linear infinite;
 }
 .mt-dice.settle {
 animation: mtDiceSettle 0.32s ease-out;
 }
 @keyframes mtDiceRoll {
 0% { transform: rotate(0deg) scale(1); }
 50% { transform: rotate(180deg) scale(1.06); }
 100% { transform: rotate(360deg) scale(1); }
 }
 @keyframes mtDiceSettle {
 0% { transform: scale(1.18); }
 60% { transform: scale(0.95); }
 100% { transform: scale(1); }
 }

 /* Path log */
 .mt-path-log {
 background: rgba(30, 40, 70, 0.85);
 padding: 0;
 border-top: 1px solid rgba(255,255,255,0.06);
 }
 .mt-log-toggle {
 display: flex;
 width: 100%;
 align-items: center;
 gap: 8px;
 padding: 9px 16px;
 background: transparent;
 border: none;
 cursor: pointer;
 color: inherit;
 font-family: inherit;
 font-size: 11px;
 letter-spacing: 2px;
 opacity: 0.7;
 transition: opacity 0.15s, background 0.15s;
 }
 .mt-log-toggle:hover { opacity: 1; background: rgba(255,255,255,0.04); }
 .mt-log-arrow {
 display: inline-block;
 font-size: 9px;
 transition: transform 0.2s;
 width: 10px;
 text-align: center;
 }
 .mt-path-log:not(.collapsed) .mt-log-arrow { transform: rotate(90deg); }
 .mt-log-count {
 margin-left: auto;
 font-size: 11px;
 opacity: 0.6;
 letter-spacing: 0.5px;
 font-variant-numeric: tabular-nums;
 }
 .mt-log-items-wrap {
 max-height: 128px;
 overflow-y: auto;
 padding: 0 16px 12px;
 }
 .mt-path-log.collapsed .mt-log-items-wrap { display: none; }
 .mt-log-items {
 display: flex;
 flex-wrap: wrap;
 gap: 6px;
 align-items: center;
 font-size: 14px;
 font-family: 'Consolas', 'Menlo', monospace;
 }
 .mt-log-item {
 padding: 2px 8px;
 background: rgba(255,255,255,0.08);
 border-radius: 4px;
 white-space: nowrap;
 }
 .mt-log-item.start-tag { background: rgba(100, 170, 255, 0.25); color: #9ad2ff; }
 .mt-log-item.op-plus { color: #7fff7f; }
 .mt-log-item.op-minus { color: #ff9a9a; }
 .mt-log-item.op-times { color: #ffd27f; }
 .mt-log-item.op-div { color: #b79cff; }
 .mt-log-item.shortcut { background: rgba(255, 220, 100, 0.18); color: #ffe066; }

 /* Input panel */
 .mt-input-panel {
 background: rgba(30, 40, 70, 0.95);
 padding: 16px;
 border-radius: 0 0 12px 12px;
 text-align: center;
 border-top: 1px solid rgba(255,255,255,0.08);
 }
 .mt-input-label {
 font-size: 14px;
 opacity: 0.9;
 margin-bottom: 10px;
 }
 .mt-input-row {
 display: flex;
 gap: 8px;
 justify-content: center;
 align-items: center;
 flex-wrap: wrap;
 }
 .mt-input-row input {
 width: 160px;
 padding: 10px 14px;
 font-size: 20px;
 font-weight: 700;
 border: 2px solid rgba(255,255,255,0.25);
 border-radius: 8px;
 background: rgba(255,255,255,0.1);
 color: #fff;
 text-align: center;
 outline: none;
 font-family: 'Consolas', 'Menlo', monospace;
 }
 .mt-input-row input:focus { border-color: rgba(100,200,255,0.7); }
 .mt-result {
 margin-top: 10px;
 font-size: 15px;
 font-weight: 700;
 min-height: 22px;
 }
 .mt-result.correct { color: #7fff7f; }
 .mt-result.wrong { color: #ff9a9a; }

 /* Toast */
 .mt-toast {
 position: absolute;
 top: 10px;
 left: 50%;
 transform: translateX(-50%);
 background: rgba(0,0,0,0.78);
 color: #ffe066;
 padding: 6px 14px;
 border-radius: 20px;
 font-size: 13px;
 font-weight: 700;
 pointer-events: none;
 opacity: 0;
 transition: opacity 0.3s;
 z-index: 20;
 white-space: nowrap;
 }
 .mt-toast.show { opacity: 1; }

 /* Rounded bottom when input panel hidden */
 .mt-wrap.no-input .mt-path-log { border-radius: 0 0 12px 12px; }

 @media (max-width: 540px) {
 .mt-hud-value { font-size: 16px; }
 .mt-cell { font-size: clamp(9px, 2.6vw, 13px); }
 .mt-btn { padding: 9px 12px; font-size: 13px; }
 .mt-btn-primary { min-width: 120px; }
 .mt-input-row input { width: 120px; font-size: 18px; }
 }
&lt;/style&gt;

&lt;div class="mt-wrap no-input" id="mathTrack"&gt;
 &lt;div class="mt-hud"&gt;
 &lt;div class="mt-hud-item"&gt;
 &lt;span class="mt-hud-label"&gt;LV&lt;/span&gt;
 &lt;span class="mt-hud-value" id="mt-level"&gt;1&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="mt-hud-item"&gt;
 &lt;span class="mt-hud-label"&gt;ROLLS&lt;/span&gt;
 &lt;span class="mt-hud-value" id="mt-rolls"&gt;0&lt;/span&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="mt-board-wrap"&gt;
 &lt;div class="mt-board" id="mt-board"&gt;
 &lt;svg class="mt-lines" viewBox="0 0 100 100" preserveAspectRatio="none" aria-hidden="true"&gt;
 &lt;line class="mt-line" x1="92" y1="92" x2="92" y2="8"&gt;&lt;/line&gt;
 &lt;line class="mt-line" x1="92" y1="8" x2="8" y2="8"&gt;&lt;/line&gt;
 &lt;line class="mt-line" x1="8" y1="8" x2="8" y2="92"&gt;&lt;/line&gt;
 &lt;line class="mt-line" x1="8" y1="92" x2="92" y2="92"&gt;&lt;/line&gt;
 &lt;line class="mt-line diag" x1="92" y1="8" x2="8" y2="92"&gt;&lt;/line&gt;
 &lt;line class="mt-line diag" x1="8" y1="8" x2="92" y2="92"&gt;&lt;/line&gt;
 &lt;/svg&gt;
 &lt;div class="mt-pawn" id="mt-pawn"&gt;&lt;/div&gt;
 &lt;div class="mt-toast" id="mt-toast"&gt;&lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="mt-controls"&gt;
 &lt;button type="button" class="mt-dice" id="mt-dice" aria-label="주사위"&gt;
 &lt;svg viewBox="0 0 60 60" preserveAspectRatio="xMidYMid meet" aria-hidden="true"&gt;
 &lt;defs&gt;
 &lt;linearGradient id="mtDiceGrad" x1="0" y1="0" x2="0" y2="1"&gt;
 &lt;stop offset="0%" stop-color="#ffffff"/&gt;
 &lt;stop offset="100%" stop-color="#dde6f3"/&gt;
 &lt;/linearGradient&gt;
 &lt;/defs&gt;
 &lt;rect class="mt-dice-bg" x="3" y="3" width="54" height="54" rx="11"&gt;&lt;/rect&gt;
 &lt;g id="mt-dice-pips"&gt;&lt;/g&gt;
 &lt;/svg&gt;
 &lt;/button&gt;
 &lt;button class="mt-btn mt-btn-primary" id="mt-roll-btn"&gt;주사위 굴리기&lt;/button&gt;
 &lt;button class="mt-btn mt-btn-ghost" id="mt-new-btn"&gt;새 보드&lt;/button&gt;
 &lt;/div&gt;

 &lt;div class="mt-path-log collapsed" id="mt-log-root"&gt;
 &lt;button type="button" class="mt-log-toggle" id="mt-log-toggle" aria-expanded="false" aria-controls="mt-log-items-wrap"&gt;
 &lt;span class="mt-log-arrow"&gt;▶&lt;/span&gt;
 &lt;span class="mt-log-label"&gt;PATH LOG&lt;/span&gt;
 &lt;span class="mt-log-count" id="mt-log-count"&gt;0&lt;/span&gt;
 &lt;/button&gt;
 &lt;div class="mt-log-items-wrap" id="mt-log-items-wrap"&gt;
 &lt;div class="mt-log-items" id="mt-log-items"&gt;&lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="mt-input-panel" id="mt-input-panel" style="display:none;"&gt;
 &lt;div class="mt-input-label"&gt;출발지로 돌아왔습니다. 최종 값을 입력하세요.&lt;/div&gt;
 &lt;div class="mt-input-row"&gt;
 &lt;input type="number" id="mt-answer" placeholder="정수" autocomplete="off" inputmode="numeric" /&gt;
 &lt;button class="mt-btn mt-btn-primary" id="mt-submit-btn" style="flex:0 0 auto; min-width:80px;"&gt;제출&lt;/button&gt;
 &lt;/div&gt;
 &lt;div class="mt-result" id="mt-result"&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
(function() {
 const COORDS = {
 0: [92, 92],
 1: [92, 75.2], 2: [92, 58.4], 3: [92, 41.6], 4: [92, 24.8],
 5: [92, 8],
 6: [75.2, 8], 7: [58.4, 8], 8: [41.6, 8], 9: [24.8, 8],
 10: [8, 8],
 11: [8, 24.8], 12: [8, 41.6], 13: [8, 58.4], 14: [8, 75.2],
 15: [8, 92],
 16: [24.8, 92], 17: [41.6, 92], 18: [58.4, 92], 19: [75.2, 92],
 20: [78, 22], 21: [64, 36],
 22: [50, 50],
 23: [64, 64], 24: [78, 78],
 25: [22, 22], 26: [36, 36],
 27: [36, 64], 28: [22, 78]
 };

 const N_CELLS = 29;
 const START = 0, CORNER_A = 5, CORNER_B = 10, CORNER_C = 15, CENTER = 22;
 const CORNERS = new Set([CORNER_A, CORNER_B, CORNER_C]);
 const DIAG_CELLS = new Set([20, 21, 23, 24, 25, 26, 27, 28]);

 function levelConfig(lv) {
 if (lv &lt;= 1) return { ops: ['+', '-'], addMax: 9 };
 if (lv === 2) return { ops: ['+', '-'], addMax: 20 };
 if (lv === 3) return { ops: ['+', '-', '+', '-', '×'], addMax: 20, mulMax: 5 };
 if (lv === 4) return { ops: ['+', '-', '+', '-', '×', '÷'], addMax: 30, mulMax: 9, divMax: 5 };
 return { ops: ['+', '-', '+', '-', '×', '÷'], addMax: 50, mulMax: 12, divMax: 9 };
 }

 function randInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }

 function genOp(cfg) {
 const t = cfg.ops[randInt(0, cfg.ops.length - 1)];
 let n;
 if (t === '+' || t === '-') n = randInt(1, cfg.addMax);
 else if (t === '×') n = randInt(2, cfg.mulMax);
 else n = randInt(2, cfg.divMax);
 return { type: t, n: n };
 }

 function applyOp(v, op) {
 switch (op.type) {
 case '+': return v + op.n;
 case '-': return v - op.n;
 case '×': return v * op.n;
 case '÷': return Math.trunc(v / op.n);
 }
 return v;
 }

 function opText(op) { return op.type + op.n; }
 function opClass(op) {
 return { '+': 'op-plus', '-': 'op-minus', '×': 'op-times', '÷': 'op-div' }[op.type];
 }

 const SVG_NS = 'http://www.w3.org/2000/svg';
 const PIP_POS = {
 tl: [16, 16], tr: [44, 16],
 ml: [16, 30], mr: [44, 30],
 c: [30, 30],
 bl: [16, 44], br: [44, 44]
 };
 const PIP_SETS = {
 1: ['c'],
 2: ['tl', 'br'],
 3: ['tl', 'c', 'br'],
 4: ['tl', 'tr', 'bl', 'br'],
 5: ['tl', 'tr', 'c', 'bl', 'br'],
 6: ['tl', 'tr', 'ml', 'mr', 'bl', 'br']
 };

 let level = 1;
 let pos = START;
 let value = 0;
 let rolls = 0;
 let shortcutPending = null; // 'A' | 'B' | null
 let cells = [];
 let phase = 'idle'; // 'idle' | 'rolling' | 'moving' | 'input'
 let pathLog = [];

 let wrap, board, pawn, rollBtn, newBtn;
 let levelEl, diceEl, dicePipsEl, rollsEl, logEl, logScroll, logRoot, logToggleBtn, logCountEl;
 let inputPanel, answerInput, submitBtn, resultEl, toastEl;

 function init() {
 wrap = document.getElementById('mathTrack');
 board = document.getElementById('mt-board');
 pawn = document.getElementById('mt-pawn');
 rollBtn = document.getElementById('mt-roll-btn');
 newBtn = document.getElementById('mt-new-btn');
 levelEl = document.getElementById('mt-level');
 diceEl = document.getElementById('mt-dice');
 dicePipsEl = document.getElementById('mt-dice-pips');
 rollsEl = document.getElementById('mt-rolls');
 logEl = document.getElementById('mt-log-items');
 logScroll = document.getElementById('mt-log-items-wrap');
 logRoot = document.getElementById('mt-log-root');
 logToggleBtn = document.getElementById('mt-log-toggle');
 logCountEl = document.getElementById('mt-log-count');
 inputPanel = document.getElementById('mt-input-panel');
 answerInput = document.getElementById('mt-answer');
 submitBtn = document.getElementById('mt-submit-btn');
 resultEl = document.getElementById('mt-result');
 toastEl = document.getElementById('mt-toast');

 buildBoard();
 resetRun(false);

 rollBtn.addEventListener('click', onRoll);
 diceEl.addEventListener('click', onRoll);
 newBtn.addEventListener('click', onNewBoard);
 submitBtn.addEventListener('click', onSubmit);
 logToggleBtn.addEventListener('click', toggleLog);
 answerInput.addEventListener('keydown', function(e) {
 if (e.key === 'Enter') { e.preventDefault(); onSubmit(); }
 });
 }

 function clearDicePips() {
 while (dicePipsEl.firstChild) dicePipsEl.removeChild(dicePipsEl.firstChild);
 }

 function renderDiceFace(n) {
 clearDicePips();
 const positions = PIP_SETS[n] || [];
 positions.forEach(function(key) {
 const xy = PIP_POS[key];
 const pip = document.createElementNS(SVG_NS, 'circle');
 pip.setAttribute('cx', xy[0]);
 pip.setAttribute('cy', xy[1]);
 pip.setAttribute('r', 4.2);
 pip.setAttribute('class', 'mt-dice-pip');
 dicePipsEl.appendChild(pip);
 });
 }

 function renderDiceQuestion() {
 clearDicePips();
 const t = document.createElementNS(SVG_NS, 'text');
 t.setAttribute('x', 30);
 t.setAttribute('y', 41);
 t.setAttribute('text-anchor', 'middle');
 t.setAttribute('class', 'mt-dice-q');
 t.textContent = '?';
 dicePipsEl.appendChild(t);
 }

 function buildBoard() {
 cells.forEach(function(c) { if (c.el) c.el.remove(); });
 cells = [];
 const cfg = levelConfig(level);

 for (let i = 0; i &lt; N_CELLS; i++) {
 const coord = COORDS[i];
 const el = document.createElement('div');
 el.className = 'mt-cell';
 if (i === START) el.classList.add('start');
 else if (CORNERS.has(i)) el.classList.add('corner');
 else if (i === CENTER) el.classList.add('center');
 else if (DIAG_CELLS.has(i)) el.classList.add('diag');

 el.style.left = coord[0] + '%';
 el.style.top = coord[1] + '%';

 let op = null;
 if (i === START) {
 el.textContent = 'START';
 } else {
 op = genOp(cfg);
 el.textContent = opText(op);
 }
 board.appendChild(el);
 cells.push({ idx: i, op: op, el: el });
 }
 }

 function resetRun(regenerateBoard) {
 phase = 'idle';
 pos = START;
 value = 0;
 rolls = 0;
 shortcutPending = null;
 pathLog = [];
 diceEl.classList.remove('rolling', 'settle');
 diceEl.disabled = false;
 renderDiceQuestion();
 rollsEl.textContent = '0';
 levelEl.textContent = level;
 resultEl.textContent = '';
 resultEl.className = 'mt-result';
 inputPanel.style.display = 'none';
 wrap.classList.add('no-input');
 answerInput.value = '';
 rollBtn.disabled = false;
 rollBtn.textContent = '주사위 굴리기';

 if (regenerateBoard) buildBoard();
 clearVisitedHighlights();
 updatePawn(false);
 pushLog({ type: 'start' });
 }

 function clearVisitedHighlights() {
 cells.forEach(function(c) {
 c.el.classList.remove('current', 'visited', 'just-applied');
 });
 if (cells[START]) cells[START].el.classList.add('current');
 }

 const PAWN_OFFSET_X = 4.2;
 const PAWN_OFFSET_Y = -4.2;

 function updatePawn(animate) {
 const coord = COORDS[pos];
 if (!animate) pawn.style.transition = 'none';
 pawn.style.left = (coord[0] + PAWN_OFFSET_X) + '%';
 pawn.style.top = (coord[1] + PAWN_OFFSET_Y) + '%';
 if (!animate) {
 void pawn.offsetWidth;
 pawn.style.transition = '';
 }
 }

 function standardNext(p) {
 if (p === 20) return 21;
 if (p === 21) return CENTER;
 if (p === 25) return 26;
 if (p === 26) return CENTER;
 if (p === CENTER) return 23;
 if (p === 23) return 24;
 if (p === 24) return START;
 if (p === 27) return 28;
 if (p === 28) return CORNER_C;
 if (p &gt;= 0 &amp;&amp; p &lt; 19) return p + 1;
 if (p === 19) return START;
 return START;
 }

 function nextStep(p) {
 if (p === CORNER_A &amp;&amp; shortcutPending === 'A') { shortcutPending = null; return 20; }
 if (p === CORNER_B &amp;&amp; shortcutPending === 'B') { shortcutPending = null; return 25; }
 return standardNext(p);
 }

 function pushLog(entry) {
 pathLog.push(entry);
 renderLog();
 }

 function renderLog() {
 logEl.innerHTML = '';
 pathLog.forEach(function(e) {
 const span = document.createElement('span');
 span.className = 'mt-log-item';
 if (e.type === 'start') {
 span.classList.add('start-tag');
 span.textContent = 'START · 0';
 } else if (e.type === 'op') {
 span.classList.add(opClass(e.op));
 span.textContent = opText(e.op);
 } else if (e.type === 'shortcut') {
 span.classList.add('shortcut');
 span.textContent = '↘ shortcut';
 } else if (e.type === 'center-pass') {
 span.classList.add('shortcut');
 span.textContent = '✦ CENTER';
 } else if (e.type === 'end') {
 span.classList.add('start-tag');
 span.textContent = '⟲ BACK';
 }
 logEl.appendChild(span);
 });
 logCountEl.textContent = pathLog.length;
 logScroll.scrollTop = logScroll.scrollHeight;
 }

 function toggleLog() {
 const willCollapse = !logRoot.classList.contains('collapsed');
 logRoot.classList.toggle('collapsed', willCollapse);
 logToggleBtn.setAttribute('aria-expanded', String(!willCollapse));
 if (!willCollapse) {
 logScroll.scrollTop = logScroll.scrollHeight;
 }
 }

 function showToast(text, ms) {
 toastEl.textContent = text;
 toastEl.classList.add('show');
 clearTimeout(showToast._t);
 showToast._t = setTimeout(function() { toastEl.classList.remove('show'); }, ms || 1200);
 }

 function onRoll() {
 if (phase !== 'idle') return;
 phase = 'rolling';
 rollBtn.disabled = true;
 diceEl.disabled = true;

 diceEl.classList.remove('settle');
 diceEl.classList.add('rolling');
 let ticks = 0;
 const maxTicks = 9;
 const tick = setInterval(function() {
 renderDiceFace(randInt(1, 6));
 ticks++;
 if (ticks &gt;= maxTicks) {
 clearInterval(tick);
 const d = randInt(1, 6);
 renderDiceFace(d);
 diceEl.classList.remove('rolling');
 diceEl.classList.add('settle');
 setTimeout(function() { diceEl.classList.remove('settle'); }, 320);
 rolls++;
 rollsEl.textContent = rolls;
 movePawn(d);
 }
 }, 70);
 }

 function movePawn(steps) {
 phase = 'moving';
 let remaining = steps;
 let reachedStart = false;
 // True only while a single dice move is passing through CENTER mid-stride.
 // When set, the very next step from CENTER diverts onto the BL diagonal
 // (27 → 28 → CORNER_C) instead of the standard BR diagonal (23 → 24).
 let centerPassthrough = false;

 function stepOnce() {
 const prev = pos;
 let nxt;
 if (prev === CENTER &amp;&amp; centerPassthrough) {
 nxt = 27;
 centerPassthrough = false;
 } else {
 nxt = nextStep(prev);
 }
 cells[prev].el.classList.remove('current');
 if (prev !== START) cells[prev].el.classList.add('visited');
 pos = nxt;
 cells[nxt].el.classList.add('current');
 updatePawn(true);

 if (nxt === START) {
 reachedStart = true;
 setTimeout(finishMove, 260);
 return;
 }

 remaining--;

 if (nxt === CENTER &amp;&amp; remaining &gt; 0) {
 centerPassthrough = true;
 pushLog({ type: 'center-pass' });
 showToast('중앙 통과 → 좌하단 경로!');
 }

 if (remaining &gt; 0) setTimeout(stepOnce, 220);
 else setTimeout(finishMove, 260);
 }

 function finishMove() {
 if (reachedStart) {
 pushLog({ type: 'end' });
 enterInputPhase();
 return;
 }
 const cell = cells[pos];
 if (cell.op) {
 value = applyOp(value, cell.op);
 pushLog({ type: 'op', op: cell.op });
 cell.el.classList.add('just-applied');
 setTimeout(function() { cell.el.classList.remove('just-applied'); }, 550);
 }
 if (pos === CORNER_A) {
 shortcutPending = 'A';
 pushLog({ type: 'shortcut' });
 showToast('지름길 진입!');
 } else if (pos === CORNER_B) {
 shortcutPending = 'B';
 pushLog({ type: 'shortcut' });
 showToast('지름길 진입!');
 }
 phase = 'idle';
 rollBtn.disabled = false;
 diceEl.disabled = false;
 }

 stepOnce();
 }

 function enterInputPhase() {
 phase = 'input';
 rollBtn.disabled = true;
 diceEl.disabled = true;
 inputPanel.style.display = 'block';
 wrap.classList.remove('no-input');
 answerInput.value = '';
 resultEl.textContent = '';
 resultEl.className = 'mt-result';
 setTimeout(function() { answerInput.focus(); }, 60);
 }

 function onSubmit() {
 if (phase !== 'input') return;
 const raw = answerInput.value.trim();
 if (raw === '' || !/^-?\d+$/.test(raw)) {
 resultEl.textContent = '정수를 입력하세요.';
 resultEl.className = 'mt-result wrong';
 return;
 }
 const user = parseInt(raw, 10);
 if (user === value) {
 resultEl.textContent = '정답! 최종 값 = ' + value + ' · Lv ' + (level + 1) + '(으)로 진행합니다.';
 resultEl.className = 'mt-result correct';
 level++;
 setTimeout(function() { resetRun(true); }, 1700);
 } else {
 resultEl.textContent = '오답. 정답은 ' + value + ' 입니다. 같은 보드로 다시 도전합니다.';
 resultEl.className = 'mt-result wrong';
 setTimeout(function() { resetRun(false); }, 2400);
 }
 }

 function onNewBoard() {
 if (phase === 'moving' || phase === 'rolling') return;
 resetRun(true);
 }

 if (document.readyState === 'loading') {
 document.addEventListener('DOMContentLoaded', init);
 } else {
 init();
 }
})();
&lt;/script&gt;

&lt;hr&gt;
&lt;h2 id="규칙"&gt;규칙&lt;a class="anchor" href="#%ea%b7%9c%ec%b9%99"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;시작 시 값은 &lt;code&gt;0&lt;/code&gt; 이고 말은 &lt;strong&gt;출발지(START)&lt;/strong&gt; 에 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주사위(1~6)&lt;/strong&gt; 를 굴려 나온 눈만큼 한 칸씩 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도착한 칸의 연산만&lt;/strong&gt; 현재 값에 적용됩니다. 지나친 칸은 적용되지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상단 좌우 코너&lt;/strong&gt;(우상단 / 좌상단)에 정확히 도착하면, 다음 굴림부터 자동으로 대각선 &lt;strong&gt;지름길&lt;/strong&gt;을 탑니다 (항상 출발지까지 더 짧은 경로).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;중앙(CENTER)&lt;/strong&gt; 통과 규칙:
&lt;ul&gt;
&lt;li&gt;중앙에 &lt;strong&gt;정확히 도착해서 멈추면&lt;/strong&gt;, 다음 굴림부터 우하단 대각선(&lt;code&gt;23 → 24 → START&lt;/code&gt;)을 따라 &lt;strong&gt;출발지로 직행&lt;/strong&gt;합니다.&lt;/li&gt;
&lt;li&gt;중앙을 &lt;strong&gt;그냥 지나치면&lt;/strong&gt;(이번 굴림에 남은 칸이 있다면), 좌하단 대각선(&lt;code&gt;27 → 28 → 좌하단 코너&lt;/code&gt;)으로 빠져나가 외곽을 돌아 출발지로 갑니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;말이 출발지로 돌아오면(또는 넘어서면) 최종 값 입력 모드로 전환됩니다.&lt;/li&gt;
&lt;li&gt;정답을 맞히면 다음 단계(Level)로, 틀리면 같은 보드에서 다시 도전합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="계산-규칙"&gt;계산 규칙&lt;a class="anchor" href="#%ea%b3%84%ec%82%b0-%ea%b7%9c%ec%b9%99"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;누적값은 화면에 표시되지 않습니다.&lt;/strong&gt; 경로 로그의 연산 순서만 보고 암산으로 추적하세요.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;÷&lt;/code&gt; 연산은 &lt;strong&gt;정수 몫(소수점 버림)&lt;/strong&gt; 입니다. 예: &lt;code&gt;7 ÷ 3 = 2&lt;/code&gt;, &lt;code&gt;-7 ÷ 3 = -2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;뺄셈과 곱셈으로 &lt;strong&gt;음수&lt;/strong&gt; 가 자연스럽게 나올 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="단계level"&gt;단계(Level)&lt;a class="anchor" href="#%eb%8b%a8%ea%b3%84level"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Lv&lt;/th&gt;
 &lt;th&gt;연산&lt;/th&gt;
 &lt;th&gt;피연산자 범위&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1 ~ 9&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1 ~ 20&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;×&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1 ~ 20 (×는 2 ~ 5)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;×&lt;/code&gt;, &lt;code&gt;÷&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1 ~ 30 (×는 2 ~ 9, ÷는 2 ~ 5)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5+&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;×&lt;/code&gt;, &lt;code&gt;÷&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;1 ~ 50 (×는 2 ~ 12, ÷는 2 ~ 9)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="팁"&gt;팁&lt;a class="anchor" href="#%ed%8c%81"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;경로 로그에는 지나온 연산과 지름길 진입 기록이 차례로 쌓입니다. 중간에 감을 잃었을 때 되짚어 보세요.&lt;/li&gt;
&lt;li&gt;너무 복잡한 연산 배치가 걸렸다 싶으면 &lt;strong&gt;&amp;ldquo;새 보드&amp;rdquo;&lt;/strong&gt; 로 연산 배치를 다시 뽑을 수 있습니다 (단계는 유지).&lt;/li&gt;
&lt;li&gt;곱셈·나눗셈이 섞이면 작은 수부터 빠르게 계산하는 습관을 들이세요. 예를 들어 &lt;code&gt;× 7&lt;/code&gt; 뒤에 &lt;code&gt;÷ 2&lt;/code&gt; 가 오면, 가능하면 먼저 간단히 만들 수 있는 묶음을 찾습니다.&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>