"> 제목 "> 제목 ">
<head>
  <!-- HTML Meta Tags -->
  <meta charset="UTF-8" />
  <title> 제목 </title>
  <meta
    name="description"   content="  Attension시뮬레이션(with YangPhago) "   />
  <meta name="keywords" content=" Attension 시뮬레이션, attension, attension explainer" />

  <!-- Open Graph / Facebook -->
  <meta   property="og:title"   content=" Attension시뮬레이션(with YangPhago) "  />
  <meta  property="og:description" content="Attension 시뮬레이션, attension, attension explainer "  />
  <meta property="og:image" content="대표 이미지" />
  <meta property="og:url" content="페이지 주소" />
  <meta property="og:type" content="website" />
</head>

<aside> 💡 AI와 함께 코딩, 개노다로 2일간 약 30시간을 투자해 만든 한국인을 위한 Attension 메카니즘 체험시뮬레이션(코파일럿이 짱

</aside>

-원래는 클로드의 아티팩트나 구글 제미나이 캔버스로 구현하려고 했으나 코드가 3000줄이 넘어가는 관계로 코파일럿으로 구현 후, 깃허브 탑재

🤖 어텐션 8단계 시뮬레이션_Made By Yangphago(with Copilot)

image.png

[참고문헌]

  1. https://towardsdatascience.com/transformers-explained-visually-part-1-overview-of-functionality-95a6dd460452/
  2. https://velog.io/@shparksue/CS224n-Transformers-셀프-어텐션-멀티-헤드-어텐션-포지셔널-임베딩-등
  3. https://bigdaheta.tistory.com/67
  4. https://modulabs.co.kr/blog/introducing-attention
  5. https://skyil.tistory.com/256

[코드 전체]

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>🤖 어텐션 8단계 시뮬레이션_Made By Yangphago(with Copilot)</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh; padding: 100px 20px 20px 20px;
        }
        .container {
            max-width: 1400px; margin: 0 auto; background: #fff; border-radius: 20px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1); overflow: hidden;
        }
        .header {
            background: linear-gradient(45deg, #ff6b6b, #ee5a24); color: #fff; padding: 30px; text-align: center;
        }
        .header h1 { font-size: 2.5em; margin-bottom: 10px; }
        .content { padding: 40px; }
        
        /* 제어 패널 */
        .control-panel {
            background: #f8f9fa; border-radius: 15px; padding: 30px; margin-bottom: 30px;
            border-left: 5px solid #4caf50;
        }
        .input-section { margin-bottom: 20px; }
        .input-section label { font-weight: bold; margin-bottom: 10px; display: block; }
        .input-section input {
            width: 100%; padding: 12px; border: 2px solid #ddd; border-radius: 8px;
            font-size: 1.1em; transition: border-color 0.3s;
        }
        .input-section input:focus { border-color: #4caf50; outline: none; }
        
        .control-buttons {
            display: flex; 
            gap: 15px; 
            margin-top: 20px; 
            flex-wrap: wrap;
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 1000;
            background: #ffffff;
            border-radius: 20px;
            padding: 15px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
            border: 1px solid rgba(255, 255, 255, 0.2);
            justify-content: center;
            max-width: 300px;
        }
        .btn {
            background: linear-gradient(45deg, #4caf50, #45a049); color: #fff; border: none;
            padding: 8px 16px; border-radius: 20px; cursor: pointer; font-size: 0.9em;
            transition: all 0.3s ease; font-weight: bold; white-space: nowrap;
        }
        .btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); }
        .btn.secondary { background: linear-gradient(45deg, #2196f3, #1976d2); }
        .btn.danger { background: linear-gradient(45deg, #f44336, #d32f2f); }
        
        /* 진행률 표시 */
        .progress-section {
            background: #e3f2fd; border-radius: 15px; padding: 20px; margin: 20px 0;
            text-align: center;
        }
        .progress-bar {
            width: 100%; height: 15px; background: #ddd; border-radius: 10px;
            overflow: hidden; margin: 15px 0;
        }
        .progress-fill {
            height: 100%; background: linear-gradient(90deg, #4caf50, #2196f3);
            width: 0%; transition: width 0.5s ease; border-radius: 10px;
        }
        .step-counter {
            display: flex; justify-content: space-between; margin: 20px 0;
        }
        .step-circle {
            width: 40px; height: 40px; border-radius: 50%; background: #ddd;
            display: flex; align-items: center; justify-content: center;
            font-weight: bold; color: #666; transition: all 0.3s ease;
        }
        .step-circle.active { background: #4caf50; color: #fff; transform: scale(1.2); }
        .step-circle.completed { background: #2196f3; color: #fff; }
        
        /* 단계별 섹션 */
        .step-section {
            background: #fff; border-radius: 15px; padding: 30px; margin: 30px 0;
            border: 2px solid #e0e0e0; opacity: 1; transition: all 0.5s ease;
        }
        .step-section.active {
            opacity: 1; border-color: #4caf50;
        }
        .step-title {
            font-size: 1.5em; font-weight: bold; margin-bottom: 20px;
            display: flex; align-items: center; gap: 10px;
        }
        
        /* 1단계: 인코더-디코더 */
        .encoder-decoder {
            display: grid; grid-template-columns: 1fr 100px 1fr; gap: 30px; margin: 20px 0;
            align-items: center;
        }
        .io-box {
            background: #f5f5f5; border-radius: 10px; padding: 20px; text-align: center;
            min-height: 150px; display: flex; flex-direction: column; justify-content: center;
            transition: all 0.3s ease;
        }
        .io-box.encoder { border-left: 5px solid #4caf50; }
        .io-box.decoder { border-left: 5px solid #ff9800; }
        .io-box.active { background: #e8f5e8; transform: scale(1.05); }
        .arrow {
            font-size: 3em; color: #666; text-align: center;
            animation: none;
        }
        
        /* 2단계: 포지셔널 인코딩 */
        .positional-demo {
            display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin: 20px 0;
        }
        .encoding-comparison {
            background: #f5f5f5; border-radius: 10px; padding: 20px;
        }
        .word-item {
            display: flex; justify-content: space-between; align-items: center;
            padding: 10px; margin: 5px 0; border-radius: 8px; background: #fff;
            transition: all 0.3s ease;
        }
        .word-item.highlight { background: #ffeb3b; transform: translateX(5px); }
        .position-value {
            font-family: monospace; font-size: 0.9em; color: #666;
        }
        
        /* 3-6단계: 어텐션 시각화 */
        .attention-container {
            margin: 20px 0;
        }
        
        /* Q, K, V 설명 섹션 */
        .qkv-explanation {
            background: #f0f8ff; border-radius: 15px; padding: 25px; margin: 20px 0;
            border-left: 5px solid #2196f3;
        }
        .qkv-container {
            display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0;
        }
        .qkv-box {
            background: #fff; border-radius: 10px; padding: 20px; text-align: center;
            border: 2px solid #ddd; transition: all 0.3s ease;
        }
        .qkv-box.query { border-color: #4caf50; }
        .qkv-box.key { border-color: #ff9800; }
        .qkv-box.value { border-color: #9c27b0; }
        .qkv-box:hover { transform: translateY(-3px); box-shadow: 0 5px 15px rgba(0,0,0,0.1); }
        .qkv-box h5 { margin-bottom: 10px; font-size: 1.1em; }
        .qkv-box.query h5 { color: #4caf50; }
        .qkv-box.key h5 { color: #ff9800; }
        .qkv-box.value h5 { color: #9c27b0; }
        .qkv-box p { font-size: 0.9em; color: #666; margin-bottom: 15px; }
        
        .mini-matrix {
            display: grid; grid-template-columns: repeat(3, 30px); gap: 2px; justify-content: center;
        }
        .mini-cell {
            width: 30px; height: 30px; display: flex; align-items: center; justify-content: center;
            font-size: 0.7em; font-weight: bold; color: #fff; border-radius: 3px;
        }
        
        .attention-formula {
            background: #fff; border-radius: 10px; padding: 20px; margin: 20px 0; text-align: center;
            border: 2px solid #2196f3;
        }
        .formula {
            font-size: 1.2em; font-weight: bold; color: #2196f3; margin-top: 10px;
            font-family: 'Times New Roman', serif;
        }
        
        .tokens-row {
            display: flex; gap: 15px; margin: 20px 0; justify-content: center; flex-wrap: wrap;
        }
        .token {
            background: #e3f2fd; border: 2px solid #2196f3; border-radius: 10px;
            padding: 15px 20px; text-align: center; min-width: 80px;
            transition: all 0.3s ease; cursor: pointer; position: relative;
        }
        .token:hover { transform: translateY(-3px); box-shadow: 0 5px 15px rgba(33,150,243,0.3); }
        .token.selected { background: #2196f3; color: #fff; transform: scale(1.1); }
        .token.focused { background: #ff9800; color: #fff; transform: scale(1.15); }
        
        .attention-matrix {
            display: grid; gap: 3px; margin: 20px 0; justify-content: center;
            background: #f5f5f5; padding: 20px; border-radius: 10px;
        }
        .attention-cell {
            width: 50px; height: 50px; display: flex; align-items: center; justify-content: center;
            border-radius: 5px; font-size: 0.8em; font-weight: bold; color: #fff;
            transition: all 0.3s ease; cursor: pointer;
        }
        .attention-cell:hover { transform: scale(1.2); z-index: 10; }
        
        /* 멀티헤드 */
        .multihead-container {
            display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px;
        }
        .head-box {
            background: #f8f9fa; border-radius: 10px; padding: 15px; text-align: center;
            border: 2px solid #ddd; transition: all 0.3s ease;
        }
        .head-box.active { border-color: #9c27b0; background: #f3e5f5; }
        .head-title { font-weight: bold; margin-bottom: 10px; color: #9c27b0; }
        
        /* 연결선 애니메이션 */
        .connection-line {
            position: absolute; height: 3px; background: linear-gradient(90deg, #4caf50, #2196f3);
            border-radius: 2px; opacity: 0; transition: opacity 0.5s ease;
            pointer-events: none; z-index: 100;
        }
        .connection-line.active { opacity: 0.8; }
        
        /* 정보 패널 */
        .info-panel {
            background: #fff3e0; border-radius: 15px; padding: 25px; margin: 25px 0;
            border-left: 5px solid #ff9800;
        }
        .info-panel h3 { color: #ef6c00; margin-bottom: 15px; }
        
        /* 키프레임 애니메이션 */
        @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
        @keyframes slideIn { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
        @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
        @keyframes bounce { 0%, 20%, 50%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-5px); } 60% { transform: translateY(-3px); } }
        
        .slide-in { animation: slideIn 0.8s ease; }
        .fade-in { animation: fadeIn 0.6s ease; }
        .bounce { animation: bounce 1s; }
        
        /* 학습 과정 스타일 */
        .training-container { margin: 20px 0; }
        .training-controls { 
            display: flex; align-items: center; gap: 20px; margin-bottom: 30px;
            background: #f8f9fa; padding: 20px; border-radius: 10px;
        }
        .training-progress { flex: 1; }
        .comparison-container { margin: 30px 0; }
        .matrix-comparison { 
            display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0;
        }
        .matrix-before, .matrix-after { 
            background: #f5f5f5; padding: 15px; border-radius: 10px; text-align: center;
        }
        .matrix-before h5 { color: #f44336; }
        .matrix-after h5 { color: #4caf50; }
        
        /* 테스트 스타일 */
        .test-container { 
            margin: 20px 0;
            background: #f8f9ff;
            border-radius: 15px;
            padding: 25px;
        }
        
        /* 새로운 비교 테스트 스타일 */
        .comparison-mode {
            display: flex;
            gap: 20px;
            margin: 15px 0;
            align-items: center;
        }
        
        .comparison-mode label {
            display: flex;
            align-items: center;
            gap: 5px;
            cursor: pointer;
            padding: 8px 15px;
            border-radius: 20px;
            background: #e8f2ff;
            transition: all 0.3s ease;
        }
        
        .comparison-mode label:hover {
            background: #d0e7ff;
        }
        
        .attention-comparison-results {
            margin-top: 30px;
        }
        
        .comparison-metrics {
            display: grid;
            grid-template-columns: 1fr 1fr 1fr;
            gap: 20px;
            margin-bottom: 30px;
        }
        
        .metric-card {
            background: white;
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 3px 15px rgba(0,0,0,0.1);
            border: 2px solid #e1e8ed;
        }
        
        .metric-card h5 {
            margin: 0 0 15px 0;
            color: #2c3e50;
            font-size: 1.1em;
        }
        
        .metric-bars {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        
        .metric-bar {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .metric-bar span:first-child {
            min-width: 70px;
            font-size: 0.9em;
            color: #666;
        }
        
        .metric-bar span:last-child {
            min-width: 40px;
            font-weight: bold;
            font-size: 0.9em;
        }
        
        .bar {
            height: 20px;
            flex-grow: 1;
            border-radius: 10px;
            overflow: hidden;
            position: relative;
        }
        
        .bar.before {
            background: #ffebee;
            border: 1px solid #ffcdd2;
        }
        
        .bar.after {
            background: #e8f5e8;
            border: 1px solid #c8e6c9;
        }
        
        .bar .fill {
            height: 100%;
            border-radius: 10px;
            transition: width 1s ease-in-out;
        }
        
        .bar.before .fill {
            background: linear-gradient(90deg, #ff5722, #ff8a65);
        }
        
        .bar.after .fill {
            background: linear-gradient(90deg, #4caf50, #81c784);
        }
        
        .improvement-score {
            text-align: center;
        }
        
        .score-circle {
            width: 80px;
            height: 80px;
            border-radius: 50%;
            margin: 0 auto 15px;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            background: conic-gradient(from 0deg, #4caf50 0%, #4caf50 var(--progress, 0%), #e0e0e0 var(--progress, 0%));
        }
        
        .score-circle::before {
            content: '';
            position: absolute;
            width: 60px;
            height: 60px;
            border-radius: 50%;
            background: white;
        }
        
        .score-circle span {
            position: relative;
            z-index: 1;
            font-weight: bold;
            font-size: 1.2em;
            color: #2c3e50;
        }
        
        .matrix-comparison-test {
            display: grid;
            grid-template-columns: 1fr auto 1fr;
            gap: 30px;
            align-items: start;
            margin-bottom: 30px;
        }
        
        .test-matrix-before, .test-matrix-after {
            background: white;
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 3px 15px rgba(0,0,0,0.1);
        }
        
        .test-matrix-before {
            border-left: 4px solid #f44336;
        }
        
        .test-matrix-after {
            border-left: 4px solid #4caf50;
        }
        
        .comparison-arrow {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 20px 0;
        }
        
        .arrow-container {
            text-align: center;
        }
        
        .learning-arrow {
            font-size: 2em;
            display: block;
            margin-bottom: 10px;
            animation: pulse 2s infinite;
        }
        
        @keyframes pulse {
            0%, 100% { transform: scale(1); }
            50% { transform: scale(1.1); }
        }
        
        .matrix-analysis {
            margin-top: 15px;
            padding-top: 15px;
            border-top: 1px solid #eee;
        }
        
        .matrix-analysis p {
            margin: 5px 0;
            font-size: 0.9em;
            color: #666;
        }
        
        .interactive-features {
            background: #f0f7ff;
            border-radius: 12px;
            padding: 20px;
            border: 2px dashed #4a90e2;
        }
        
        .interactive-features h5 {
            margin: 0 0 15px 0;
            color: #2c3e50;
        }
        
        .feature-buttons {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }
        
        .feature-btn {
            padding: 8px 16px;
            font-size: 0.9em;
            border-radius: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        
        .feature-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
        }
        
        .highlight-difference {
            border: 3px solid #ff9800 !important;
            box-shadow: 0 0 20px rgba(255, 152, 0, 0.5) !important;
            animation: glow 1.5s ease-in-out infinite alternate;
        }
        
        @keyframes glow {
            from { box-shadow: 0 0 20px rgba(255, 152, 0, 0.5); }
            to { box-shadow: 0 0 30px rgba(255, 152, 0, 0.8); }
        }
        
        .heatmap-mode .attention-cell {
            transition: all 0.3s ease;
        }
        
        .evolution-animation {
            animation: evolve 3s ease-in-out;
        }
        
        @keyframes evolve {
            0% { opacity: 0.3; transform: scale(0.8); }
            50% { opacity: 0.7; transform: scale(1.1); }
            100% { opacity: 1; transform: scale(1); }
        }
        
        /* Q,K,V 매트릭스 비교 스타일 */
        .qkv-comparison {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 30px;
            margin: 20px 0;
        }
        
        .qkv-before, .qkv-after {
            background: white;
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 3px 15px rgba(0,0,0,0.1);
        }
        
        .qkv-before {
            border-left: 4px solid #f44336;
        }
        
        .qkv-after {
            border-left: 4px solid #4caf50;
        }
        
        .qkv-matrices {
            display: grid;
            grid-template-columns: 1fr 1fr 1fr;
            gap: 15px;
            margin-top: 15px;
        }
        
        .qkv-matrix h6 {
            margin: 0 0 10px 0;
            text-align: center;
            color: #2c3e50;
            font-size: 0.9em;
        }
        
        .mini-matrix {
            display: grid;
            grid-template-columns: repeat(3, 30px);
            gap: 2px;
            justify-content: center;
        }
        
        .mini-matrix .mini-cell {
            width: 30px;
            height: 30px;
            border-radius: 4px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.7em;
            font-weight: bold;
            color: white;
            text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
        }
        
        /* 추가 스타일 */
        .attention-comparison-results {
            opacity: 0;
            transform: translateY(20px);
            transition: all 0.6s ease;
        }
        
        .attention-cell.highlighted {
            border: 2px solid #ff6b35 !important;
            transform: scale(1.1);
            z-index: 10;
            position: relative;
        }
        
        .attention-cell:hover {
            transform: scale(1.05);
            transition: all 0.2s ease;
            border: 2px solid #4a90e2;
            z-index: 5;
            position: relative;
        }
        
        /* 매트릭스 라벨 스타일 */
        .matrix-label-cell {
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 0.8em;
            font-weight: bold;
            color: #2c3e50;
            background: #f8f9fa;
            border: 1px solid #dee2e6;
        }
        
        .matrix-label-cell.empty {
            background: transparent;
            border: none;
        }
        
        .matrix-label-cell.column-label {
            background: linear-gradient(135deg, #e3f2fd, #bbdefb);
            border-bottom: 2px solid #2196f3;
            writing-mode: vertical-rl;
            text-orientation: mixed;
            height: 40px;
        }
        
        .matrix-label-cell.row-label {
            background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
            border-right: 2px solid #4caf50;
            width: 80px;
            height: 40px;
            text-align: center;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
        
        .matrix-label-cell:hover {
            background: #fff3cd;
            color: #856404;
            transform: scale(1.02);
            transition: all 0.2s ease;
        }
        
        @media (max-width: 768px) {
            .comparison-metrics {
                grid-template-columns: 1fr;
            }
            
            .matrix-comparison-test {
                grid-template-columns: 1fr;
                gap: 20px;
            }
            
            .comparison-arrow {
                transform: rotate(90deg);
                padding: 10px 0;
            }
            
            .feature-buttons {
                justify-content: center;
            }
            
            .control-buttons {
                position: fixed;
                top: 10px;
                left: 50%;
                right: auto;
                transform: translateX(-50%);
                max-width: 90vw;
                gap: 8px;
                padding: 10px;
            }
            
            .btn {
                padding: 6px 12px;
                font-size: 0.8em;
            }
            
            body {
                padding: 120px 10px 20px 10px;
            }
            
            .matrix-label-cell.column-label {
                font-size: 0.7em;
                height: 35px;
            }
            
            .matrix-label-cell.row-label {
                font-size: 0.7em;
                width: 70px;
                height: 35px;
            }
            
            .attention-cell {
                width: 35px !important;
                height: 35px !important;
                font-size: 0.7em;
            }
        }
        .test-input { 
            background: #e3f2fd; padding: 20px; border-radius: 10px; margin-bottom: 20px;
        }
        .test-results { 
            display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px;
        }
        .result-box { 
            background: #fff; padding: 15px; border-radius: 8px; border: 2px solid #ddd;
            min-height: 60px; display: flex; align-items: center; justify-content: center;
        }
        .confidence-bar { 
            position: relative; width: 100%; height: 30px; background: #ddd; 
            border-radius: 15px; overflow: hidden;
        }
        .confidence-fill { 
            height: 100%; background: linear-gradient(90deg, #4caf50, #8bc34a);
            width: 0%; transition: width 1s ease;
        }
        .confidence-bar span { 
            position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
            font-weight: bold; color: #333;
        }
        .attention-matrix.small { max-width: 200px; margin: 0 auto; }
        
        /* 반응형 */
        @media (max-width: 768px) {
            .encoder-decoder { grid-template-columns: 1fr; }
            .positional-demo { grid-template-columns: 1fr; }
            .tokens-row { justify-content: center; }
            .matrix-comparison { grid-template-columns: 1fr; }
            .test-results { grid-template-columns: 1fr; }
        }
        
        /* 🕸️ 그래프 시각화 스타일 */
        .graph-container {
            display: flex;
            flex-direction: column;
            gap: 20px;
            margin-top: 20px;
        }
        
        .attention-graph {
            background: white;
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
            display: flex;
            justify-content: center;
        }
        
        .graph-controls {
            display: flex;
            gap: 20px;
            margin: 15px 0;
            align-items: center;
        }
        
        .graph-controls label {
            display: flex;
            align-items: center;
            gap: 5px;
            cursor: pointer;
            padding: 8px 15px;
            background: #fff;
            border-radius: 20px;
            border: 2px solid #e0e0e0;
            transition: all 0.3s ease;
        }
        
        .graph-controls label:hover {
            border-color: #4285f4;
            background: #f0f7ff;
        }
        
        .graph-legend {
            background: #f8f9ff;
            border-radius: 10px;
            padding: 15px;
            border-left: 4px solid #4285f4;
        }
        
        .legend-items {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }
        
        .legend-item {
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .line-sample {
            width: 40px;
            height: 3px;
            border-radius: 2px;
        }
        
        .line-sample.thick {
            background: #ff4444;
            height: 4px;
            opacity: 0.9;
        }
        
        .line-sample.medium {
            background: #ffaa00;
            height: 3px;
            opacity: 0.7;
        }
        
        .line-sample.thin {
            background: #cccccc;
            height: 2px;
            opacity: 0.5;
        }
        
        .graph-analysis {
            background: white;
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
        }
        
        .analysis-metrics {
            display: flex;
            justify-content: space-around;
            gap: 20px;
        }
        
        .metric-item {
            text-align: center;
            flex: 1;
        }
        
        .metric-item h6 {
            margin: 0 0 10px 0;
            color: #666;
            font-size: 14px;
        }
        
        .metric-item div {
            font-size: 24px;
            font-weight: bold;
            color: #4285f4;
        }
        
        /* SVG 그래프 스타일 */
        #graphSvg {
            border: 1px solid #e0e0e0;
            border-radius: 10px;
            background: #fafafa;
        }
        
        .graph-node {
            cursor: pointer;
            transition: all 0.3s ease;
        }
        
        .graph-node:hover {
            transform: scale(1.1);
        }
        
        .graph-edge {
            transition: all 0.3s ease;
        }
        
        .graph-edge:hover {
            stroke-width: 4;
        }
        
        .node-label {
            font-family: 'Noto Sans KR', sans-serif;
            font-size: 12px;
            font-weight: 500;
            text-anchor: middle;
            fill: #333;
            pointer-events: none;
        }

        

    /* 🧩 컨텍스트 벡터 패널 (이미지 스타일) */
    .context-panel { background:#fff; border:1px solid #e5eaf3; border-radius:12px; padding:16px; margin:14px 0; }
    .context-title { font-weight:700; color:#2c3e50; margin-bottom:8px; }
    .context-grid { display:grid; grid-template-columns: 70px 200px 28px 140px 28px 200px; gap:10px 12px; align-items:center; }
    .ctx-label { font-weight:600; color:#37474f; text-align:right; }
    .hs-box { border:2px solid #1a237e; border-radius:8px; padding:8px 10px; display:flex; gap:8px; align-items:center; background:#f7f9ff; }
    .vec-dot { width:18px; height:18px; border-radius:50%; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.1); }
    .mul { text-align:center; font-weight:700; color:#455a64; }
    .arrow { text-align:center; color:#000; font-weight:700; }
    .plus { text-align:center; color:#000; font-weight:700; }
    .eq { text-align:center; font-weight:800; color:#263238; }
    .weight-box { display:flex; align-items:center; gap:8px; }
    .weight-bar { flex:1; height:14px; background:#eceff1; border:1px solid #cfd8dc; border-radius:7px; position:relative; }
    .weight-fill { position:absolute; left:0; top:0; height:100%; border-radius:7px; }
    .weight-val { min-width:56px; text-align:left; font-family:monospace; color:#455a64; font-weight:700; }
    .ctx-box { border:2px solid #263238; border-radius:8px; padding:8px 10px; display:flex; gap:8px; align-items:center; background:#ffffff; }
    .ctx-right { grid-column: 6 / 7; }
        
        /* 🎭 마스킹 시뮬레이션 스타일 */
        .masking-simulation {
            background: #f8f9ff;
            border-radius: 15px;
            padding: 20px;
            margin: 20px 0;
            border-left: 4px solid #9c27b0;
        }
        
        .masking-demo {
            display: flex;
            align-items: center;
            justify-content: space-around;
            margin: 20px 0;
            flex-wrap: wrap;
        }
        
        .masking-step {
            text-align: center;
            flex: 1;
            min-width: 200px;
        }
        
        .masking-arrow {
            text-align: center;
            margin: 0 20px;
        }
        
        .masking-arrow p {
            margin: 5px 0;
            font-size: 0.9em;
            color: #666;
        }
        
        .attention-matrix.small {
            max-width: 200px;
            margin: 0 auto;
        }
        
        .attention-matrix.small .attention-cell {
            width: 30px !important;
            height: 30px !important;
            font-size: 0.7em !important;
        }
        
        .attention-matrix.small .matrix-label-cell {
            width: auto !important;
            height: auto !important;
            padding: 4px 6px;
            font-size: 0.75em !important;
            white-space: nowrap;
        }
        
        .masking-explanation {
            background: white;
            border-radius: 10px;
            padding: 15px;
            margin-top: 15px;
        }
        
        .masking-explanation h6 {
            margin: 0 0 10px 0;
            color: #9c27b0;
        }
        
        .masking-explanation ul {
            margin: 0;
            padding-left: 20px;
        }
        
        .masked-cell {
            background: #ffebee !important;
            color: #d32f2f !important;
            position: relative;
        }
        
        .masked-cell::after {
            content: '🚫';
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            font-size: 0.8em;
        }
        
    </style>
</head>
<body>
<div class="container">
    <div class="header">
        <h1> 👍어텐션 8단계 시뮬레이션_Made By Yangphago(with Copilot)</h1>
        <h2>인코더·디코더 → 포지셔널 인코딩 → 어텐션 → 셀프 어텐션 → 멀티헤드 → 최종 결과 → 학습 과정</h2>
        <p> Attention메커니즘을 한국의 학생들이 보다 쉽게 이해할 수 있도록 구현한 시뮬레이션 사이트(일종의 explainer)</p>
        <p> 오류발생시 roughkyo@gmail.com로 회신 부탁드립니다.</p>

    </div>

    <div class="content">
        <!-- 제어 패널 -->
        <div class="control-panel">
            <h3>🎮 실험 제어판</h3>
            <div class="input-section">
                <label for="inputSentence">예시 문단 선택:</label>
                <select id="inputSentence" onchange="loadSelectedText()">
                    <option value="">문단을 선택하세요</option>
                    <option value="오늘은 날씨가 정말 좋습니다. 하늘이 맑고 바람이 시원하게 불어옵니다. 친구들과 함께 공원에서 산책을 하며 즐거운 시간을 보냈습니다.">문단 1: 날씨와 산책</option>
                    <option value="어머니는 매일 아침 일찍 일어나서 가족을 위해 맛있는 아침식사를 준비하십니다. 따뜻한 밥과 김치찌개, 그리고 신선한 야채들로 상을 차려주십니다. 우리 가족은 함께 모여서 즐겁게 식사를 합니다.">문단 2: 가족의 아침</option>
                    <option value="학교에서 새로운 선생님이 오셨습니다. 선생님은 매우 친절하시고 수업을 재미있게 해주십니다. 학생들은 모두 선생님을 좋아하며 열심히 공부하고 있습니다.">문단 3: 새로운 선생님</option>
                    <option value="도서관은 조용하고 평화로운 공간입니다. 많은 사람들이 책을 읽거나 공부를 하고 있습니다. 나는 좋아하는 소설책을 찾아서 편안한 의자에 앉아 읽기 시작했습니다.">문단 4: 도서관에서</option>
                    <option value="주말에 가족들과 함께 바다로 여행을 갔습니다. 파란 바다와 하얀 모래사장이 정말 아름다웠습니다. 우리는 바닷가에서 조개를 줍고 파도와 함께 놀았습니다.">문단 5: 바다 여행</option>
                </select>
                <div id="selectedText" style="margin-top: 10px; padding: 10px; background: #f5f5f5; border-radius: 5px; min-height: 60px; display: none;">
                    선택한 문단이 여기에 표시됩니다.
                </div>
            </div>
            <div class="control-buttons">
                <button class="btn" onclick="startStepSimulation()">🚀 단계별 시뮬레이션 시작</button>
                <button class="btn secondary" onclick="nextStep()">➡️ 다음 단계</button>
                <button class="btn secondary" onclick="prevStep()">⬅️ 이전 단계</button>
                <button class="btn danger" onclick="resetSimulation()">🔄 초기화</button>
            </div>
        </div>

        <!-- 진행률 표시 -->
        <div class="progress-section">
            <h4>📊 진행 상황</h4>
            <div class="step-counter">
                <div class="step-circle" id="step-0">1</div>
                <div class="step-circle" id="step-1">2</div>
                <div class="step-circle" id="step-2">3</div>
                <div class="step-circle" id="step-3">4</div>
                <div class="step-circle" id="step-4">5</div>
                <div class="step-circle" id="step-5">6</div>
                <div class="step-circle" id="step-6">7</div>
                <div class="step-circle" id="step-7">8</div>
            </div>
            <div class="progress-bar">
                <div class="progress-fill" id="progressFill"></div>
            </div>
            <div id="currentStep">단계별 시뮬레이션을 시작하세요!</div>
        </div>

        <!-- 1단계: 인코더-디코더 -->
        <div class="step-section" id="section-0">
            <div class="step-title">
                <span>🔢</span>
                <span>1단계: 인코더-디코더 구조</span>
            </div>
            <div class="encoder-decoder">
                <div class="io-box encoder" id="encoderBox">
                    <h4>🔢 인코더 (Encoder)</h4>
                    <div id="encoderText">한글 입력 대기 중...</div>
                    <div id="encoderTokens" class="tokens-row"></div>
                </div>
                <div class="arrow">➡️</div>
                <div class="io-box decoder" id="decoderBox">
                    <h4>🎯 디코더 (Decoder)</h4>
                    <div id="decoderText">영어 출력 대기 중...</div>
                    <div id="decoderTokens" class="tokens-row"></div>
                </div>
            </div>
        </div>

        <!-- 2단계: 포지셔널 인코딩 -->
        <div class="step-section" id="section-1">
            <div class="step-title">
                <span>📍</span>
                <span>2단계: 포지셔널 인코딩</span>
            </div>
            <div class="positional-demo">
                <div class="encoding-comparison">
                    <h4>일반 워드 임베딩 (위치 정보 없음)</h4>
                    <div id="normalEmbedding"></div>
                </div>
                <div class="encoding-comparison">
                    <h4>포지셔널 인코딩 추가 (위치 정보 포함)</h4>
                    <div id="positionalEmbedding"></div>
                </div>
            </div>
        </div>

        <!-- 3-0단계: 멀티헤드 어텐션-->
        <div class="step-section" id="section-2">
            <div class="step-title">
                <span>🎯</span>
                <span>3-0단계: 멀티헤드 어텐션 시뮬</span>
            </div>
            <div class="multihead-container" id="mhAnyContainer"></div>
        </div>

        <!-- 3-1단계: 인코더 멀티헤드 셀프 어텐션 -->
        <div class="step-section" id="section-3">
            <div class="step-title">
                <span>🔄</span>
                <span>3-1단계: 인코더 멀티헤드 셀프 어텐션 (문장 내부 연관도)</span>
            </div>
            <div class="attention-container">
                <div id="selfAttentionTokens" class="tokens-row"></div>
                <div id="selfAttentionMatrix" class="attention-matrix"></div>
            </div>
            <div id="mhSelfScores" style="margin-top:16px;">
                <div style="display:flex; gap:30px; justify-content:center; font-weight:600; color:#555;">
                    <div style="text-decoration: underline; text-decoration-color:#333;">Input</div>
                    <div style="color:#e91e63; text-decoration: underline; text-decoration-color:#f8a5b8;">Score 1</div>
                    <div style="color:#f9a825; text-decoration: underline; text-decoration-color:#ffe082;">Score 2</div>
                </div>
                <div style="text-align:center; margin-top:6px; color:#666; font-size:0.9em;">
                    입력 단어를 클릭하면, 두 헤드(Score 1/2)의 가장 강한 연결 2개만 선으로 표시됩니다.
                </div>
                <div id="esvRowWrapper" style="display:flex; gap:30px; justify-content:center; align-items:flex-start; margin-top:8px; position:relative;">
                    <div id="esvInput" style="display:flex; flex-direction:column; gap:6px;"></div>
                    <div id="esvScore1" style="display:flex; flex-direction:column; gap:6px;"></div>
                    <div id="esvScore2" style="display:flex; flex-direction:column; gap:6px;"></div>
                    <!-- 연결선 SVG 오버레이 -->
                    <svg id="esvOverlay" style="position:absolute; left:0; top:0; width:100%; height:100%; pointer-events:none;"></svg>
                </div>
                </div>
            </div>
        </div>

        <!-- 3-2단계: 디코더 멀티헤드 마스크드 셀프 어텐션 -->
        <div class="step-section" id="section-4">
            <div class="step-title">
                <span>🎭</span>
                <span>3-2단계: 디코더 멀티헤드 마스크드 셀프 어텐션</span>
            </div>
            <div class="attention-container">
                <div style="margin:6px 0 4px 0; color:#444; font-size:0.92em; line-height:1.6;">
                    입력 시퀀스는 <strong>[START]</strong>로 시작합니다. 디코더 마스크는 <strong>현재 시점과 미래 시점(j ≥ i)</strong>의 위치에 <code>-∞</code>(계산상 매우 큰 음수)를 적용하여 softmax 후 확률이 0이 되도록 합니다. 이로써 <strong>[START] 행 전체</strong>와 <strong>대각선(자기 자신)</strong>, 그리고 <strong>미래 토큰</strong>을 모두 볼 수 없게 만듭니다.
                    <div style="margin-top:6px; color:#666;">
                        <strong>설명</strong>: "그러나 디코더는 출력 시퀀스를 입력으로 한 번에 받기 때문에, 현재 시점의 단어를 예측하고자 할 때 입력 시퀀스 행렬로부터 미래 시점의 단어까지도 참고할 수 있는 현상이 발생한다. 이를 방지하기 위해 디코더는 예측해야 하는 것의 정답을 알면 안 되도록 설계해야 한다."
                    </div>
                </div>
                <div id="decoderMaskedMatrix" class="attention-matrix"></div>
            </div>
        </div>

        <!-- 3-3단계: 인코더-디코더 크로스 멀티헤드 어텐션 -->
        <div class="step-section" id="section-5">
            <div class="step-title">
                <span>👁️</span>
                <span>3-3단계: 인코더-디코더 크로스 멀티헤드 어텐션</span>
            </div>
            <div class="multihead-container" id="crossMultiContainer"></div>
        </div>

    <!-- 4단계: 학습 전·후 인코더의 멀티헤드 셀프 어텐션 패턴 비교 -->
    <div class="step-section" id="section-6">
            <div class="step-title">
                <span>🎓</span>
        <span>4단계: 학습 전·후 인코더의 멀티헤드 셀프 어텐션 패턴 변화</span>
            </div>
            <div class="info-box" style="margin:12px 0; padding:12px; background:#f7faff; border:1px solid #e0ecff; border-radius:8px;">
                <strong>중요:</strong> 학습으로 직접 바뀌는 것은 <em>Attention Heatmap</em>이 아니라 <em>Q/K/V를 생성하는 가중치 행렬(Wq, Wk, Wv)</em>입니다.
                <div style="margin-top:6px; font-size:0.9em; color:#555;">
                    Attention(Q,K,V) = softmax(QK<sup>T</sup>/√d<sub>k</sub>)V, 여기서 Q= XWq, K= XWk, V= XWv
                </div>
            </div>
            <div class="training-container">
                <!-- 🧩 학습 전 컨텍스트(버튼 위) -->
                <div id="contextBefore" class="context-panel">
                    <div class="context-title">학습 전: Hidden State · a(가중치) · 각 항의 가중합 → C(context vector)</div>
                    <div id="contextBeforeGrid" class="context-grid"></div>
                </div>
                <div class="training-controls">
                    <button class="btn" onclick="startTraining()">🚀 학습 시작</button>
                    <div class="training-progress">
                        <div class="progress-bar">
                            <div class="progress-fill" id="trainingProgress"></div>
                        </div>
                        <div id="trainingStatus">학습 대기 중...</div>
                        <div id="lossText" style="font-size: 0.9em; color: #666; margin-top: 6px;">손실: -</div>
                    </div>
                </div>
                <!-- 🧩 학습 후 컨텍스트(버튼 아래) -->
                <div id="contextAfter" class="context-panel" style="display:none;">
                    <div class="context-title">학습 후: Hidden State · a(가중치) · 각 항의 가중합 → C(context vector)</div>
                    <div id="contextAfterGrid" class="context-grid"></div>
                </div>

                
            </div>
        </div>

        <!-- 5단계: 학습 후 디코더 출력 (Cross Attention 정렬 강화) -->
        <div class="step-section" id="section-7">
            <div class="step-title">
                <span>🧩</span>
                <span>5단계: 학습 후 디코더 출력 (Cross Attention)</span>
            </div>
            <div class="attention-container">
                <div style="margin:6px 0 8px 0; color:#444; font-size:0.92em; line-height:1.6;">
                    학습 이후, 인코더-디코더 <strong>크로스 어텐션</strong>이 정렬처럼 강화되어 <em>각 한국어(인코더) 토큰</em>과 대응하는 <em>영어(디코더) 토큰</em>의 값이 가장 크게 나타납니다.
                    시각화는 흑백(Grayscale) 히트맵으로 표현하며, 밝을수록 강한 연결입니다.
                </div>
                <div id="decoderOutputAfter" class="attention-matrix"></div>
            </div>
        </div>

        <!-- 정보 패널 -->
        <div class="info-panel">
            <h3>💡 현재 단계 설명</h3>
            <div id="stepExplanation">
                단계별 시뮬레이션을 시작하면 각 단계의 상세한 설명이 여기에 표시됩니다.
            </div>
        </div>
    </div>
</div>

<script>
// 🎯 8단계 시뮬레이션 시스템
let currentStep = -1;
let isSimulationRunning = false;
let animationSpeed = 1500;
let tokens = [];
let translatedTokens = [];
let isModelTrained = false;
let finalAttentionMatrix = []; // 6단계에서 생성된 실제 어텐션 매트릭스
let trainingData = {
    beforeAttention: [],
    afterAttention: [],
    beforeSelfAttention: [],
    afterSelfAttention: []
};
// 3-1 최근 헤드 가중치 (리사이즈 시 재그리기용)
let __lastSelfHeads = null;

// 📚 한글 → 영어 자동 번역 데이터베이스 (확장됨)
const translationDB = {
    // 기본 어텐션 관련 용어
    '트랜스포머를': 'transformer',
    '트랜스포머': 'transformer',
    '트랜스포머의': 'transformer\\'s',
    '학습해보자': 'let\\'s learn',
    '학습': 'learning',
    '학습하기': 'learning',
    '해보자': 'let\\'s try',
    '위해': 'for',
    '위해서는': 'in order to',
    '어텐션': 'attention',
    '구조': 'structure',
    '구조를': 'structure',
    '전체적으로': 'comprehensively',
    '살펴봐야': 'should examine',
    '살펴보자': 'let\\'s examine',
    '살펴볼꺼야': 'will examine',
    '어머니가': 'mother is',
    '어텐션이니까': 'because attention',
    
    // 인명 및 일반 명사
    '철수는': 'Cheolsu',
    '철수': 'Cheolsu',
    '영희는': 'Younghee',
    '영희': 'Younghee',
    '민수': 'Minsu',
    '지영': 'Jiyoung',
    
    // 시간 관련
    '어제': 'yesterday',
    '오늘은': 'today',
    '오늘': 'today',
    '내일': 'tomorrow',
    '지금': 'now',
    '나중에': 'later',
    '아침에': 'in the morning',
    '아침': 'morning',
    '아침식사를': 'breakfast',
    '아침식사': 'breakfast',
    '매일': 'every day',
    '일찍': 'early',
    '저녁에': 'in the evening',
    '밤에': 'at night',
    '밤새도록': 'all night long',
    '주말에': 'on weekend',
    
    // 날씨 관련 (문단 1)
    '날씨가': 'weather is',
    '날씨': 'weather',
    '정말': 'really',
    '좋습니다.': 'is good.',
    '좋습니다': 'is good',
    '좋다': 'good',
    '좋아하는': 'favorite',
    '좋아하며': 'like and',
    '좋아': 'like',
    '하늘이': 'sky is',
    '하늘': 'sky',
    '맑고': 'clear and',
    '맑은': 'clear',
    '바람이': 'wind is',
    '바람': 'wind',
    '시원하게': 'coolly',
    '시원한': 'cool',
    '불어옵니다.': 'blows.',
    '불어옵니다': 'blows',
    '오늘': 'today',
    '내일': 'tomorrow',
    '지금': 'now',
    '나중에': 'later',
    '아침에': 'in the morning',
    '아침': 'morning',
    '아침식사를': 'breakfast',
    '아침식사': 'breakfast',
    '매일': 'every day',
    '일찍': 'early',
    '저녁에': 'in the evening',
    '밤에': 'at night',
    '밤새도록': 'all night long',
    '주말에': 'on weekend',
    
    // 장소 관련
    '도서관에서': 'at library',
    '도서관은': 'library is',
    '도서관': 'library',
    '학교에서': 'at school',
    '학교': 'school',
    '집에서': 'at home',
    '집': 'home',
    '교실': 'classroom',
    '카페': 'cafe',
    '공원에서': 'in the park',
    '공원': 'park',
    '바다로': 'to the sea',
    '바다와': 'sea and',
    '바다': 'sea',
    '바닷가에서': 'at the beach',
    '바닷가': 'beach',
    '공간입니다': 'is a space',
    '공간': 'space',
    
    // 날씨 관련
    '날씨가': 'weather is',
    '날씨': 'weather',
    '정말': 'really',
    '좋습니다': 'is good',
    '좋다': 'good',
    '좋아하는': 'favorite',
    '좋아하며': 'like and',
    '좋아': 'like',
    '하늘이': 'sky is',
    '하늘': 'sky',
    '맑고': 'clear and',
    '맑은': 'clear',
    '바람이': 'wind is',
    '바람': 'wind',
    '시원하게': 'coolly',
    '시원한': 'cool',
    '불어옵니다': 'blows',
    '파란': 'blue',
    '하얀': 'white',
    '아름다웠습니다': 'was beautiful',
    '아름다운': 'beautiful',
    
    // 사람 관련
    '친구들과': 'with friends',
    '친구들': 'friends',
    '친구': 'friend',
    '함께': 'together',
    '가족을': 'family',
    '가족들과': 'with family',
    '가족은': 'family is',
    '가족': 'family',
    '어머니는': 'mother',
    '어머니': 'mother',
    '선생님이': 'teacher',
    '선생님은': 'teacher is',
    '선생님을': 'teacher',
    '선생님': 'teacher',
    '학생들은': 'students',
    '학생들': 'students',
    '사람들이': 'people',
    '사람들': 'people',
    '우리는': 'we',
    '우리': 'we',
    '나는': 'I',
    '나': 'I',
    
    // 행동 관련 (동사)
    '산책을': 'walk',
    '산책': 'walk',
    '하며': 'while doing',
    '보냈습니다': 'spent',
    '보냈다': 'spent',
    '일어나서': 'waking up',
    '일어나': 'wake up',
    '준비하십니다': 'prepares',
    '준비': 'prepare',
    '차려주십니다': 'serves',
    '모여서': 'gathering',
    '식사를': 'meal',
    '식사': 'meal',
    '합니다': 'do',
    '하고': 'do',
    '오셨습니다': 'came',
    '해주십니다': 'does for us',
    '공부를': 'study',
    '공부하고': 'studying',
    '공부': 'study',
    '있습니다': 'exist',
    '있고': 'exist and',
    '있다': 'exist',
    '읽거나': 'reading or',
    '읽기': 'reading',
    '읽고': 'read and',
    '찾아서': 'finding',
    '찾아': 'find',
    '앉아': 'sitting',
    '시작했습니다': 'started',
    '시작': 'start',
    '갔습니다': 'went',
    '갔다': 'went',
    '줍고': 'picking up',
    '놀았습니다': 'played',
    '놀았다': 'played',
    '빌렸다': 'borrowed',
    '빌려서': 'borrowing',
    '먹으면서': 'while eating',
    '보았다': 'watched',
    '보고': 'watching',
    
    // 음식 관련
    '맛있는': 'delicious',
    '따뜻한': 'warm',
    '밥과': 'rice and',
    '밥': 'rice',
    '김치찌개': 'kimchi stew',
    '김치찌개,': 'kimchi stew,',
    '신선한': 'fresh',
    '야채들로': 'with vegetables',
    '야채': 'vegetables',
    '상을': 'table',
    '상': 'table',
    '치킨과': 'chicken and',
    '치킨': 'chicken',
    '만화책을': 'comic book',
    '만화책': 'comic book',
    
    // 형용사 및 상태
    '새로운': 'new',
    '매우': 'very',
    '친절하시고': 'kind and',
    '친절한': 'kind',
    '재미있게': 'interestingly',
    '재미있는': 'interesting',
    '모두': 'all',
    '열심히': 'diligently',
    '조용하고': 'quiet and',
    '조용한': 'quiet',
    '평화로운': 'peaceful',
    '많은': 'many',
    '편안한': 'comfortable',
    '즐거운': 'joyful',
    '즐겁게': 'joyfully',
    
    // 물건 관련
    '책을': 'book',
    '책': 'book',
    '소설책을': 'novel',
    '소설책': 'novel',
    '의자에': 'on chair',
    '의자': 'chair',
    '모래사장이': 'sand beach',
    '모래사장': 'sand beach',
    '파도와': 'waves and',
    '파도': 'waves',
    '조개를': 'shells',
    '조개': 'shells',
    
    // 수업 관련
    '수업을': 'class',
    '수업': 'class',
    
    // 여행 관련
    '여행을': 'trip',
    '여행': 'trip',
    
    // 장소 지정어
    '에서': 'at',
    '에': 'to',
    '로': 'to',
    '와': 'and',
    '과': 'and',
    '도': 'also',
    '만': 'only',
    '을': 'object marker',
    '를': 'object marker',
    '이': 'subject marker',
    '가': 'subject marker',
    '은': 'topic marker',
    '는': 'topic marker',
    '의': 'possessive',
    '시간을': 'time',
    '시간': 'time',
    
    // 추가 기본 단어들
    '안녕': 'hello',
    '세상': 'world',
    '학생': 'student',
    '이다': 'am',
    '딥러닝': 'deep learning',
    '모델': 'model',
    '훈련': 'training',
    '자연어': 'natural language',
    '처리': 'processing',
    '인공지능': 'artificial intelligence',
    '기계': 'machine',
    '번역': 'translation',
    '언어': 'language',
    '신경망': 'neural network',
    '공부': 'study',
    '모델을': 'model',
    '훈련하자': 'let\\'s train',
    '딥러닝을': 'deep learning',
    '공부하자': 'let\\'s study',
    '배우자': 'let\\'s learn',
    '시작하자': 'let\\'s start',
    '만들어보자': 'let\\'s create',
    '실습해보자': 'let\\'s practice',
    '중요한': 'important',
    '중요하다': 'important',
    '개념': 'concept',
    '이해': 'understanding',
    '이해하기': 'understanding',
    '필요하다': 'necessary',
    '필요한': 'necessary',
    '과정': 'process',
    '방법': 'method',
    '기술': 'technology',
    '데이터': 'data',
    '알고리즘': 'algorithm',
    '성능': 'performance',
    '결과': 'result',
    '분석': 'analysis',
    '예측': 'prediction',
    '정확도': 'accuracy',
    
    // 영어 → 한글 번역
    'transformer': '트랜스포머',
    'transformer\\'s': '트랜스포머의',
    'let\\'s learn': '학습해보자',
    'learning': '학습',
    'let\\'s try': '해보자',
    'for': '위해',
    'attention': '어텐션',
    'structure': '구조',
    'comprehensively': '전체적으로',
    'should examine': '살펴봐야',
    'let\\'s examine': '살펴보자',
    'will examine': '살펴볼꺼야',
    'mother is': '어머니가',
    'because attention': '어텐션이니까',
    'hello': '안녕',
    'world': '세상',
    'I': '나는',
    'student': '학생',
    'am': '이다',
    'deep learning': '딥러닝',
    'model': '모델',
    'training': '훈련',
    'natural language': '자연어',
    'processing': '처리',
    'artificial intelligence': '인공지능',
    'machine': '기계',
    'translation': '번역',
    'language': '언어',
    'neural network': '신경망',
    'study': '공부',
    'let\\'s train': '훈련하자',
    'let\\'s study': '공부하자',
    'let\\'s start': '시작하자',
    'let\\'s create': '만들어보자',
    'let\\'s practice': '실습해보자',
    'important': '중요한',
    'concept': '개념',
    'understanding': '이해',
    'necessary': '필요한',
    'process': '과정',
    'method': '방법',
    'technology': '기술',
    'data': '데이터',
    'algorithm': '알고리즘',
    'performance': '성능',
    'result': '결과',
    'analysis': '분석',
    'prediction': '예측',
    'accuracy': '정확도'
};

// 📊 단계별 설명
const stepExplanations = {
    0: { title: "인코더-디코더 구조", content: "🔢 한글을 입력하면 자동으로 영어 문장이 생성됩니다. 인코더는 입력을 이해하고, 디코더는 출력을 생성합니다." },
    1: { title: "포지셔널 인코딩", content: "📍 단어 순서 정보를 추가합니다. 위치 인코딩으로 문맥 흐름을 반영합니다." },
    2: { title: "3-0 멀티헤드 어텐션(임의)", content: "🎯 임의 문장으로 여러 헤드가 각기 다른 패턴(문법/의미/위치/문맥)을 잡는 모습을 봅니다." },
    3: { title: "3-1 인코더 MHA(셀프)", content: "🔄 인코더 안에서 같은 문장 토큰끼리의 연관도(셀프 어텐션)를 멀티헤드로 시각화합니다." },
    4: { title: "3-2 디코더 MHA(마스크드 셀프)", content: "🎭 디코더의 마스크드 셀프 어텐션을 보여주며 미래 토큰이 가려지는 효과를 확인합니다." },
    5: { title: "3-3 인코더-디코더 크로스 MHA", content: "👁️ 인코더의 출력과 디코더의 현재 상태 사이의 크로스 어텐션 히트맵을 봅니다." },
    6: { title: "4 학습 전·후 어텐션 레이어와 가중치 변화", content: "🎓 행별 softmax 가중치(합=1)와 해당 가중합(Weighted Sum) 벡터가 학습으로 어떻게 달라지는지 시각화합니다." },
    7: { title: "5 학습 후 디코더 출력 (Cross Attention)", content: "🧩 학습 이후, 인코더(KR) ↔ 디코더(EN)의 정렬이 강화된 그레이스케일 히트맵을 확인합니다." }
};

// 🚀 메인 시뮬레이션 시작
async function startStepSimulation() {
    if (isSimulationRunning) return;
    
    isSimulationRunning = true;
    currentStep = -1;
    
    const inputText = document.getElementById('inputSentence').value.trim();
    if (!inputText) {
        alert('예시 문단을 선택해주세요!');
        isSimulationRunning = false;
        return;
    }
    
    // 입력 언어 감지
    const isKorean = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(inputText);
    
    // 토큰화 (입력 언어 - 단어 단위) - 길이 제한 증가
    tokens = inputText.trim().split(/\\s+/).slice(0, 15); // 8개에서 15개로 증가
    
    // 번역 생성 (양방향)
    translatedTokens = generateTranslation(inputText);
    
    console.log('🎯 시뮬레이션 시작:', { 
        inputLanguage: isKorean ? '한글' : '영어',
        tokens, 
        translatedTokens 
    });
    
    await nextStep();
}

// 🔄 다음 단계로 이동
async function nextStep() {
    if (currentStep >= 7) return;
    
    currentStep++;
    await executeStep(currentStep);
}

// ⬅️ 이전 단계로 이동
async function prevStep() {
    if (currentStep <= 0) return;
    
    currentStep--;
    await executeStep(currentStep);
}

// 🎯 특정 단계 실행
async function executeStep(stepIndex) {
    updateProgress(stepIndex);
    updateStepExplanation(stepIndex);
    
    // 모든 섹션 비활성화
    document.querySelectorAll('.step-section').forEach(section => {
        section.classList.remove('active');
    });
    
    // 현재 단계 활성화
    const currentSection = document.getElementById(`section-${stepIndex}`);
    if (currentSection) {
        currentSection.classList.add('active');
        currentSection.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
    
    // 단계별 실행
    switch(stepIndex) {
        case 0: await executeEncoderDecoder(); break;             // 1단계
        case 1: await executePositionalEncoding(); break;         // 2단계
        case 2: await executeMHAnySentence(); break;              // 3-0단계
        case 3: await executeEncoderSelfMH(); break;              // 3-1단계
        case 4: await executeDecoderMaskedSelfMH(); break;        // 3-2단계
        case 5: await executeCrossMultiHead(); break;             // 3-3단계
        case 6: await executeTrainingSimulation(); break;         // 4단계
        case 7: await executeDecoderOutputAfter(); break;         // 5단계
    }
}

// 📊 진행률 업데이트
function updateProgress(stepIndex) {
    const progress = ((stepIndex + 1) / 8) * 100;
    document.getElementById('progressFill').style.width = `${progress}%`;
    
    // 단계 원 업데이트
    document.querySelectorAll('.step-circle').forEach((circle, index) => {
        circle.classList.remove('active', 'completed');
        if (index < stepIndex) {
            circle.classList.add('completed');
        } else if (index === stepIndex) {
            circle.classList.add('active');
        }
    });
    
    document.getElementById('currentStep').textContent = 
    `${stepIndex + 1}/8 단계: ${stepExplanations[stepIndex]?.title || '진행 중'}`;
}

// 💭 단계 설명 업데이트
function updateStepExplanation(stepIndex) {
    const explanation = stepExplanations[stepIndex];
    if (explanation) {
        document.getElementById('stepExplanation').innerHTML = 
            `<strong>${explanation.title}</strong><br>${explanation.content}`;
    }
}

// 🔤 영어 번역 생성
function generateTranslation(inputText, isSingleToken = false) {
    // 단일 토큰 처리
    if (isSingleToken) {
        // translationDB에서 직접 찾기
        if (translationDB[inputText]) {
            return translationDB[inputText];
        }
        
        // 기본 한글 처리
        const isKorean = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(inputText);
        if (isKorean) {
            // 문장 부호는 그대로
            if (/[.!?]/.test(inputText)) {
                return inputText;
            }
            
            // 강화된 기본 번역 로직 - 더 많은 패턴 처리
            const commonTranslations = {
                // 조사 및 어미
                '은': 'is', '는': 'is', '이': 'this', '가': 'is',
                '을': 'object', '를': 'object', '에': 'to', '에서': 'from',
                '와': 'and', '과': 'and', '의': 'of', '도': 'also',
                '하며': 'while', '하고': 'and', '에게': 'to',
                
                // 일반적인 동사/형용사 어미
                '습니다': 'is', '입니다': 'is', '됩니다': 'becomes',
                '합니다': 'do', '했습니다': 'did', '있습니다': 'exist',
                '보냅니다': 'send', '갑니다': 'go', '옵니다': 'come',
                
                // 기본 단어들
                '친구들과': 'with friends', '함께': 'together', '공원에서': 'in park',
                '산책을': 'walk', '하며': 'while doing', '즐거운': 'joyful',
                '시간을': 'time', '보냈습니다': 'spent'
            };
            
            // 패턴 매칭 시도
            const foundTranslation = commonTranslations[inputText];
            if (foundTranslation) {
                return foundTranslation;
            }
            
            // 어미 분석 시도
            if (inputText.endsWith('습니다')) {
                const root = inputText.slice(0, -3);
                return `${root}_is`;
            }
            if (inputText.endsWith('했습니다')) {
                const root = inputText.slice(0, -4);
                return `${root}_did`;
            }
            if (inputText.endsWith('입니다')) {
                const root = inputText.slice(0, -3);
                return `${root}_is`;
            }
            
            // 최후 수단: 영어 단어로 변환
            return 'word';
        }
        return inputText; // 영어는 그대로
    }
    
    // 문장 전체 처리
    const words = inputText.trim().split(/\\s+/);
    
    // 입력 언어 감지 (한글인지 영어인지)
    const isKorean = /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(inputText);
    
    const translated = words.map(word => {
        // 번역 데이터베이스에서 직접 번역 찾기
        if (translationDB[word]) {
            return translationDB[word];
        }
        
        // 번역이 없는 경우 - 개선된 한글 처리
        if (isKorean) {
            // 한글 → 영어: 더 포괄적인 기본 번역
            const commonKoreanToEnglish = {
                // 조사
                '이': 'this', '그': 'that', '저': 'that',
                '은': 'is', '는': 'is', '이다': 'is', '다': 'is',
                '을': 'the', '를': 'the', '에': 'in', '에서': 'from',
                '와': 'and', '과': 'and', '하고': 'and',
                '의': 'of', '도': 'also', '만': 'only',
                // 기본 동사
                '좋다': 'good', '나쁘다': 'bad', '크다': 'big', '작다': 'small',
                '있다': 'have', '없다': 'not', '되다': 'become', '하다': 'do',
                '보다': 'see', '듣다': 'hear', '말하다': 'speak', '쓰다': 'write',
                '가다': 'go', '오다': 'come', '주다': 'give', '받다': 'receive',
                // 일반적인 단어들
                '함께': 'together', '같이': 'together', '혼자': 'alone',
                '많이': 'much', '조금': 'little', '전부': 'all', '모든': 'every'
            };
            
            // 특정 변수명이나 알 수 없는 단어는 영어 단어로 대체
            const translatedWord = commonKoreanToEnglish[word];
            if (translatedWord) {
                return translatedWord;
            }
            
            // 인명이나 고유명사는 그대로 유지 (한글 2글자 이상)
            if (word.length >= 2 && /^[가-힣]+$/.test(word)) {
                return word; // 한글 그대로 유지
            }
            
            return 'word'; // 기본값
        } else {
            // 영어 → 한글: 기본 한글 단어로 변환
            const commonEnglishToKorean = {
                'the': '그', 'a': '하나의', 'an': '하나의', 'and': '그리고',
                'is': '이다', 'are': '이다', 'was': '였다', 'were': '였다',
                'have': '가지다', 'has': '가지다', 'had': '가졌다',
                'do': '하다', 'does': '하다', 'did': '했다',
                'will': '할것이다', 'would': '할것이다', 'can': '할수있다',
                'good': '좋은', 'bad': '나쁜', 'big': '큰', 'small': '작은',
                'see': '보다', 'hear': '듣다', 'speak': '말하다', 'write': '쓰다'
            };
            return commonEnglishToKorean[word.toLowerCase()] || '단어';
        }
    });
    
    // 모든 단어를 유지 - 필터링하지 않음
    return translated;
}

// 🏗️ 1단계: 인코더-디코더 실행 (한국어 토큰화 → 영어 매핑)
async function executeEncoderDecoder() {
    const encoderBox = document.getElementById('encoderBox');
    const decoderBox = document.getElementById('decoderBox');
    const encoderText = document.getElementById('encoderText');
    const decoderText = document.getElementById('decoderText');
    const encoderTokensDiv = document.getElementById('encoderTokens');
    const decoderTokensDiv = document.getElementById('decoderTokens');
    
    // 선택된 문장 가져오기
    const inputText = document.getElementById('inputSentence').value.trim();
    if (!inputText) {
        alert('예시 문단을 선택해주세요!');
        return;
    }
    
    // 한국어 입력을 정확히 토큰화 (단어 단위로 분리)
    const koreanTokens = inputText.trim()
        .replace(/([.!?])/g, ' $1')  // 문장부호 앞에 공백 추가
        .split(/\\s+/)                // 공백으로 분리
        .filter(token => token.length > 0)  // 빈 토큰 제거
        .slice(0, 20);               // 최대 20개 토큰
    
    tokens = koreanTokens;
    
    // 각 한국어 토큰을 영어로 번역
    const englishTokens = koreanTokens.map(token => {
        // 문장 부호는 그대로
        if (/^[.!?]+$/.test(token)) {
            return token;
        }
        // translationDB에서 번역 찾기, 없으면 기본 번역 함수 호출
        return translationDB[token] || generateTranslation(token, true);
    });
    translatedTokens = englishTokens;
    
    // 인코더 활성화 (한국어 입력)
    encoderBox.classList.add('active');
    encoderText.textContent = `한국어 입력: ${inputText}`;
    
    // 한국어 토큰 표시
    encoderTokensDiv.innerHTML = '';
    for (let i = 0; i < koreanTokens.length; i++) {
        await sleep(200);
        const tokenDiv = document.createElement('div');
        tokenDiv.className = 'token fade-in';
        tokenDiv.textContent = koreanTokens[i];
        tokenDiv.style.backgroundColor = '#ffeb3b';
        tokenDiv.style.color = '#333';
        encoderTokensDiv.appendChild(tokenDiv);
    }
    
    await sleep(500);
    
    // 디코더 활성화 (영어 번역)
    decoderBox.classList.add('active');
    decoderText.textContent = `영어 번역: ${englishTokens.join(' ')}`;
    
    // 영어 토큰 표시
    decoderTokensDiv.innerHTML = '';
    for (let i = 0; i < englishTokens.length; i++) {
        await sleep(200);
        const tokenDiv = document.createElement('div');
        tokenDiv.className = 'token fade-in';
        tokenDiv.textContent = englishTokens[i];
        tokenDiv.style.backgroundColor = '#4caf50';
        tokenDiv.style.color = '#white';
        decoderTokensDiv.appendChild(tokenDiv);
    }
}

// 📍 2단계: 포지셔널 인코딩 실행 (인코더의 한국어 단어를 임베딩)
async function executePositionalEncoding() {
    const normalEmbedding = document.getElementById('normalEmbedding');
    const positionalEmbedding = document.getElementById('positionalEmbedding');
    
    // tokens(한국어)가 있는지 확인
    if (!tokens || tokens.length === 0) {
        alert('먼저 1단계를 진행해주세요!');
        return;
    }
    
    // 일반 워드 임베딩 표시 (인코더의 한국어 단어 사용)
    normalEmbedding.innerHTML = '';
    for (let i = 0; i < tokens.length; i++) {
        await sleep(150);
        const wordItem = document.createElement('div');
        wordItem.className = 'word-item fade-in';
        wordItem.innerHTML = `
            <span>${tokens[i]}</span>
            <span class="position-value">[${(Math.random() * 0.8 + 0.1).toFixed(3)}, ${(Math.random() * 0.8 + 0.1).toFixed(3)}, ...]</span>
        `;
        wordItem.style.backgroundColor = '#e3f2fd';
        normalEmbedding.appendChild(wordItem);
    }
    
    await sleep(300);
    
    // 포지셔널 인코딩 표시 (인코더의 한국어 단어에 위치 정보 추가)
    positionalEmbedding.innerHTML = '';
    for (let i = 0; i < tokens.length; i++) {
        await sleep(150);
        const wordItem = document.createElement('div');
        wordItem.className = 'word-item fade-in';
        
        // 위치에 따른 사인/코사인 값 생성 (실제 트랜스포머 공식)
        const posValue1 = Math.sin(i / Math.pow(10000, (0 / 512))).toFixed(3);
        const posValue2 = Math.cos(i / Math.pow(10000, (1 / 512))).toFixed(3);
        
        wordItem.innerHTML = `
            <span>${tokens[i]}</span>
            <span class="position-value">pos:${i} [${posValue1}, ${posValue2}, ...]</span>
        `;
        wordItem.style.backgroundColor = '#fff3e0';
        positionalEmbedding.appendChild(wordItem);
        
        // 강조 효과
        setTimeout(() => {
            wordItem.classList.add('highlight');
            setTimeout(() => wordItem.classList.remove('highlight'), 500);
        }, 100);
    }
    
    // 설명 텍스트 업데이트
    const explanationDiv = document.querySelector('#section-1 .explanation');
    if (explanationDiv) {
        explanationDiv.innerHTML = `
            <h4>포지셔널 인코딩 설명</h4>
            <p><strong>일반 워드 임베딩:</strong> 인코더의 각 한국어 단어가 벡터로 변환됩니다.</p>
            <p><strong>포지셔널 인코딩:</strong> 한국어 단어의 위치 정보가 추가되어 문장 내에서의 순서를 학습합니다.</p>
            <p>현재 임베딩 중인 한국어 문장: <em>"${tokens.join(' ')}"</em></p>
        `;
    }
}

// 3-0단계: 멀티헤드 어텐션 (임의 문장)
async function executeMHAnySentence() {
    // 기존 멀티헤드 생성 로직 재사용
    const container = document.getElementById('mhAnyContainer');
    container.innerHTML = '';
    const heads = [
        { name: 'Head 1: 문법 구조', color: '#e91e63', focus: 'syntax' },
        { name: 'Head 2: 의미 관계', color: '#9c27b0', focus: 'semantic' },
        { name: 'Head 3: 위치 정보', color: '#673ab7', focus: 'position' },
        { name: 'Head 4: 문맥 흐름', color: '#3f51b5', focus: 'context' }
    ];
    for (let headIndex = 0; headIndex < heads.length; headIndex++) {
        await sleep(150);
        const headBox = document.createElement('div');
        headBox.className = 'head-box fade-in';
        headBox.innerHTML = `
            <div class="head-title" style="color: ${heads[headIndex].color}">
                ${heads[headIndex].name}
            </div>
            <div class="tokens-row" id="head-any-${headIndex}-tokens"></div>
            <div class="attention-matrix" id="head-any-${headIndex}-matrix"></div>
        `;
        container.appendChild(headBox);
    // 토큰 표시 (인코더 한글 토큰 기준)
    const tokensDiv = document.getElementById(`head-any-${headIndex}-tokens`);
    tokens.slice(0, 6).forEach(t => {
            const td = document.createElement('div');
            td.className = 'token'; td.style.fontSize = '0.8em'; td.textContent = t; tokensDiv.appendChild(td);
        });
        // 매트릭스
        await generateHeadAttentionCustom(`head-any-${headIndex}-matrix`, heads[headIndex]);
    }
}

async function generateHeadAttentionCustom(matrixId, headInfo) {
    const matrixDiv = document.getElementById(matrixId);
    const matrixSize = Math.min(tokens.length, 4);
    const displayTokens = tokens.slice(0, matrixSize);
    matrixDiv.style.display = 'grid';
    matrixDiv.style.gridTemplateColumns = `60px repeat(${matrixSize}, 30px)`;
    matrixDiv.style.gridTemplateRows = `40px repeat(${matrixSize}, 30px)`;
    matrixDiv.style.gap = '2px';
    matrixDiv.appendChild(document.createElement('div'));
    for (let j = 0; j < matrixSize; j++) {
        const c = document.createElement('div'); c.className = 'matrix-label col-label'; c.textContent = displayTokens[j]; matrixDiv.appendChild(c);
    }
    for (let i = 0; i < matrixSize; i++) {
        const r = document.createElement('div'); r.className = 'matrix-label row-label'; r.textContent = displayTokens[i]; matrixDiv.appendChild(r);
        for (let j = 0; j < matrixSize; j++) {
            await sleep(30);
            let weight;
            switch(headInfo.focus) {
                case 'syntax': weight = (i === j) ? 0.8 : Math.random() * 0.4; break;
                case 'semantic': weight = Math.random() * 0.7 + 0.2; break;
                case 'position': weight = 1 - Math.abs(i - j) * 0.3; break;
                case 'context': weight = (Math.abs(i - j) === 1) ? 0.9 : Math.random() * 0.3; break;
                default: weight = Math.random() * 0.6;
            }
            const cell = document.createElement('div'); cell.className = 'attention-cell'; cell.textContent = weight.toFixed(1); cell.style.backgroundColor = getAttentionColor(weight); matrixDiv.appendChild(cell);
        }
    }
}

// 3-1단계: 인코더 멀티헤드 셀프 어텐션 (Score1/Score2 세로 리스트 표현)
async function executeEncoderSelfMH() {
    const size = Math.min(tokens.length, 9);
    const words = tokens.slice(0, size);
    if (words.length === 0) return;
    // 컨테이너 초기화
    document.getElementById('selfAttentionTokens').innerHTML = '';
    document.getElementById('selfAttentionMatrix').innerHTML = '';
    const colInput = document.getElementById('esvInput');
    const colS1 = document.getElementById('esvScore1');
    const colS2 = document.getElementById('esvScore2');
    colInput.innerHTML = ''; colS1.innerHTML = ''; colS2.innerHTML = '';
    
    // 두 개의 헤드(Score1/Score2)를 가정하고 각 토큰에 대한 가중치 생성
    const head1 = words.map((_, i) => words.map((__, j) => Math.max(0, 1 - Math.abs(i - j) * 0.35))); // 인접 강조
    const head2 = words.map((_, i) => words.map((__, j) => (j === (i+2)%size ? 0.95 : Math.random()*0.4))); // 점프 패턴
    
    // 색상 유틸: 분홍(Score1), 노랑(Score2)
    const toPink = v => `rgba(255, 64, 64, ${Math.min(0.15 + v*0.85, 1)})`;
    const toYellow = v => `rgba(255, 193, 7, ${Math.min(0.15 + v*0.85, 1)})`;
    const lightPink = v => `rgba(255, 64, 64, ${Math.min(0.08 + v*0.5, 0.35)})`;
    const lightYellow = v => `rgba(255, 193, 7, ${Math.min(0.08 + v*0.5, 0.35)})`;
    
    // 같은 행 정렬로 배치: 세 열 모두 동일 순서(각 단어 1개씩)
    words.forEach((w, idx) => {
        const inDiv = document.createElement('div');
        inDiv.textContent = w;
        inDiv.className = 'esv-input';
        inDiv.setAttribute('data-index', String(idx));
        inDiv.style.padding = '6px 10px';
        inDiv.style.border = '1px solid #eee';
        inDiv.style.background = idx % 2 ? '#fafafa' : '#fff';
        inDiv.style.cursor = 'pointer';
        inDiv.title = '클릭하여 강한 연결을 확인하세요';
        colInput.appendChild(inDiv);
    });
    words.forEach((w, idx) => {
        const s1 = document.createElement('div');
        s1.textContent = w;
        s1.className = 'esv-s1-item';
        s1.setAttribute('data-index', String(idx));
        s1.style.padding = '6px 10px';
        s1.style.border = '1px solid #eee';
        s1.style.background = idx % 2 ? '#fffafc' : '#fff';
        colS1.appendChild(s1);
    });
    words.forEach((w, idx) => {
        const s2 = document.createElement('div');
        s2.textContent = w;
        s2.className = 'esv-s2-item';
        s2.setAttribute('data-index', String(idx));
        s2.style.padding = '6px 10px';
        s2.style.border = '1px solid #eee';
        s2.style.background = idx % 2 ? '#fffef7' : '#fff';
        colS2.appendChild(s2);
    });

    // 클릭 시에만 하이라이트/라인 표시
    colInput.querySelectorAll('.esv-input').forEach(el => {
        el.addEventListener('click', () => {
            const row = Number(el.getAttribute('data-index'));
            __selectedRow = row;
            // 입력 열 강조 초기화/적용
            colInput.querySelectorAll('.esv-input').forEach(n => { n.style.outline=''; n.style.background = (Number(n.getAttribute('data-index'))%2?'#fafafa':'#fff'); });
            el.style.outline = '3px solid #9e9e9e';
            el.style.background = '#e0e0e0';
            // 점수 하이라이트
            updateMHSelfHighlights(row, head1, head2);
            // 라인 표시
            drawMHSelfLinesRow(row);
        });
    });

    // 상태 저장: 클릭 시 참조
    __lastSelfHeads = { head1, head2 };
}

// 클릭 시 두 헤드 분포를 색으로 반영(자기 자신 제외 또는 약화)
function updateMHSelfHighlights(row, head1, head2){
    const s1Items = document.querySelectorAll('#esvScore1 .esv-s1-item');
    const s2Items = document.querySelectorAll('#esvScore2 .esv-s2-item');
    const N = s1Items.length;
    const base1 = (i)=> i%2? '#fffafc' : '#fff';
    const base2 = (i)=> i%2? '#fffef7' : '#fff';
    s1Items.forEach((el,i)=>{ el.style.background = base1(i); });
    s2Items.forEach((el,i)=>{ el.style.background = base2(i); });
    if (row == null || !head1 || !head2) return;
    const weakenSelf = 0; // 자기 자신은 0으로
    for (let j=0;j<N;j++){
        const v1 = j===row ? weakenSelf : (head1[row]?.[j] || 0);
        const v2 = j===row ? weakenSelf : (head2[row]?.[j] || 0);
        const a1 = Math.min(0.12 + v1*0.88, 0.95);
        const a2 = Math.min(0.12 + v2*0.88, 0.95);
        if (v1>0) s1Items[j].style.background = `rgba(233,30,99, ${a1})`;
        if (v2>0) s2Items[j].style.background = `rgba(255,193,7, ${a2})`;
    }
}

// 3-1 연결선 그리기 (강도 기반, 상위 1개) - 선택된 행만
function drawMHSelfLinesRow(row){
    const wrapper = document.getElementById('esvRowWrapper');
    const svg = document.getElementById('esvOverlay');
    if(!wrapper || !svg) return;
    // 기존 라인 제거
    while (svg.firstChild) svg.removeChild(svg.firstChild);

    if (!__lastSelfHeads) return;
    const h1 = __lastSelfHeads.head1, h2 = __lastSelfHeads.head2;
    if (row == null || row < 0 || row >= h1.length) return;

    // 좌표 계산을 위해 bbox를 한 번 갱신
    const wrapRect = wrapper.getBoundingClientRect();
    const inputEl = wrapper.querySelector(`.esv-input[data-index='${row}']`);
    // 최댓값(자기 자신 제외)
    const bestIdx = (arr) => {
        let idx = -1, mv = -Infinity;
        for (let j=0;j<arr.length;j++){
            if (j===row) continue;
            if (arr[j] > mv){ mv = arr[j]; idx = j; }
        }
        return { idx, val: mv };
    };
    if (!h1 || !h2) return;
    const b1 = bestIdx(h1[row]||[]);
    const b2 = bestIdx(h2[row]||[]);
    const s1Best = wrapper.querySelector(`#esvScore1 .esv-s1-item[data-index='${b1.idx}']`);
    const s2Best = wrapper.querySelector(`#esvScore2 .esv-s2-item[data-index='${b2.idx}']`);

    if (!inputEl || !s1Best || !s2Best) return;

    const lineFor = (x1,y1,x2,y2,color,width,opacity,dash=undefined) => {
        const path = document.createElementNS('<http://www.w3.org/2000/svg','path>');
        // 곡선(부드럽게)
        const mx = (x1 + x2)/2;
        const d = `M ${x1},${y1} C ${mx},${y1} ${mx},${y2} ${x2},${y2}`;
        path.setAttribute('d', d);
        path.setAttribute('fill', 'none');
        path.setAttribute('stroke', color);
        path.setAttribute('stroke-width', width);
        path.setAttribute('opacity', opacity);
        if (dash) path.setAttribute('stroke-dasharray', dash);
        svg.appendChild(path);
    };

    const centerOf = (el) => {
        const r = el.getBoundingClientRect();
        return {
            cx: r.left - wrapRect.left + r.width/2,
            cy: r.top - wrapRect.top + r.height/2
        };
    };

    const clamp01 = v => Math.max(0, Math.min(1, v));

    // 선택된 행의 최댓값들 계산
    const best1Val = b1.val;
    const best2Val = b2.val;

    const pInput = centerOf(inputEl);
    const pS1 = centerOf(s1Best);
    const pS2 = centerOf(s2Best);

    // 강도 → 두께/투명도 매핑
    const w1 = 1.5 + 3.5 * clamp01(best1Val);
    const o1 = 0.35 + 0.55 * clamp01(best1Val);
    const w2 = 1.5 + 3.5 * clamp01(best2Val);
    const o2 = 0.35 + 0.55 * clamp01(best2Val);

    // Score1: 분홍, Score2: 노랑(점선)
    lineFor(pInput.cx, pInput.cy, pS1.cx, pS1.cy, '#e91e63', w1, o1);
    lineFor(pInput.cx, pInput.cy, pS2.cx, pS2.cy, '#f9a825', w2, o2, '6 4');
}

// 리사이즈 시 연결선 재그리기
let __selectedRow = -1;
window.addEventListener('resize', () => {
    if (__lastSelfHeads && __selectedRow >= 0 && document.getElementById('section-3')?.classList.contains('active')){
        drawMHSelfLinesRow(__selectedRow);
    }
});

// 3-2단계: 디코더 멀티헤드 마스크드 셀프 어텐션
async function executeDecoderMaskedSelfMH() {
    const container = document.getElementById('decoderMaskedMatrix');
    container.innerHTML = '';
    const maxTokens = 6;
    const base = translatedTokens.slice(0, Math.max(0, maxTokens-1));
    const display = ['[START]', ...base];
    const size = display.length;
    container.style.display = 'grid';
    container.style.gridTemplateColumns = `70px repeat(${size}, 46px)`;
    container.style.gridTemplateRows = `64px repeat(${size}, 40px)`;
    container.appendChild(document.createElement('div'));
    // 상단 열 라벨(겹침 방지: -40deg 회전)
    for (let j = 0; j < size; j++) {
        const c = document.createElement('div');
        c.className='matrix-label';
        c.textContent=display[j];
        c.style.transform = 'rotate(-40deg)';
        c.style.transformOrigin = 'left bottom';
        c.style.whiteSpace = 'nowrap';
        c.style.height = '60px';
        c.style.display = 'flex';
        c.style.alignItems = 'flex-end';
        c.style.justifyContent = 'center';
        c.style.color = '#333';
        container.appendChild(c);
    }
    for (let i = 0; i < size; i++) {
        const r = document.createElement('div'); r.className='matrix-label'; r.textContent=display[i]; container.appendChild(r);
        for (let j = 0; j < size; j++) {
            const cell = document.createElement('div'); cell.className='attention-cell';
            // 요구사항: [START] 행(i===0)은 전부 마스크, 그 외에는 현재/미래 마스킹(j >= i)
            const masked = (i === 0) || (j >= i);
            const NEG = -1e9; // softmax에서 0으로 수렴시키는 큰 음수
            // 로그잇(내적 값) 시뮬레이션
            let logit = masked ? NEG : (0.2 + Math.random()*0.3);
            if (masked) {
                cell.classList.add('masked-cell');
                cell.textContent = '-∞'; // 시각적으로 명확하게 표시
                cell.style.backgroundColor = '#ffebee';
                cell.style.color = '#d32f2f';
                const reason = (i===0) ? 'START 행 전체 마스크' : (j===i ? '현재(자기 자신) 마스크' : '미래 토큰 마스크');
                cell.title = `${display[i]} → ${display[j]}: ${NEG} (${reason}, softmax ≈ 0)`;
            } else {
                cell.textContent = logit.toFixed(2);
                cell.style.backgroundColor = getAttentionColor(logit);
                cell.title = `${display[i]} → ${display[j]}: ${logit.toFixed(3)}`;
            }
            container.appendChild(cell);
        }
    }
}

// 3-3단계: 인코더-디코더 크로스 멀티헤드 어텐션
async function executeCrossMultiHead() {
    const container = document.getElementById('crossMultiContainer');
    container.innerHTML = '';
    const headBox = document.createElement('div');
    headBox.className = 'head-box';
    headBox.innerHTML = `
        <div class="head-title" style="color:#2e7d32">Cross Attention (Encoder ↔ Decoder)</div>
        <div class="tokens-row" id="cross-tokens"></div>
        <div class="attention-matrix" id="cross-matrix"></div>
    `;
    container.appendChild(headBox);
    const trow = document.getElementById('cross-tokens');
    // 열(Columns): 디코더 토큰들
    trow.innerHTML = '';
    translatedTokens.forEach(t=>{const d=document.createElement('div'); d.className='token'; d.textContent=t; trow.appendChild(d);});
    const m = document.getElementById('cross-matrix');
    const size = Math.min(tokens.length, translatedTokens.length, 6);
    m.style.display='grid'; m.style.gridTemplateColumns = `80px repeat(${size}, 50px)`;
    m.appendChild(document.createElement('div'));
    // 상단 열 라벨: 디코더 토큰들
    for (let j=0;j<size;j++){
        const c=document.createElement('div'); c.className='matrix-label'; c.textContent=translatedTokens[j]||`단어${j+1}`; m.appendChild(c);
    }
    // 행 라벨: 인코더 토큰들
    for (let i=0;i<size;i++){
        const r=document.createElement('div'); r.className='matrix-label'; r.textContent=tokens[i]||`단어${i+1}`; m.appendChild(r);
        for (let j=0;j<size;j++){
            const w=0.1+Math.random()*0.8; const cell=document.createElement('div'); cell.className='attention-cell'; cell.textContent=w.toFixed(2); cell.style.backgroundColor=getAttentionColor(w); m.appendChild(cell);
        }
    }
}

// 5단계: 학습 후 디코더 출력 (그레이스케일 히트맵)
async function executeDecoderOutputAfter() {
    const container = document.getElementById('decoderOutputAfter');
    if (!container) return;
    container.innerHTML = '';

    // 학습된 헤드 사용: __currHeads는 로짓, rowSoftmaxMatrix로 확률 변환
    let A = null; // [nEnc][nDec] 유사 행렬(여기선 nTok x nTok)
    try {
        const L = window.__currHeads?.length || 0;
        const H = L ? window.__currHeads[0]?.length || 0 : 0;
        if (L && H) {
            const l = window.__selectedLayer ?? 0;
            const h = window.__selectedHead ?? 0;
            const logits = window.__currHeads[l][h];
            A = rowSoftmaxMatrix(logits);
        }
    } catch(e) { console.warn('학습 후 행렬 사용 불가, 휴리스틱 사용', e); }

    // 백업: 간단 정렬 휴리스틱 (동일 인덱스에 피크 부여)
    const n = Math.min(tokens.length, translatedTokens.length, 8);
    if (!A) {
        A = Array.from({length: n}, (_,i)=> Array.from({length:n}, (_,j)=> {
            const base = Math.max(0, 1 - Math.abs(i-j)*0.6);
            return base + Math.random()*0.05;
        }));
        A = normalizeRows(A);
    } else {
        // 크기 보정: 시각을 위해 상위 n토큰으로 제한
        A = A.slice(0, n).map(row => row.slice(0, n));
        A = normalizeRows(A);
    }

    // 그리드 구성: 열=영어(디코더), 행=한국어(인코더)
    container.style.display = 'grid';
    container.style.gridTemplateColumns = `80px repeat(${n}, 44px)`;
    container.style.gridAutoRows = '44px';
    container.style.gap = '4px';

    // 좌상단 빈칸
    const empty = document.createElement('div');
    empty.className = 'matrix-label';
    empty.style.visibility = 'hidden';
    container.appendChild(empty);

    // 상단 열 라벨: EN
    for (let j=0;j<n;j++) {
        const lab = document.createElement('div'); lab.className='matrix-label';
        lab.textContent = translatedTokens[j] || `EN${j+1}`; lab.title = `Decoder: ${translatedTokens[j]||''}`;
        container.appendChild(lab);
    }

    // 셀 채우기
    for (let i=0;i<n;i++) {
        // 행 라벨: KR
        const rlab = document.createElement('div'); rlab.className='matrix-label';
        rlab.textContent = tokens[i] || `KR${i+1}`; rlab.title = `Encoder: ${tokens[i]||''}`;
        container.appendChild(rlab);

        // 각 열 셀
        const row = A[i];
        const maxv = Math.max(...row);
        const maxj = row.indexOf(maxv);
        for (let j=0;j<n;j++) {
            const v = row[j];
            const cell = document.createElement('div');
            cell.className = 'attention-cell';
            cell.style.backgroundColor = getGrayColor(v);
            cell.style.color = v > 0.75*maxv ? '#000' : '#222';
            cell.textContent = v.toFixed(2);
            const kr = tokens[i]||''; const en = translatedTokens[j]||'';
            cell.title = `${kr} ↔ ${en}: ${v.toFixed(3)}`;
            if (j === maxj) {
                cell.style.outline = '2px solid #111';
                cell.style.boxShadow = 'inset 0 0 0 2px rgba(255,255,255,0.6)';
            }
            container.appendChild(cell);
        }
    }
}

function getGrayColor(w){
    // 0..1 -> 밝기 40(짙은회색) .. 230(연한회색): 값이 클수록 더 밝게
    const clamped = Math.max(0, Math.min(1, w));
    const v = Math.round(40 + clamped * 190); // 40..230
    return `rgb(${v},${v},${v})`;
}

// 🔄 4단계: 셀프 어텐션 실행
async function executeSelfAttention() {
    const tokensDiv = document.getElementById('selfAttentionTokens');
    const matrixDiv = document.getElementById('selfAttentionMatrix');
    
    // 번역된 토큰 표시
    tokensDiv.innerHTML = '';
    translatedTokens.forEach((token, i) => {
        const tokenDiv = document.createElement('div');
        tokenDiv.className = 'token';
        tokenDiv.textContent = token;
        tokenDiv.onclick = () => showSelfAttentionConnections(i);
        tokensDiv.appendChild(tokenDiv);
    });
    
    // 셀프 어텐션 매트릭스 (행/열 라벨 포함)
    const matrixSize = translatedTokens.length;
    matrixDiv.style.gridTemplateColumns = `80px repeat(${matrixSize}, 50px)`;
    matrixDiv.innerHTML = '';
    
    // 좌상단 빈 셀
    const emptyCell = document.createElement('div');
    emptyCell.className = 'matrix-label-cell empty';
    matrixDiv.appendChild(emptyCell);
    
    // 상단 열 라벨 (같은 문장)
    for (let j = 0; j < matrixSize; j++) {
        const colLabel = document.createElement('div');
        colLabel.className = 'matrix-label-cell column-label';
        colLabel.textContent = translatedTokens[j] || `단어${j+1}`;
        colLabel.title = `Target: ${translatedTokens[j]}`;
        matrixDiv.appendChild(colLabel);
    }
    
    // 각 행
    for (let i = 0; i < matrixSize; i++) {
        // 왼쪽 행 라벨 (같은 문장)
        const rowLabel = document.createElement('div');
        rowLabel.className = 'matrix-label-cell row-label';
        rowLabel.textContent = translatedTokens[i] || `단어${i+1}`;
        rowLabel.title = `Source: ${translatedTokens[i]}`;
        matrixDiv.appendChild(rowLabel);
        
        // 셀프 어텐션 값 셀들
        for (let j = 0; j < matrixSize; j++) {
            await sleep(70);
            const cell = document.createElement('div');
            cell.className = 'attention-cell';
            
            // 셀프 어텐션 가중치 (초기화된 값)
            const weight = calculateSelfAttentionWeight(i, j);
            cell.style.backgroundColor = getAttentionColor(weight);
            cell.textContent = weight.toFixed(3);
            cell.title = `${translatedTokens[i]} → ${translatedTokens[j]}: ${weight.toFixed(4)}`;
            
            matrixDiv.appendChild(cell);
        }
    }
}

// 🎯 5단계: 멀티헤드 어텐션 실행
async function executeMultiHeadAttention() {
    const container = document.getElementById('multiheadContainer');
    container.innerHTML = '';
    
    const heads = [
        { name: 'Head 1: 문법 구조', color: '#e91e63', focus: 'syntax' },
        { name: 'Head 2: 의미 관계', color: '#9c27b0', focus: 'semantic' },
        { name: 'Head 3: 위치 정보', color: '#673ab7', focus: 'position' },
        { name: 'Head 4: 문맥 흐름', color: '#3f51b5', focus: 'context' }
    ];
    
    for (let headIndex = 0; headIndex < heads.length; headIndex++) {
        await sleep(300);
        
        const headBox = document.createElement('div');
        headBox.className = 'head-box fade-in';
        headBox.innerHTML = `
            <div class="head-title" style="color: ${heads[headIndex].color}">
                ${heads[headIndex].name}
            </div>
            <div class="tokens-row" id="head-${headIndex}-tokens"></div>
            <div class="attention-matrix" id="head-${headIndex}-matrix"></div>
        `;
        
        container.appendChild(headBox);
        
        // 각 헤드의 토큰과 어텐션 표시
        await generateHeadAttention(headIndex, heads[headIndex]);
    }
}

// 🎉 6단계: 최종 결과 시각화
async function executeFinalVisualization() {
    const container = document.getElementById('finalVisualization');
    
    container.innerHTML = `
        <h4>🎊 최종 어텐션 결과 종합</h4>
        <div class="tokens-row" id="finalTokens"></div>
        <div id="finalMatrix" class="attention-matrix"></div>
        <div style="margin-top: 30px; text-align: center;">
            <h4>🔧 어텐션 매트릭스 생성 완료</h4>
            <p>다음 단계에서 모델을 학습시켜 어텐션 패턴을 최적화할 수 있습니다.</p>
        </div>
    `;
    
    // 최종 번역된 토큰 표시
    const finalTokensDiv = document.getElementById('finalTokens');
    translatedTokens.forEach((token, i) => {
        const tokenDiv = document.createElement('div');
        tokenDiv.className = 'token focused bounce';
        tokenDiv.textContent = token;
        finalTokensDiv.appendChild(tokenDiv);
    });
    
    // 종합 어텐션 매트릭스 (행열 단어 라벨 포함)
    const finalMatrixDiv = document.getElementById('finalMatrix');
    const matrixSize = translatedTokens.length;
    
    // 그리드 레이아웃 설정 (라벨 공간 포함)
    finalMatrixDiv.style.display = 'grid';
    finalMatrixDiv.style.gridTemplateColumns = `80px repeat(${matrixSize}, 50px)`;
    finalMatrixDiv.style.gridTemplateRows = `50px repeat(${matrixSize}, 50px)`;
    finalMatrixDiv.style.gap = '3px';
    finalMatrixDiv.style.alignItems = 'center';
    finalMatrixDiv.style.justifyItems = 'center';
    
    // 빈 셀 (좌상단)
    const emptyCell = document.createElement('div');
    emptyCell.style.width = '80px';
    emptyCell.style.height = '50px';
    finalMatrixDiv.appendChild(emptyCell);
    
    // 상단 열 라벨 (To)
    for (let j = 0; j < matrixSize; j++) {
        const colLabel = document.createElement('div');
        colLabel.className = 'matrix-label col-label';
        colLabel.style.width = '50px';
        colLabel.style.height = '50px';
        colLabel.style.fontSize = '0.7em';
        colLabel.style.fontWeight = 'bold';
        colLabel.style.color = '#666';
        colLabel.style.display = 'flex';
        colLabel.style.alignItems = 'center';
        colLabel.style.justifyContent = 'center';
        colLabel.style.background = '#f0f7ff';
        colLabel.style.borderRadius = '5px';
        colLabel.textContent = translatedTokens[j];
        finalMatrixDiv.appendChild(colLabel);
    }
    
    // 매트릭스 행들 (행 라벨 + 셀들)
    for (let i = 0; i < matrixSize; i++) {
        // 좌측 행 라벨 (From)
        const rowLabel = document.createElement('div');
        rowLabel.className = 'matrix-label row-label';
        rowLabel.style.width = '80px';
        rowLabel.style.height = '50px';
        rowLabel.style.fontSize = '0.7em';
        rowLabel.style.fontWeight = 'bold';
        rowLabel.style.color = '#666';
        rowLabel.style.display = 'flex';
        rowLabel.style.alignItems = 'center';
        rowLabel.style.justifyContent = 'center';
        rowLabel.style.background = '#fff0f0';
        rowLabel.style.borderRadius = '5px';
        rowLabel.textContent = translatedTokens[i];
        finalMatrixDiv.appendChild(rowLabel);
        
        // 어텐션 셀들
        for (let j = 0; j < matrixSize; j++) {
            await sleep(100);
            const cell = document.createElement('div');
            cell.className = 'attention-cell bounce';
            cell.style.width = '50px';
            cell.style.height = '50px';
            
            // 모든 헤드의 평균 어텐션
            const avgWeight = (
                calculateAttentionWeight(i, j) + 
                calculateSelfAttentionWeight(i, j) + 
                Math.random() * 0.3
            ) / 3;
            
            // 전역 어텐션 매트릭스에 저장
            if (!finalAttentionMatrix[i]) finalAttentionMatrix[i] = [];
            finalAttentionMatrix[i][j] = avgWeight;
            
            cell.style.backgroundColor = getAttentionColor(avgWeight);
            cell.textContent = avgWeight.toFixed(2);
            cell.title = `${translatedTokens[i]} → ${translatedTokens[j]}: ${avgWeight.toFixed(3)}`;
            
            finalMatrixDiv.appendChild(cell);
        }
    }
    
    // 완료 효과
    setTimeout(() => {
        container.classList.add('bounce');
        isSimulationRunning = false;
    }, 1000);
}

// 🔍 Q, K, V 매트릭스 생성 시뮬레이션
async function generateQKVMatrices() {
    const queryMatrix = document.getElementById('queryMatrix');
    const keyMatrix = document.getElementById('keyMatrix');
    const valueMatrix = document.getElementById('valueMatrix');
    
    // 학습 전 초기화된 Q 매트릭스 (작은 랜덤 값들)
    queryMatrix.innerHTML = '';
    for (let i = 0; i < 9; i++) {
        await sleep(100);
        const cell = document.createElement('div');
        cell.className = 'mini-cell fade-in';
        const value = (Math.random() * 0.2 + 0.01).toFixed(3); // 0.01~0.21
        cell.textContent = value;
        cell.style.backgroundColor = `rgba(76, 175, 80, 0.3)`;
        cell.title = `초기화된 Query 값: ${value}`;
        queryMatrix.appendChild(cell);
    }
    
    await sleep(200);
    
    // 학습 전 초기화된 K 매트릭스
    keyMatrix.innerHTML = '';
    for (let i = 0; i < 9; i++) {
        await sleep(100);
        const cell = document.createElement('div');
        cell.className = 'mini-cell fade-in';
        const value = (Math.random() * 0.2 + 0.01).toFixed(3);
        cell.textContent = value;
        cell.style.backgroundColor = `rgba(255, 152, 0, 0.3)`;
        cell.title = `초기화된 Key 값: ${value}`;
        keyMatrix.appendChild(cell);
    }
    
    await sleep(200);
    
    // 학습 전 초기화된 V 매트릭스
    valueMatrix.innerHTML = '';
    for (let i = 0; i < 9; i++) {
        await sleep(100);
        const cell = document.createElement('div');
        cell.className = 'mini-cell fade-in';
        const value = (Math.random() * 0.2 + 0.01).toFixed(3);
        cell.textContent = value;
        cell.style.backgroundColor = `rgba(156, 39, 176, 0.3)`;
        cell.title = `초기화된 Value 값: ${value}`;
        valueMatrix.appendChild(cell);
    }
    
    await sleep(500);
}

// 🧮 헬퍼 함수들
function calculateAttentionWeight(i, j) {
    // 학습 전 초기화된 어텐션 - 무작위 패턴
    return 0.05 + Math.random() * 0.2; // 0.05~0.25 범위의 작은 값
}

function calculateSelfAttentionWeight(i, j) {
    // 학습 전 초기화된 어텐션 - 거의 무작위 패턴
    if (i === j) return 0.15 + Math.random() * 0.1; // 자기 자신도 크지 않음
    return 0.05 + Math.random() * 0.15; // 대부분 작은 값
}

function getAttentionColor(weight) {
    const intensity = Math.min(255, Math.floor(weight * 255));
    return `rgb(${Math.floor(intensity * 0.3)}, ${Math.floor(intensity * 0.6)}, ${intensity})`;
}

async function generateHeadAttention(headIndex, headInfo) {
    const tokensDiv = document.getElementById(`head-${headIndex}-tokens`);
    const matrixDiv = document.getElementById(`head-${headIndex}-matrix`);
    
    // 번역된 토큰 표시
    translatedTokens.forEach(token => {
        const tokenDiv = document.createElement('div');
        tokenDiv.className = 'token';
        tokenDiv.style.fontSize = '0.8em';
        tokenDiv.textContent = token;
        tokensDiv.appendChild(tokenDiv);
    });
    
    // 헤드별 특성화된 어텐션 매트릭스 (행열 단어 라벨 포함)
    const matrixSize = Math.min(translatedTokens.length, 4); // 작은 매트릭스
    const displayTokens = translatedTokens.slice(0, matrixSize);
    
    // 그리드 레이아웃 설정 (라벨 공간 포함)
    matrixDiv.style.display = 'grid';
    matrixDiv.style.gridTemplateColumns = `60px repeat(${matrixSize}, 30px)`;
    matrixDiv.style.gridTemplateRows = `40px repeat(${matrixSize}, 30px)`;
    matrixDiv.style.gap = '2px';
    matrixDiv.style.alignItems = 'center';
    matrixDiv.style.justifyItems = 'center';
    
    // 빈 셀 (좌상단)
    const emptyCell = document.createElement('div');
    emptyCell.style.width = '60px';
    emptyCell.style.height = '40px';
    matrixDiv.appendChild(emptyCell);
    
    // 상단 열 라벨 (To)
    for (let j = 0; j < matrixSize; j++) {
        const colLabel = document.createElement('div');
        colLabel.className = 'matrix-label col-label';
        colLabel.style.width = '30px';
        colLabel.style.height = '40px';
        colLabel.style.fontSize = '0.6em';
        colLabel.style.fontWeight = 'bold';
        colLabel.style.color = '#666';
        colLabel.style.display = 'flex';
        colLabel.style.alignItems = 'center';
        colLabel.style.justifyContent = 'center';
        colLabel.style.background = '#f0f7ff';
        colLabel.style.borderRadius = '3px';
        colLabel.textContent = displayTokens[j];
        matrixDiv.appendChild(colLabel);
    }
    
    // 매트릭스 행들 (행 라벨 + 셀들)
    for (let i = 0; i < matrixSize; i++) {
        // 좌측 행 라벨 (From)
        const rowLabel = document.createElement('div');
        rowLabel.className = 'matrix-label row-label';
        rowLabel.style.width = '60px';
        rowLabel.style.height = '30px';
        rowLabel.style.fontSize = '0.6em';
        rowLabel.style.fontWeight = 'bold';
        rowLabel.style.color = '#666';
        rowLabel.style.display = 'flex';
        rowLabel.style.alignItems = 'center';
        rowLabel.style.justifyContent = 'center';
        rowLabel.style.background = '#fff0f0';
        rowLabel.style.borderRadius = '3px';
        rowLabel.textContent = displayTokens[i];
        matrixDiv.appendChild(rowLabel);
        
        // 어텐션 셀들
        for (let j = 0; j < matrixSize; j++) {
            await sleep(50);
            const cell = document.createElement('div');
            cell.className = 'attention-cell';
            cell.style.width = '30px';
            cell.style.height = '30px';
            cell.style.fontSize = '0.7em';
            
            let weight;
            switch(headInfo.focus) {
                case 'syntax': weight = (i === j) ? 0.8 : Math.random() * 0.4; break;
                case 'semantic': weight = Math.random() * 0.7 + 0.2; break;
                case 'position': weight = 1 - Math.abs(i - j) * 0.3; break;
                case 'context': weight = (Math.abs(i - j) === 1) ? 0.9 : Math.random() * 0.3; break;
                default: weight = Math.random() * 0.6;
            }
            
            cell.style.backgroundColor = getAttentionColor(weight);
            cell.textContent = weight.toFixed(1);
            cell.title = `${displayTokens[i]} → ${displayTokens[j]}: ${weight.toFixed(2)}`;
            matrixDiv.appendChild(cell);
        }
    }
}

function highlightAttentionWeights(tokenIndex) {
    const tokens = document.querySelectorAll('#attentionTokens .token');
    tokens.forEach((token, i) => {
        token.classList.toggle('selected', i === tokenIndex);
    });
    
    // 해당 행과 열 강조
    const cells = document.querySelectorAll('#attentionWeights .attention-cell');
    const matrixSize = tokens.length;
    cells.forEach((cell, index) => {
        const row = Math.floor(index / matrixSize);
        const col = index % matrixSize;
        cell.style.transform = (row === tokenIndex || col === tokenIndex) ? 'scale(1.2)' : 'scale(1)';
    });
}

function showSelfAttentionConnections(tokenIndex) {
    const tokens = document.querySelectorAll('#selfAttentionTokens .token');
    tokens.forEach((token, i) => {
        token.classList.toggle('focused', i === tokenIndex);
        token.classList.toggle('selected', Math.abs(i - tokenIndex) <= 1);
    });
}

function highlightConnection(row, col) {
    // 어텐션 매트릭스에서 특정 셀 클릭 시 연결 강조
    const tokens = document.querySelectorAll('#attentionTokens .token');
    tokens.forEach((token, i) => {
        token.classList.remove('selected', 'focused');
        if (i === row) token.classList.add('focused');
        if (i === col) token.classList.add('selected');
    });
    
    // 해당 셀 강조
    const cells = document.querySelectorAll('#attentionWeights .attention-cell');
    const matrixSize = tokens.length;
    cells.forEach((cell, index) => {
        const cellRow = Math.floor(index / matrixSize);
        const cellCol = index % matrixSize;
        cell.style.transform = (cellRow === row && cellCol === col) ? 'scale(1.3)' : 'scale(1)';
        cell.style.zIndex = (cellRow === row && cellCol === col) ? '10' : '1';
    });
}

// 🔄 초기화
function resetSimulation() {
    currentStep = -1;
    isSimulationRunning = false;
    isModelTrained = false;
    finalAttentionMatrix = [];
    trainingData = { beforeAttention: [], afterAttention: [], beforeSelfAttention: [], afterSelfAttention: [] };
    
    document.getElementById('progressFill').style.width = '0%';
    document.getElementById('currentStep').textContent = '단계별 시뮬레이션을 시작하세요!';
    document.getElementById('stepExplanation').textContent = '단계별 시뮬레이션을 시작하면 각 단계의 상세한 설명이 여기에 표시됩니다.';
    
    document.querySelectorAll('.step-section').forEach(section => {
        section.classList.remove('active');
    });
    
    document.querySelectorAll('.step-circle').forEach(circle => {
        circle.classList.remove('active', 'completed');
    });
    // 학습 UI 초기화
    const trainingProgress = document.getElementById('trainingProgress');
    if (trainingProgress) trainingProgress.style.width = '0%';
    const trainingStatus = document.getElementById('trainingStatus');
    if (trainingStatus) trainingStatus.textContent = '학습 대기 중...';
    const lossText = document.getElementById('lossText');
    if (lossText) lossText.textContent = '손실: -';
    const before = document.getElementById('attentionBefore');
    const after = document.getElementById('attentionAfter');
    const sab = document.getElementById('selfAttentionBefore');
    const saa = document.getElementById('selfAttentionAfter');
    if (before) before.innerHTML = '';
    if (after) after.innerHTML = '';
    if (sab) sab.innerHTML = '';
    if (saa) saa.innerHTML = '';
    // (제거됨) 보조 패널 초기화 코드
}

// 🎓 학습 과정 시뮬레이션 (이전 단계 어텐션 기반)
async function executeTrainingSimulation() {
    console.log('🎓 학습 과정 시뮬레이션 시작');
    
    // 토큰 존재 확인 (없으면 1단계를 유도)
    if (!tokens || tokens.length === 0) {
        alert('먼저 1단계를 진행해주세요!');
        return;
    }
    
    // 전/후 비교의 '전' 상태: 멀티 레이어/헤드 생성 후 기본(0,0)으로 렌더
    const tokensView = tokens.slice(0, Math.min(tokens.length, 8));
    const n = tokensView.length; if (n === 0) return;
    const baseW = ensureSquareMatrixFromFinal(tokensView);
    const V = sampleValueVectors(n, 8);
    const L = 3, H = 4; // 레이어/헤드 수(시뮬)
    const W0Heads = makeHeadVariants(baseW, L, H);
    // 캐시
    window.__Vrand = V; window.__tokensViewN = n;
    window.__W0Heads = W0Heads; // [L][H][n][n]
    window.__selectedLayer = 0; window.__selectedHead = 0;
    try {
        renderContextPanel('Before', W0Heads[0][0], V, 0);
        updateContextTitles(0,0, null);
    } catch(e) { console.warn(e); }
    
    // 학습 과정은 자동으로 시작하지 않고 버튼 클릭 대기
    document.getElementById('trainingStatus').textContent = '학습 버튼을 클릭하세요';
}

// 학습 시작 함수 (실제 어텐션 최적화)
async function startTraining() {
    if (isModelTrained) {
        alert('모델이 이미 학습되었습니다!');
        return;
    }
    
    const progressBar = document.getElementById('trainingProgress');
    const statusDiv = document.getElementById('trainingStatus');
    const lossText = document.getElementById('lossText');
    
    statusDiv.textContent = '학습 중...';
    
    // 멀티 레이어/헤드 학습 준비
    const W0Heads = window.__W0Heads; // [L][H][n][n]
    const nTok = window.__tokensViewN || Math.min(tokens.length, 6);
    const L = W0Heads?.length || 0; const H = (W0Heads && W0Heads[0]) ? W0Heads[0].length : 0;
    if (!W0Heads || !L || !H) { alert('초기 헤드 구성이 없습니다. 4단계를 다시 시작하세요.'); return; }
    // current: 로짓(logits), target: 정규화된 확률
    const current = Array.from({length:L}, (_,l)=> Array.from({length:H}, (_,h)=> toLogits(W0Heads[l][h])));
    const target = Array.from({length:L}, (_,l)=> Array.from({length:H}, (_,h)=> normalizeRows(sharpenTarget(W0Heads[l][h], 1.8))));
    // 이전 버전의 보조 매트릭스 렌더링 블록 제거됨 (undefined 변수 사용으로 오류 유발)
    
    // 손실 기반 반복 학습 시뮬레이션 (교차 엔트로피 유사)
    const epochs = 50; // 요청: 약 50회
    const lr = 0.70;   // 요청: 수렴 가속을 위한 학습률 추가 상향
    for (let epoch = 1; epoch <= epochs; epoch++) {
        let loss = 0;
        for (let l=0;l<L;l++){
            for (let h=0;h<H;h++){
                for (let i = 0; i < nTok; i++) {
                    const row = current[l][h][i].slice();
                    const trow = target[l][h][i].slice();
                    // 확률(softmax)
                    const maxVal = Math.max(...row);
                    const exp = row.map(v => Math.exp(v - maxVal));
                    const sumExp = exp.reduce((a,b)=>a+b,0) || 1;
                    const prob = exp.map(v => v / sumExp);
                    // 타깃 확률: 이미 정규화되어 있음
                    const tprob = trow;
                    // CE 손실
                    const eps = 1e-8;
                    loss += -tprob.reduce((acc, tp, j) => acc + tp * Math.log((prob[j]||eps) + eps), 0);
                    // 로짓 업데이트
                    for (let j = 0; j < nTok; j++) {
                        const grad = tprob[j] - prob[j];
                        current[l][h][i][j] = row[j] + lr * grad;
                    }
                }
            }
        }
        const progress = Math.round((epoch / epochs) * 100);
        progressBar.style.width = progress + '%';
        statusDiv.textContent = `학습 진행 중... ${progress}% (에폭 ${epoch}/${epochs})`;
        if (lossText) lossText.textContent = `손실(CE≈): ${loss.toFixed(4)}`;
        await sleep(400);
    }
    
    // 가장 변화가 큰 레이어/헤드 선택
    const deltas = [];
    for (let l=0;l<L;l++){
        for (let h=0;h<H;h++){
            const d = meanAbsDiff( rowSoftmaxMatrix(current[l][h]), rowSoftmaxMatrix(W0Heads[l][h]) );
            deltas.push({l,h,d});
        }
    }
    deltas.sort((a,b)=> b.d - a.d);
    const best = deltas[0];
    window.__currHeads = current; // 학습 후 행렬 저장(로짓)
    window.__selectedLayer = best.l; window.__selectedHead = best.h;
    // Before/After 모두 선택된 헤드 기준으로 렌더
    const Vuse = window.__Vrand || sampleValueVectors(nTok, 8);
    try {
    const ABefore = W0Heads[best.l][best.h];
    const AAfter = rowSoftmaxMatrix(current[best.l][best.h]);
        // 쿼리 행 선택: 구두점 토큰은 가능하면 제외하고, After에서 최댓값이 큰 행을 선호
        const isPunc = (t)=> (/^[.,!?;:·…“”"'`~\\-]+$/.test((t||'').trim()));
        const scored = [];
        for (let i=0;i<AAfter.length;i++){
            const maxRow = Math.max(...AAfter[i]);
            const tok = (typeof tokens!=='undefined' && tokens[i]) ? tokens[i] : '';
            const penalty = isPunc(tok) ? 1 : 0; // 구두점이면 불이익
            scored.push({i, score: maxRow - penalty});
        }
        scored.sort((a,b)=> b.score - a.score);
        const iBest = (scored.length? scored[0].i : 0);
        window.__selectedRow = iBest;
        renderContextPanel('Before', ABefore, Vuse, iBest);
        renderContextPanel('After', AAfter, Vuse, iBest);
        const afterPanel = document.getElementById('contextAfter');
        if (afterPanel) afterPanel.style.display = 'block';
        updateContextTitles(best.l, best.h, best.d);
    } catch(e) { console.warn(e); }
    // 상관관계 강한 단어쌍 비교(패널이 있었다면) 갱신
    generateTopCorrelationsComparison();
    
    isModelTrained = true;
    statusDiv.textContent = '✅ 학습 완료! 상관관계가 강한 단어쌍들을 확인하세요.';
}

// 학습 전 데이터 생성 (6단계 어텐션 매트릭스 기반)
function generateBeforeTrainingData() {
    // 상관관계가 강한 단어쌍 찾기 (상위 4-6개)
    const topCorrelations = findTopCorrelations(finalAttentionMatrix, 6);
    const matrixSize = topCorrelations.length;
    
    // 학습 전 어텐션 매트릭스 (선별된 강한 상관관계만)
    const attentionBefore = document.getElementById('attentionBefore');
    attentionBefore.style.gridTemplateColumns = `60px repeat(${matrixSize}, 50px)`;
    attentionBefore.innerHTML = '';
    
    // 좌상단 빈 셀
    const emptyCell1 = document.createElement('div');
    emptyCell1.style.width = '60px';
    emptyCell1.style.height = '40px';
    attentionBefore.appendChild(emptyCell1);
    
    // 상단 열 라벨 (To)
    topCorrelations.forEach(corr => {
        const colLabel = document.createElement('div');
        colLabel.className = 'matrix-label';
        colLabel.style.fontSize = '0.6em';
        colLabel.style.backgroundColor = '#e3f2fd';
        colLabel.textContent = corr.toWord;
        attentionBefore.appendChild(colLabel);
    });
    
    // 매트릭스 데이터 (학습 전 - 노이즈 추가된 원본 데이터)
    topCorrelations.forEach((fromCorr, i) => {
        // 좌측 행 라벨 (From)
        const rowLabel = document.createElement('div');
        rowLabel.className = 'matrix-label';
        rowLabel.style.fontSize = '0.6em';
        rowLabel.style.backgroundColor = '#fff3e0';
        rowLabel.textContent = fromCorr.fromWord;
        attentionBefore.appendChild(rowLabel);
        
        // 데이터 셀들 (학습 전 - 약간의 노이즈와 함께)
        topCorrelations.forEach((toCorr, j) => {
            const cell = document.createElement('div');
            cell.className = 'attention-cell';
            cell.style.fontSize = '0.7em';
            
            // 원본 값에 약간의 노이즈 추가 (학습 전)
            const originalValue = finalAttentionMatrix[fromCorr.fromIndex] ? 
                                finalAttentionMatrix[fromCorr.fromIndex][toCorr.toIndex] || 0.1 : 0.1;
            const noisyValue = Math.max(0, originalValue + (Math.random() - 0.5) * 0.3);
            
            cell.style.backgroundColor = getAttentionColor(noisyValue);
            cell.textContent = noisyValue.toFixed(2);
            cell.title = `${fromCorr.fromWord} → ${toCorr.toWord}: ${noisyValue.toFixed(3)}`;
            
            attentionBefore.appendChild(cell);
        });
    });
}

// 🔍 상관관계가 강한 단어쌍 찾기 함수
function findTopCorrelations(attentionMatrix, topN = 6) {
    const correlations = [];
    
    // 모든 단어쌍의 어텐션 값 수집
    for (let i = 0; i < attentionMatrix.length; i++) {
        for (let j = 0; j < attentionMatrix[i].length; j++) {
            if (i !== j) { // 자기 자신 제외
                correlations.push({
                    fromIndex: i,
                    toIndex: j,
                    fromWord: tokens[i],
                    toWord: tokens[j],
                    weight: attentionMatrix[i][j]
                });
            }
        }
    }
    
    // 어텐션 값 기준으로 정렬하고 상위 N개 선택
    correlations.sort((a, b) => b.weight - a.weight);
    return correlations.slice(0, topN);
}

// 🎯 학습 후 데이터 생성 (최적화된 어텐션)
function generateAfterTrainingData() {
    const topCorrelations = findTopCorrelations(finalAttentionMatrix, 6);
    const matrixSize = topCorrelations.length;
    
    // 학습 후 어텐션 매트릭스 (최적화된 값들)
    const attentionAfter = document.getElementById('attentionAfter');
    attentionAfter.style.gridTemplateColumns = `60px repeat(${matrixSize}, 50px)`;
    attentionAfter.innerHTML = '';
    
    // 좌상단 빈 셀
    const emptyCell1 = document.createElement('div');
    emptyCell1.style.width = '60px';
    emptyCell1.style.height = '40px';
    attentionAfter.appendChild(emptyCell1);
    
    // 상단 열 라벨 (To)
    topCorrelations.forEach(corr => {
        const colLabel = document.createElement('div');
        colLabel.className = 'matrix-label';
        colLabel.style.fontSize = '0.6em';
        colLabel.style.backgroundColor = '#e8f5e8';
        colLabel.textContent = corr.toWord;
        attentionAfter.appendChild(colLabel);
    });
    
    // 매트릭스 데이터 (학습 후 - 최적화된 값들)
    topCorrelations.forEach((fromCorr, i) => {
        // 좌측 행 라벨 (From)
        const rowLabel = document.createElement('div');
        rowLabel.className = 'matrix-label';
        rowLabel.style.fontSize = '0.6em';
        rowLabel.style.backgroundColor = '#fff8e1';
        rowLabel.textContent = fromCorr.fromWord;
        attentionAfter.appendChild(rowLabel);
        
        // 데이터 셀들 (학습 후 - 최적화된 값)
        topCorrelations.forEach((toCorr, j) => {
            const cell = document.createElement('div');
            cell.className = 'attention-cell';
            cell.style.fontSize = '0.7em';
            
            // 학습 후 최적화된 값 (강한 연결은 더 강하게, 약한 연결은 더 약하게)
            const originalValue = finalAttentionMatrix[fromCorr.fromIndex] ? 
                                finalAttentionMatrix[fromCorr.fromIndex][toCorr.toIndex] || 0.1 : 0.1;
            const optimizedValue = originalValue > 0.5 ? 
                                 Math.min(0.95, originalValue * 1.3) :  // 강한 연결 강화
                                 Math.max(0.05, originalValue * 0.7);   // 약한 연결 약화
            
            cell.style.backgroundColor = getAttentionColor(optimizedValue);
            cell.textContent = optimizedValue.toFixed(2);
            cell.title = `${fromCorr.fromWord} → ${toCorr.toWord}: ${optimizedValue.toFixed(3)} (최적화됨)`;
            
            attentionAfter.appendChild(cell);
        });
    });
}

// 📊 상관관계 분석 비교 생성
function generateTopCorrelationsComparison() {
    const topCorrelations = findTopCorrelations(finalAttentionMatrix, 6);
    
    // 마스킹 정보 업데이트
    const maskingInfo = document.getElementById('maskingInfo');
    if (maskingInfo) {
        maskingInfo.innerHTML = `
            <h4>🎯 학습된 강한 상관관계 Top ${topCorrelations.length}</h4>
            <ul>
                ${topCorrelations.map((corr, index) => `
                    <li><strong>${index + 1}위:</strong> ${corr.fromWord} → ${corr.toWord} 
                        <span style="color: #2196f3;">(${(corr.weight * 100).toFixed(1)}%)</span>
                    </li>
                `).join('')}
            </ul>
            <p style="margin-top: 15px; color: #666;">
                💡 학습을 통해 의미적으로 관련성이 높은 단어들 간의 어텐션이 강화되었습니다.
            </p>
        `;
    }
}

// 🕸️ 최종 어텐션 그래프 생성 함수 (1단계 입력 기반)
async function generateFinalAttentionGraph() {
    if (!tokens || tokens.length === 0) {
        alert('먼저 1단계에서 문장을 입력하고 시뮬레이션을 진행해주세요!');
        return;
    }
    
    if (!isModelTrained) {
        alert('먼저 7/8단계(학습)에서 모델을 학습시켜주세요!');
        return;
    }
    
    // 학습 후 최적화된 어텐션 매트릭스 생성
    const attentionMatrix = generateLearnedAttentionMatrix(translatedTokens);
    
    // 그래프 시각화 (학습 후 결과만)
    await visualizeAttentionGraph(translatedTokens, attentionMatrix, 'learned');
    
    // 추가 분석 정보 표시
    showConnectionAnalysis(attentionMatrix, translatedTokens);
}

// 연결 분석 정보 표시
function showConnectionAnalysis(matrix, tokens) {
    // 기존 분석 메트릭들은 제거하고 간단한 정보만 표시
    console.log('학습 후 어텐션 그래프가 생성되었습니다.');
    console.log(`단어 수: ${tokens.length}, 연결 패턴: 최적화됨`);
}

// 🕸️ 어텐션 그래프 생성 함수
async function generateAttentionGraph() {
    const testInput = document.getElementById('testInput').value.trim();
    if (!testInput) {
        alert('테스트 문장을 입력해주세요!');
        return;
    }
    
    const tokens = tokenizeText(testInput);
    const graphMode = document.querySelector('input[name="graphMode"]:checked').value;
    
    // 어텐션 매트릭스 생성
    let attentionMatrix;
    if (graphMode === 'before') {
        attentionMatrix = generateRandomAttentionMatrix(tokens);
    } else if (graphMode === 'after') {
        attentionMatrix = generateLearnedAttentionMatrix(tokens);
    } else { // compare mode
        const beforeMatrix = generateRandomAttentionMatrix(tokens);
        const afterMatrix = generateLearnedAttentionMatrix(tokens);
        attentionMatrix = afterMatrix; // 기본으로 학습 후 사용
    }
    
    // 그래프 시각화
    await visualizeAttentionGraph(tokens, attentionMatrix, graphMode);
    
    // 분석 메트릭 계산 및 표시
    calculateGraphMetrics(attentionMatrix, tokens);
}

// 어텐션 그래프 시각화 함수
async function visualizeAttentionGraph(tokens, matrix, mode) {
    const svg = document.getElementById('graphSvg');
    svg.innerHTML = ''; // 기존 내용 클리어
    
    const width = 600;
    const height = 400;
    const centerX = width / 2;
    const centerY = height / 2;
    const radius = Math.min(width, height) * 0.25;
    
    // 노드 위치 계산 (원형 배치)
    const nodePositions = [];
    for (let i = 0; i < tokens.length; i++) {
        const angle = (2 * Math.PI * i) / tokens.length - Math.PI / 2;
        const x = centerX + radius * Math.cos(angle);
        const y = centerY + radius * Math.sin(angle);
        nodePositions.push({ x, y });
    }
    
    // 제목 추가
    const title = document.createElementNS('<http://www.w3.org/2000/svg>', 'text');
    title.setAttribute('x', centerX);
    title.setAttribute('y', 30);
    title.setAttribute('text-anchor', 'middle');
    title.setAttribute('font-size', '16');
    title.setAttribute('font-weight', 'bold');
    title.setAttribute('fill', '#333');
    title.textContent = '학습 후 단어간 어텐션 연결 강도';
    svg.appendChild(title);
    
    // 연결선 그리기 (어텐션 강도에 따라)
    for (let i = 0; i < tokens.length; i++) {
        for (let j = 0; j < tokens.length; j++) {
            if (i !== j) {
                const attention = matrix[i][j];
                if (attention > 0.1) { // 임계값 이상만 표시
                    await sleep(50); // 부드러운 애니메이션
                    
                    const line = document.createElementNS('<http://www.w3.org/2000/svg>', 'line');
                    line.setAttribute('x1', nodePositions[i].x);
                    line.setAttribute('y1', nodePositions[i].y);
                    line.setAttribute('x2', nodePositions[j].x);
                    line.setAttribute('y2', nodePositions[j].y);
                    
                    // 어텐션 강도에 따른 스타일링
                    let strokeColor, strokeWidth, opacity;
                    if (attention > 0.7) {
                        strokeColor = '#ff4444';
                        strokeWidth = 4;
                        opacity = 0.9;
                    } else if (attention > 0.3) {
                        strokeColor = '#ffaa00';
                        strokeWidth = 3;
                        opacity = 0.7;
                    } else {
                        strokeColor = '#cccccc';
                        strokeWidth = 2;
                        opacity = 0.5;
                    }
                    
                    line.setAttribute('stroke', strokeColor);
                    line.setAttribute('stroke-width', strokeWidth);
                    line.setAttribute('opacity', opacity);
                    line.setAttribute('class', 'graph-edge');
                    
                    // 툴팁 추가
                    const title = document.createElementNS('<http://www.w3.org/2000/svg>', 'title');
                    title.textContent = `${tokens[i]} → ${tokens[j]}: ${(attention * 100).toFixed(1)}%`;
                    line.appendChild(title);
                    
                    svg.appendChild(line);
                }
            }
        }
    }
    
    // 노드 그리기
    for (let i = 0; i < tokens.length; i++) {
        await sleep(100);
        
        const circle = document.createElementNS('<http://www.w3.org/2000/svg>', 'circle');
        circle.setAttribute('cx', nodePositions[i].x);
        circle.setAttribute('cy', nodePositions[i].y);
        circle.setAttribute('r', 25);
        circle.setAttribute('fill', '#4285f4');
        circle.setAttribute('stroke', 'white');
        circle.setAttribute('stroke-width', 3);
        circle.setAttribute('class', 'graph-node');
        svg.appendChild(circle);
        
        // 단어 레이블
        const text = document.createElementNS('<http://www.w3.org/2000/svg>', 'text');
        text.setAttribute('x', nodePositions[i].x);
        text.setAttribute('y', nodePositions[i].y + 5);
        text.setAttribute('class', 'node-label');
        text.textContent = tokens[i];
        svg.appendChild(text);
    }
}

// 그래프 분석 메트릭 계산
function calculateGraphMetrics(matrix, tokens) {
    const totalConnections = matrix.length * (matrix.length - 1);
    let activeConnections = 0;
    let totalStrength = 0;
    let strongConnections = 0;
    
    for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix.length; j++) {
            if (i !== j && matrix[i][j] > 0.1) {
                activeConnections++;
                totalStrength += matrix[i][j];
                if (matrix[i][j] > 0.5) {
                    strongConnections++;
                }
            }
        }
    }
    
    const avgStrength = totalStrength / activeConnections;
    const focusScore = (strongConnections / activeConnections) * 100;
    
    document.getElementById('connectionStrength').textContent = `${(avgStrength * 100).toFixed(1)}%`;
    document.getElementById('activeConnections').textContent = `${activeConnections}/${totalConnections}`;
    document.getElementById('focusScore').textContent = `${focusScore.toFixed(1)}%`;
}

// 🎭 마스킹 시뮬레이션 함수
async function simulateMasking() {
    const maskingSection = document.getElementById('maskingSimulation');
    maskingSection.style.display = 'block';
    
    const matrixSize = Math.min(translatedTokens.length, 4);
    const displayTokens = translatedTokens.slice(0, matrixSize);
    
    // 1. 원본 어텐션 매트릭스 생성
    await generateMaskingMatrix('originalMatrix', displayTokens, false);
    
    // 2초 후 마스킹 적용
    setTimeout(async () => {
        await generateMaskingMatrix('maskedMatrix', displayTokens, true);
    }, 2000);
}

// 마스킹 매트릭스 생성 함수
async function generateMaskingMatrix(containerId, tokens, applyMask) {
    const container = document.getElementById(containerId);
    container.innerHTML = '';
    
    const matrixSize = tokens.length;
    
    // 그리드 설정 (라벨에 전체 토큰 표시)
    container.style.display = 'grid';
    container.style.gridTemplateColumns = `auto repeat(${matrixSize}, auto)`;
    container.style.gridTemplateRows = `auto repeat(${matrixSize}, auto)`;
    container.style.gap = '1px';
    container.style.justifyItems = 'center';
    container.style.alignItems = 'center';
    
    // 빈 셀 (좌상단)
    const emptyCell = document.createElement('div');
    emptyCell.className = 'matrix-label-cell empty';
    container.appendChild(emptyCell);
    
    // 상단 열 라벨
    for (let j = 0; j < matrixSize; j++) {
        const colLabel = document.createElement('div');
        colLabel.className = 'matrix-label-cell column-label';
        colLabel.textContent = tokens[j];
        container.appendChild(colLabel);
    }
    
    // 매트릭스 행들
    for (let i = 0; i < matrixSize; i++) {
    // 행 라벨
    const rowLabel = document.createElement('div');
    rowLabel.className = 'matrix-label-cell row-label';
    rowLabel.textContent = tokens[i];
    container.appendChild(rowLabel);
        
        // 어텐션 셀들
        for (let j = 0; j < matrixSize; j++) {
            await sleep(100);
            
            const cell = document.createElement('div');
            cell.className = 'attention-cell';
            
            // 마스킹 적용 여부 확인 (상삼각 영역)
            const isMasked = applyMask && j > i;
            
            if (isMasked) {
                // 마스킹된 셀 (미래 토큰)
                cell.classList.add('masked-cell');
                cell.textContent = '-∞';
                cell.style.backgroundColor = '#ffebee';
                cell.style.color = '#d32f2f';
                cell.title = `마스킹: ${tokens[i]}는 미래 토큰 ${tokens[j]}를 볼 수 없습니다`;
            } else {
                // 정상 셀 (현재/과거 토큰)
                const weight = i === j ? 0.9 : Math.random() * 0.7 + 0.1;
                cell.style.backgroundColor = getAttentionColor(weight);
                cell.textContent = weight.toFixed(1);
                cell.title = `${tokens[i]} → ${tokens[j]}: ${weight.toFixed(2)}`;
            }
            
            container.appendChild(cell);
        }
    }
}

// 학습 전 무작위 어텐션 매트릭스 생성
function generateRandomAttentionMatrix(tokens) {
    const size = tokens.length;
    const matrix = [];
    
    for (let i = 0; i < size; i++) {
        matrix[i] = [];
        let rowSum = 0;
        
        // 무작위 값 생성
        for (let j = 0; j < size; j++) {
            const randomValue = Math.random() * 0.8 + 0.1; // 0.1 ~ 0.9
            matrix[i][j] = randomValue;
            rowSum += randomValue;
        }
        
        // 정규화: 각 행의 합이 정확히 1이 되도록
        for (let j = 0; j < size; j++) {
            matrix[i][j] = matrix[i][j] / rowSum;
        }
    }
    
    return matrix;
}

// 학습 후 의미적 어텐션 매트릭스 생성
function generateLearnedAttentionMatrix(tokens) {
    const size = tokens.length;
    const matrix = [];
    
    for (let i = 0; i < size; i++) {
        matrix[i] = [];
        let rowSum = 0;
        
        // 의미적 관련성을 고려한 값 생성
        for (let j = 0; j < size; j++) {
            let attention = 0.05; // 기본 낮은 어텐션
            
            // 자기 자신에게 높은 어텐션
            if (i === j) {
                attention = 0.4 + Math.random() * 0.3;
            }
            // 인접한 단어들에게 중간 어텐션
            else if (Math.abs(i - j) === 1) {
                attention = 0.15 + Math.random() * 0.15;
            }
            // 의미적으로 관련된 단어들
            else if (isSemanticallySimilar(tokens[i], tokens[j])) {
                attention = 0.2 + Math.random() * 0.2;
            }
            // 그 외는 낮은 어텐션
            else {
                attention = 0.02 + Math.random() * 0.08;
            }
            
            matrix[i][j] = attention;
            rowSum += attention;
        }
        
        // 정규화: 각 행의 합이 정확히 1이 되도록
        for (let j = 0; j < size; j++) {
            matrix[i][j] = matrix[i][j] / rowSum;
        }
    }
    
    return matrix;
}

// 의미적 유사성 판단
function isSemanticallySimilar(word1, word2) {
    const semanticGroups = [
        ['딥러닝', '머신러닝', '인공지능', 'AI', '학습', '신경망'],
        ['공부', '학습', '연구', '분석'],
        ['트랜스포머', '어텐션', '매트릭스', '모델'],
        ['데이터', '정보', '입력', '출력']
    ];
    
    return semanticGroups.some(group => 
        group.includes(word1) && group.includes(word2) && word1 !== word2
    );
}

// 비교용 매트릭스 시각화 (행/열 라벨 포함)
async function visualizeComparisonMatrix(containerId, matrix, tokens) {
    const container = document.getElementById(containerId);
    const size = Math.min(tokens.length, 6); // 최대 6x6
    
    // 그리드 레이아웃: 첫 번째 행과 열은 라벨용
    container.style.gridTemplateColumns = `80px repeat(${size}, 40px)`;
    container.innerHTML = '';
    
    // 좌상단 빈 셀
    const emptyCell = document.createElement('div');
    emptyCell.className = 'matrix-label-cell empty';
    container.appendChild(emptyCell);
    
    // 상단 열 라벨 (Query 또는 Target)
    for (let j = 0; j < size; j++) {
        await sleep(30);
        const colLabel = document.createElement('div');
        colLabel.className = 'matrix-label-cell column-label';
        colLabel.textContent = tokens[j];
        colLabel.title = `Target: ${tokens[j]}`;
        container.appendChild(colLabel);
    }
    
    // 각 행에 대해
    for (let i = 0; i < size; i++) {
        // 왼쪽 행 라벨 (Key/Value 또는 Source)
        const rowLabel = document.createElement('div');
        rowLabel.className = 'matrix-label-cell row-label';
        rowLabel.textContent = tokens[i];
        rowLabel.title = `Source: ${tokens[i]}`;
        container.appendChild(rowLabel);
        
        // 어텐션 값 셀들
        for (let j = 0; j < size; j++) {
            await sleep(50);
            
            const cell = document.createElement('div');
            cell.className = 'attention-cell';
            cell.style.width = '40px';
            cell.style.height = '40px';
            
            const weight = matrix[i][j];
            cell.style.backgroundColor = getAttentionColor(weight);
            cell.style.opacity = 0.3 + (weight * 0.7); // 더 명확한 투명도
            cell.textContent = weight.toFixed(3);
            cell.title = `${tokens[i]} → ${tokens[j]}: ${weight.toFixed(4)}`;
            
            // 클릭 이벤트
            cell.addEventListener('click', () => {
                highlightRelatedCells(container, i + 1, j + 1, size); // +1은 라벨 때문
            });
            
            container.appendChild(cell);
        }
    }
}

// 관련 셀 하이라이트 (라벨 구조 고려)
function highlightRelatedCells(container, row, col, size) {
    const allCells = container.querySelectorAll('.attention-cell, .matrix-label-cell');
    
    // 모든 셀 초기화
    allCells.forEach(cell => cell.classList.remove('highlighted'));
    
    // 라벨이 있는 구조에서 인덱스 계산
    // 그리드: [empty][col0][col1]...[colN]
    //        [row0][cell00][cell01]...[cell0N]
    //        [row1][cell10][cell11]...[cell1N]
    
    const totalCols = size + 1; // 라벨 열 포함
    
    // 선택된 행의 라벨 하이라이트
    const rowLabelIndex = row * totalCols;
    if (allCells[rowLabelIndex]) {
        allCells[rowLabelIndex].classList.add('highlighted');
    }
    
    // 선택된 열의 라벨 하이라이트
    const colLabelIndex = col;
    if (allCells[colLabelIndex]) {
        allCells[colLabelIndex].classList.add('highlighted');
    }
    
    // 선택된 행의 모든 어텐션 셀 하이라이트
    for (let j = 1; j <= size; j++) {
        const cellIndex = row * totalCols + j;
        if (allCells[cellIndex] && allCells[cellIndex].classList.contains('attention-cell')) {
            allCells[cellIndex].classList.add('highlighted');
        }
    }
    
    // 선택된 열의 모든 어텐션 셀 하이라이트
    for (let i = 1; i <= size; i++) {
        const cellIndex = i * totalCols + col;
        if (allCells[cellIndex] && allCells[cellIndex].classList.contains('attention-cell')) {
            allCells[cellIndex].classList.add('highlighted');
        }
    }
}

// 메트릭 계산 및 표시
function calculateAndDisplayMetrics(beforeMatrix, afterMatrix) {
    // 어텐션 집중도 (최고값들의 평균)
    const concentrationBefore = calculateConcentration(beforeMatrix);
    const concentrationAfter = calculateConcentration(afterMatrix);
    
    // 관련성 정확도 (의미적 관계 반영도)
    const relevanceBefore = calculateRelevance(beforeMatrix);
    const relevanceAfter = calculateRelevance(afterMatrix);
    
    // 전체 개선도
    const improvement = Math.round(
        ((concentrationAfter - concentrationBefore) + (relevanceAfter - relevanceBefore)) / 2
    );
    
    // 애니메이션으로 표시
    animateMetric('concentrationBefore', concentrationBefore, 'concentrationBeforeText');
    animateMetric('concentrationAfter', concentrationAfter, 'concentrationAfterText');
    animateMetric('relevanceBefore', relevanceBefore, 'relevanceBeforeText');
    animateMetric('relevanceAfter', relevanceAfter, 'relevanceAfterText');
    
    // 개선도 원형 차트
    setTimeout(() => {
        const improvementScore = Math.max(0, Math.min(100, improvement + 50)); // 기준점 조정
        document.getElementById('improvementScore').style.setProperty('--progress', improvementScore + '%');
        document.getElementById('improvementText').textContent = improvementScore + '%';
        
        const descriptions = [
            { min: 80, text: '🎉 혁신적 학습 성과!' },
            { min: 65, text: '✨ 탁월한 개선 효과!' },
            { min: 50, text: '👍 우수한 학습 결과' },
            { min: 35, text: '📈 긍정적 개선 감지' },
            { min: 0, text: '🔄 지속적 학습 필요' }
        ];
        
        const description = descriptions.find(d => improvementScore >= d.min).text;
        document.getElementById('improvementDescription').textContent = description;
    }, 1500);
}

// 집중도 계산 (높은 어텐션 값들의 비율)
function calculateConcentration(matrix) {
    let highAttentionCount = 0;
    let totalCells = 0;
    
    for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[i].length; j++) {
            totalCells++;
            if (matrix[i][j] > 0.3) highAttentionCount++;
        }
    }
    
    return Math.round((highAttentionCount / totalCells) * 100);
}

// 관련성 계산 (의미적으로 관련된 부분의 어텐션 강도)
function calculateRelevance(matrix) {
    let relevantSum = 0;
    let totalSum = 0;
    let relevantCount = 0;
    
    for (let i = 0; i < matrix.length; i++) {
        for (let j = 0; j < matrix[i].length; j++) {
            totalSum += matrix[i][j];
            // 대각선과 인접 영역을 관련성 있는 것으로 간주
            if (i === j || Math.abs(i - j) <= 1) {
                relevantSum += matrix[i][j];
                relevantCount++;
            }
        }
    }
    
    return Math.round((relevantSum / totalSum) * 100);
}

// 메트릭 애니메이션
function animateMetric(fillId, targetValue, textId) {
    const fillElement = document.getElementById(fillId);
    const textElement = document.getElementById(textId);
    
    let currentValue = 0;
    const increment = targetValue / 60;
    
    const animate = () => {
        currentValue += increment;
        if (currentValue >= targetValue) {
            currentValue = targetValue;
            fillElement.style.width = currentValue + '%';
            textElement.textContent = Math.round(currentValue) + '%';
        } else {
            fillElement.style.width = currentValue + '%';
            textElement.textContent = Math.round(currentValue) + '%';
            requestAnimationFrame(animate);
        }
    };
    
    setTimeout(animate, Math.random() * 800);
}

// 셀프 어텐션 비교 생성
function generateSelfAttentionComparison() {
    const size = Math.min(translatedTokens.length, 6);
    const display = translatedTokens.slice(0, size);
    const before = document.getElementById('selfAttentionBefore');
    const after = document.getElementById('selfAttentionAfter');
    before.innerHTML = '';
    after.innerHTML = '';
    before.style.gridTemplateColumns = `60px repeat(${size}, 40px)`;
    after.style.gridTemplateColumns = `60px repeat(${size}, 40px)`;

    // header
    before.appendChild(document.createElement('div'));
    after.appendChild(document.createElement('div'));
    for (let j = 0; j < size; j++) {
        const cb = document.createElement('div');
        cb.className = 'matrix-label';
        cb.textContent = display[j];
        before.appendChild(cb);
        const ca = document.createElement('div');
        ca.className = 'matrix-label';
        ca.textContent = display[j];
        after.appendChild(ca);
    }
    for (let i = 0; i < size; i++) {
        const rb = document.createElement('div');
        rb.className = 'matrix-label';
        rb.textContent = display[i];
        before.appendChild(rb);
        const ra = document.createElement('div');
        ra.className = 'matrix-label';
        ra.textContent = display[i];
        after.appendChild(ra);
        for (let j = 0; j < size; j++) {
            const wBefore = i === j ? 0.2 + Math.random() * 0.1 : 0.05 + Math.random() * 0.1;
            const wAfter = i === j ? Math.min(0.95, wBefore + 0.5) : Math.max(0.05, wBefore - 0.05);
            const cbCell = document.createElement('div');
            cbCell.className = 'attention-cell';
            cbCell.textContent = wBefore.toFixed(2);
            cbCell.style.backgroundColor = getAttentionColor(wBefore);
            before.appendChild(cbCell);
            const caCell = document.createElement('div');
            caCell.className = 'attention-cell';
            caCell.textContent = wAfter.toFixed(2);
            caCell.style.backgroundColor = getAttentionColor(wAfter);
            after.appendChild(caCell);
        }
    }
}

// 인터랙티브 기능들
function highlightDifferences() {
    const beforeCells = document.querySelectorAll('#testAttentionBefore .attention-cell');
    const afterCells = document.querySelectorAll('#testAttentionAfter .attention-cell');
    
    beforeCells.forEach((cell, index) => {
        if (afterCells[index]) {
            const beforeOpacity = parseFloat(cell.style.opacity) || 0;
            const afterOpacity = parseFloat(afterCells[index].style.opacity) || 0;
            
            if (Math.abs(beforeOpacity - afterOpacity) > 0.2) {
                cell.classList.add('highlight-difference');
                afterCells[index].classList.add('highlight-difference');
            }
        }
    });
    
    setTimeout(() => {
        document.querySelectorAll('.highlight-difference').forEach(cell => {
            cell.classList.remove('highlight-difference');
        });
    }, 3000);
}

function showHeatmap() {
    const matrices = document.querySelectorAll('#testAttentionBefore, #testAttentionAfter');
    matrices.forEach(matrix => {
        matrix.classList.toggle('heatmap-mode');
    });
}

function animateEvolution() {
    const afterMatrix = document.getElementById('testAttentionAfter');
    afterMatrix.classList.add('evolution-animation');
    
    setTimeout(() => {
        afterMatrix.classList.remove('evolution-animation');
    }, 3000);
}

function resetComparison() {
    // 하이라이트 제거
    document.querySelectorAll('.highlight-difference').forEach(cell => {
        cell.classList.remove('highlight-difference');
    });
    
    // 히트맵 모드 해제
    document.querySelectorAll('.heatmap-mode').forEach(matrix => {
        matrix.classList.remove('heatmap-mode');
    });
    
    // 애니메이션 클래스 제거
    document.querySelectorAll('.evolution-animation').forEach(element => {
        element.classList.remove('evolution-animation');
    });
    
    alert('🔄 비교 화면이 초기화되었습니다!');
}

// 📈 가중치/가중합 전후 비교 렌더링 유틸
function softmaxRow(row){
    const m = Math.max(...row);
    const ex = row.map(v=>Math.exp(v - m));
    const s = ex.reduce((a,b)=>a+b,0)||1;
    return ex.map(v=>v/s);
}
function ensureSquareMatrixFromFinal(tokens){
    // finalAttentionMatrix는 실수 값. 행별 softmax로 정규화 보장
    const n = Math.min(tokens.length, Math.max(2, finalAttentionMatrix.length || tokens.length));
    const M = Array.from({length:n}, (_,i)=> Array.from({length:n}, (_,j)=>
        (finalAttentionMatrix[i]?.[j]) ?? (i===j?0.4:0.1+Math.random()*0.2)
    ));
    return M.map(softmaxRow);
}
function sampleValueVectors(n, d=8){
    // 간단한 고정난수 기반 V 행렬 생성(재현성보다 시각성이 목적)
    const V = Array.from({length:n}, ()=> Array.from({length:d}, ()=> (Math.random()*2-1)));
    return V;
}
function weightedSum(attRow, V){
    const d = V[0].length; const out = Array(d).fill(0);
    for(let j=0;j<attRow.length;j++){
        for(let k=0;k<d;k++) out[k]+= attRow[j]*V[j][k];
    }
    return out;
}
// === 4단계 확장 유틸: 멀티 레이어/헤드와 변화량 측정 ===
// 단일 계열 색상: 블루-보라 계열 고정, 값에 따라 투명도로 강도 표현
const BASE_HUE = 250;  // 보라 계열
const BASE_SAT = 65;
const BASE_LIG = 55;
function deepCopyMatrix(M){ return M.map(r=> r.slice()); }
function rowSoftmaxMatrix(M){ return M.map(softmaxRow); }
function meanAbsDiff(A,B){
    let s=0,c=0; for(let i=0;i<A.length;i++){ for(let j=0;j<A[i].length;j++){ s+= Math.abs((A[i][j]||0)-(B[i]?.[j]||0)); c++; }}
    return c? s/c : 0;
}
function makeHeadVariants(baseW, L=3, H=4){
    const n = baseW.length;
    const heads = Array.from({length:L}, ()=> Array.from({length:H}, ()=> Array.from({length:n}, (_,i)=> Array.from({length:n}, (_,j)=> baseW[i][j]))));
    // 각 레이어/헤드에 약간의 편향/노이즈 부여(초기화 다양성)
    for (let l=0;l<L;l++){
        for (let h=0;h<H;h++){
            const scale = 0.05 + 0.03*l + 0.02*h;
            for (let i=0;i<n;i++){
                for (let j=0;j<n;j++){
                    const bias = (i===j? 0.06: 0) + (Math.random()-0.5)*scale;
                    heads[l][h][i][j] = Math.max(0, heads[l][h][i][j] + bias);
                }
            }
            // 행별 softmax 정규화
            heads[l][h] = rowSoftmaxMatrix(heads[l][h]);
        }
    }
    return heads;
}
function sharpenTarget(W, factor=1.5){
    // 각 행에서 최고값은 확대, 나머지는 조금 축소 → 변화가 드러나도록 타깃 형성
    const n = W.length; const T = Array.from({length:n}, ()=> Array(n).fill(0));
    for (let i=0;i<n;i++){
        const row = W[i].slice(); const jStar = row.indexOf(Math.max(...row));
        for (let j=0;j<n;j++){
            const v = row[j];
            T[i][j] = (j===jStar) ? Math.min(0.98, v*factor) : Math.max(0.01, v*(2-factor));
        }
    }
    return T;
}
function updateContextTitles(layerIdx, headIdx, delta){
    const beforeTitle = document.querySelector('#contextBefore .context-title');
    const afterTitle = document.querySelector('#contextAfter .context-title');
    const suffix = (delta==null)? '' : ` · 선택: L${layerIdx+1}/H${headIdx+1} (Δ=${delta.toFixed(3)})`;
    if (beforeTitle) beforeTitle.textContent = `학습 전: Hidden State · a(가중치) · 각 항의 가중합 → C(context vector)${suffix}`;
    if (afterTitle) afterTitle.textContent = `학습 후: Hidden State · a(가중치) · 각 항의 가중합 → C(context vector)${suffix}`;
}

// === 학습용 보조 유틸 ===
const EPS = 1e-8;
function toLogits(P){
    return P.map(row => row.map(v => Math.log(Math.max(EPS, v))));
}
function normalizeRows(M){
    return M.map(row => {
        const r = row.map(v => Math.max(EPS, v));
        const s = r.reduce((a,b)=>a+b,0) || 1;
        return r.map(v => v/s);
    });
}

// 컨텍스트 패널 렌더(이미지와 유사): 하나의 대표 행 i를 선택해 항별 가중합 시각화
function renderContextPanel(phase, W, V, i){
    const isBefore = phase==='Before';
    const hostId = isBefore ? 'contextBeforeGrid' : 'contextAfterGrid';
    const grid = document.getElementById(hostId); if(!grid) return;
    grid.innerHTML='';
    const n = Math.min(W.length, tokens.length);
    const dDots = 5; // Hidden state 시각용 점 개수(고정)
    // 각 토큰 행 구성
    const arrowChar = '➜';
    // 현재 쿼리 인덱스 i에 대한 가중치 벡터 a = W[i]
    const a = (W[i] && W[i].length===n) ? W[i] : Array.from({length:n},(_,j)=> 1/n);
    let aMax = Math.max(...a);
    if (!isFinite(aMax) || aMax<=0) aMax = 1;
    // After 패널에서는 C에 가장 크게 기여한 토큰(가중치 최댓값)을 강조 박스 처리
    const isAfter = (phase==='After');
    const maxIdx = isAfter ? (a.indexOf(Math.max(...a))) : -1;
    for(let r=0;r<n;r++){
        // 1) 왼쪽 라벨(토큰)
        const lb = document.createElement('div'); lb.className='ctx-label'; lb.textContent = tokens[r] || `토큰${r+1}`;
        if (isAfter && r===maxIdx){ lb.style.border='3px solid #ff5722'; lb.style.borderRadius='8px'; lb.style.padding='4px 6px'; lb.style.background='#fff3e0'; }
        grid.appendChild(lb);
        // 2) Hidden State 박스(임의 색 농도)
        const hs = document.createElement('div'); hs.className='hs-box'; grid.appendChild(hs);
        for(let k=0;k<dDots;k++){
            const dot = document.createElement('div'); dot.className='vec-dot';
            dot.style.background = `hsl(${BASE_HUE}, ${BASE_SAT}%, ${BASE_LIG}%)`;
            dot.style.opacity = '0.85';
            hs.appendChild(dot);
        }
        // 3) 곱셈 기호
        const mul = document.createElement('div'); mul.className='mul'; mul.textContent = '×'; grid.appendChild(mul);
        // 4) 가중치 바 + 값
        const wb = document.createElement('div'); wb.className='weight-box'; grid.appendChild(wb);
    const bar = document.createElement('div'); bar.className='weight-bar'; wb.appendChild(bar);
    const fill = document.createElement('div'); fill.className='weight-fill';
    const wVal = a[r]; // 쿼리 i가 각 값 j=r에 주는 가중치
    fill.style.background = `hsl(${BASE_HUE}, ${BASE_SAT}%, ${BASE_LIG}%)`;
    fill.style.opacity = (0.25 + 0.75*Math.min(1,wVal*1.8)).toFixed(2);
    fill.style.width = `${Math.max(2, Math.round(wVal*100))}%`; bar.appendChild(fill);
        const val = document.createElement('div'); val.className='weight-val'; val.textContent = wVal.toFixed(3); wb.appendChild(val);
        // 5) 화살표
        const ar = document.createElement('div'); ar.className='arrow'; ar.textContent = arrowChar; grid.appendChild(ar);
        // 6) 항별 가중합 결과 박스(시각용 도트)
        const term = document.createElement('div'); term.className='ctx-box'; grid.appendChild(term);
        // 결과 강도: wVal 비례로 채도/명도 조절
        for(let k=0;k<dDots;k++){
            const dot = document.createElement('div'); dot.className='vec-dot';
            dot.style.background = `hsl(${BASE_HUE}, ${BASE_SAT}%, ${BASE_LIG}%)`;
            const scale = (wVal/aMax);
            dot.style.opacity = (0.25 + 0.75*scale).toFixed(2);
            term.appendChild(dot);
        }

        // 각 항 사이에 + 추가 (마지막 항 제외)
        if (r < n-1){
            for(let c=0;c<5;c++){ const blank = document.createElement('div'); blank.textContent=''; grid.appendChild(blank); }
            const plus = document.createElement('div'); plus.className='plus'; plus.textContent = '+'; grid.appendChild(plus);
        }
    }
    // 마지막 줄: = C(context vector)
    for(let c=0;c<4;c++){ const blank = document.createElement('div'); blank.textContent=''; grid.appendChild(blank); }
    const eq = document.createElement('div'); eq.className='eq'; eq.textContent = '='; grid.appendChild(eq);
    const cbox = document.createElement('div'); cbox.className='ctx-box'; grid.appendChild(cbox);
    // 간단히 Σ a_j V_j를 계산하여 4개 점의 색 강도에 반영
    const Vuse = V && V.length ? V : sampleValueVectors(n, 5);
    const ctx = weightedSum(a, Vuse);
    const ctx5 = ctx.slice(0,5);
    const maxAbs = Math.max(1e-6, ...ctx5.map(x=>Math.abs(x)));
    // 컨텍스트 벡터: 단일 계열 색상 고정, 값 크기에 따라 불투명도 증가
    ctx5.forEach((val)=>{
        const d = document.createElement('div'); d.className='vec-dot';
        const inten = Math.min(1, Math.abs(val)/maxAbs);
        d.style.background = `hsl(${BASE_HUE}, ${BASE_SAT}%, ${BASE_LIG - (isBefore?0:5)}%)`;
        d.style.opacity = (0.5 + 0.5*inten).toFixed(2); // 더 진하게
        cbox.appendChild(d);
    });
    // 라벨 한 줄
    for(let c=0;c<5;c++){ const blank = document.createElement('div'); grid.appendChild(blank); }
    const label = document.createElement('div'); label.style.color = '#607d8b'; label.style.fontWeight='700'; label.textContent = 'C (context vector)'; grid.appendChild(label);
}

// � 선택한 문단 로드 함수
function loadSelectedText() {
    const select = document.getElementById('inputSentence');
    const selectedTextDiv = document.getElementById('selectedText');
    
    if (select.value) {
        selectedTextDiv.style.display = 'block';
        selectedTextDiv.textContent = select.value;
    // 문단 변경 시 학습 상태 초기화
    isModelTrained = false;
    finalAttentionMatrix = [];
    trainingData = { beforeAttention: [], afterAttention: [], beforeSelfAttention: [], afterSelfAttention: [] };
    const trainingProgress = document.getElementById('trainingProgress');
    if (trainingProgress) trainingProgress.style.width = '0%';
    const trainingStatus = document.getElementById('trainingStatus');
    if (trainingStatus) trainingStatus.textContent = '학습 대기 중...';
    const lossText = document.getElementById('lossText');
    if (lossText) lossText.textContent = '손실: -';
    const before = document.getElementById('attentionBefore');
    const after = document.getElementById('attentionAfter');
    const sab = document.getElementById('selfAttentionBefore');
    const saa = document.getElementById('selfAttentionAfter');
    if (before) before.innerHTML = '';
    if (after) after.innerHTML = '';
    if (sab) sab.innerHTML = '';
    if (saa) saa.innerHTML = '';
    // (제거됨) 보조 패널 요소 초기화
    } else {
        selectedTextDiv.style.display = 'none';
    }
}

// �🛠️ 유틸리티 함수
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// 🎮 키보드 단축키
document.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowRight' && e.ctrlKey) {
        e.preventDefault();
        nextStep();
    } else if (e.key === 'ArrowLeft' && e.ctrlKey) {
        e.preventDefault();
        prevStep();
    } else if (e.key === 'Enter' && e.ctrlKey) {
        e.preventDefault();
        startStepSimulation();
    }
});

// 🏁 초기화
console.log('🤖 어텐션 8단계 시뮬레이션 준비 완료!');
console.log('📖 단축키: Ctrl+Enter(시작), Ctrl+→(다음), Ctrl+←(이전)');
</script>
</body>
</html>