ginipick commited on
Commit
a30a834
ยท
verified ยท
1 Parent(s): 41d7775

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +1527 -0
templates/index.html ADDED
@@ -0,0 +1,1527 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>๐Ÿ“ˆ AI ์ฃผ์‹ ๋ฆฌ์„œ์น˜ ์„ผํ„ฐ v5.5</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
10
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
11
+ <!-- TradingView Widget -->
12
+ <script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
13
+ <style>
14
+ * { margin: 0; padding: 0; box-sizing: border-box; }
15
+ body { font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #0a0a1a 0%, #1a1a2e 100%); padding: 20px; color: #e8e8e8; min-height: 100vh; }
16
+ .container { max-width: 1600px; margin: 0 auto; background: rgba(26, 26, 46, 0.95); border-radius: 20px; padding: 40px; box-shadow: 0 20px 60px rgba(0,0,0,0.5); border: 1px solid rgba(0,217,255,0.2); }
17
+ h1 { text-align: center; background: linear-gradient(135deg, #00d9ff, #00ff88); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 10px; font-size: 2.5em; }
18
+ .subtitle { text-align: center; color: #888; margin-bottom: 30px; }
19
+
20
+ .indices-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 15px; margin-bottom: 30px; }
21
+ .index-card { background: linear-gradient(135deg, rgba(0,217,255,0.1), rgba(0,255,136,0.05)); border: 1px solid rgba(0,217,255,0.3); border-radius: 15px; padding: 15px; text-align: center; }
22
+ .index-name { font-size: 0.85em; color: #888; margin-bottom: 5px; }
23
+ .index-value { font-size: 1.4em; font-weight: 700; color: #fff; }
24
+ .index-change { font-size: 0.9em; font-weight: 600; margin-top: 5px; }
25
+ .positive { color: #00ff88; }
26
+ .negative { color: #ff4757; }
27
+
28
+ .tabs { display: flex; gap: 10px; margin-bottom: 30px; border-bottom: 2px solid rgba(255,255,255,0.1); overflow-x: auto; }
29
+ .tab { padding: 15px 30px; background: transparent; border: none; cursor: pointer; font-size: 1em; font-weight: 600; color: #888; border-radius: 10px 10px 0 0; white-space: nowrap; }
30
+ .tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; }
31
+ .tab:hover:not(.active) { background: rgba(255,255,255,0.1); color: #fff; }
32
+ .tab-content { display: none; animation: fadeIn 0.5s; }
33
+ .tab-content.active { display: block; }
34
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
35
+
36
+ /* ์„ผํ‹ฐ๋จผํŠธ ๊ฒŒ์ด์ง€ */
37
+ .sentiment-section { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 30px; border: 1px solid rgba(255,255,255,0.1); }
38
+ .sentiment-title { color: #00d9ff; font-size: 1.2em; font-weight: 700; margin-bottom: 20px; text-align: center; }
39
+ .gauge-container { display: flex; justify-content: center; align-items: center; flex-direction: column; }
40
+ .gauge-wrapper { position: relative; width: 300px; height: 180px; }
41
+ .gauge-labels { display: flex; justify-content: space-between; width: 100%; margin-top: 10px; font-size: 0.7em; color: #888; }
42
+ .gauge-value { text-align: center; margin-top: 15px; }
43
+ .gauge-value .number { font-size: 2em; font-weight: 700; }
44
+ .gauge-value .label { font-size: 1.1em; margin-top: 5px; }
45
+
46
+ /* ์Šคํƒ ํžˆํŠธ๋งต (finviz ์Šคํƒ€์ผ) */
47
+ .heatmap-section { margin-bottom: 30px; }
48
+ .heatmap-container { background: rgba(0,0,0,0.3); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); }
49
+ .heatmap-title { color: #00d9ff; font-size: 1.1em; font-weight: 700; margin-bottom: 15px; }
50
+ .stock-heatmap { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 4px; }
51
+ .heatmap-cell { padding: 12px 8px; border-radius: 4px; text-align: center; cursor: pointer; transition: all 0.3s; position: relative; min-height: 70px; display: flex; flex-direction: column; justify-content: center; }
52
+ .heatmap-cell:hover { transform: scale(1.05); z-index: 10; box-shadow: 0 5px 20px rgba(0,0,0,0.5); }
53
+ .heatmap-cell .ticker { font-weight: 700; font-size: 0.9em; color: #fff; }
54
+ .heatmap-cell .change { font-size: 0.8em; font-weight: 600; margin-top: 4px; }
55
+ .heatmap-cell .price { font-size: 0.7em; color: rgba(255,255,255,0.7); margin-top: 2px; }
56
+ .heatmap-cell.strong-up { background: linear-gradient(135deg, #00c853, #00e676); }
57
+ .heatmap-cell.up { background: linear-gradient(135deg, #4caf50, #66bb6a); }
58
+ .heatmap-cell.slight-up { background: linear-gradient(135deg, #8bc34a, #9ccc65); }
59
+ .heatmap-cell.neutral { background: linear-gradient(135deg, #607d8b, #78909c); }
60
+ .heatmap-cell.slight-down { background: linear-gradient(135deg, #ff9800, #ffa726); }
61
+ .heatmap-cell.down { background: linear-gradient(135deg, #f44336, #ef5350); }
62
+ .heatmap-cell.strong-down { background: linear-gradient(135deg, #c62828, #d32f2f); }
63
+
64
+ /* ์ด์Šˆ ํ‚ค์›Œ๋“œ ํƒœ๊ทธ ํด๋ผ์šฐ๋“œ */
65
+ .keyword-cloud { display: flex; flex-wrap: wrap; gap: 10px; padding: 20px; background: rgba(255,255,255,0.03); border-radius: 15px; margin-bottom: 30px; }
66
+ .keyword-tag { padding: 8px 16px; border-radius: 20px; font-weight: 600; cursor: pointer; transition: all 0.3s; }
67
+ .keyword-tag:hover { transform: scale(1.1); }
68
+ .keyword-tag.size-xl { font-size: 1.3em; padding: 12px 24px; }
69
+ .keyword-tag.size-lg { font-size: 1.1em; padding: 10px 20px; }
70
+ .keyword-tag.size-md { font-size: 0.95em; }
71
+ .keyword-tag.size-sm { font-size: 0.85em; padding: 6px 12px; }
72
+
73
+ /* ํ‹ฐ์ปค ํ…Œ์ดํ”„ */
74
+ .ticker-tape-container { margin: -40px -40px 0 -40px; border-radius: 20px 20px 0 0; overflow: hidden; }
75
+
76
+ /* ๋งˆ์ผ“ ์˜ค๋ฒ„๋ทฐ ๊ทธ๋ฆฌ๋“œ */
77
+ .market-overview-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-bottom: 30px; }
78
+ @media (max-width: 1200px) { .market-overview-grid { grid-template-columns: repeat(2, 1fr); } }
79
+ @media (max-width: 768px) { .market-overview-grid { grid-template-columns: 1fr; } }
80
+
81
+ .market-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); display: flex; flex-direction: column; align-items: center; }
82
+ .market-card-title { color: #00d9ff; font-size: 1em; font-weight: 700; margin-bottom: 15px; text-align: center; }
83
+ .chart-container { width: 100%; height: 180px; position: relative; }
84
+ .chart-legend { display: flex; justify-content: center; gap: 15px; margin-top: 10px; font-size: 0.85em; }
85
+ .legend-item { display: flex; align-items: center; gap: 5px; }
86
+ .legend-dot { width: 12px; height: 12px; border-radius: 50%; }
87
+
88
+ /* ์œ„์ ฏ ์„น์…˜ */
89
+ .widget-section { margin-bottom: 30px; }
90
+ .widget-grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; }
91
+ @media (max-width: 992px) { .widget-grid { grid-template-columns: 1fr; } }
92
+ .widget-card { background: rgba(0,0,0,0.3); border-radius: 15px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); min-height: 400px; }
93
+ .widget-card-full { background: rgba(0,0,0,0.3); border-radius: 15px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); width: 100%; }
94
+
95
+ /* ํžˆํŠธ๋งต ํƒญ */
96
+ .heatmap-tabs { display: flex; gap: 8px; }
97
+ .heatmap-tab { padding: 8px 16px; background: rgba(255,255,255,0.1); border: none; border-radius: 20px; color: #888; cursor: pointer; font-size: 0.9em; font-weight: 600; transition: all 0.3s; }
98
+ .heatmap-tab:hover { background: rgba(255,255,255,0.2); color: #fff; }
99
+ .heatmap-tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; }
100
+
101
+ /* ํŽ˜์ด์ง€๋„ค์ด์…˜ ํƒญ */
102
+ .pagination-tabs { display: flex; gap: 8px; }
103
+ .page-tab { padding: 8px 16px; background: rgba(255,255,255,0.1); border: none; border-radius: 20px; color: #888; cursor: pointer; font-size: 0.85em; font-weight: 600; transition: all 0.3s; }
104
+ .page-tab:hover { background: rgba(255,255,255,0.2); color: #fff; }
105
+ .page-tab.active { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: #fff; }
106
+
107
+ /* ์Šคํฌ๋ฆฌ๋‹ ์„œ๋ธŒ ํƒญ */
108
+ .sub-tabs { display: flex; gap: 10px; margin-bottom: 10px; border-bottom: 2px solid rgba(255,255,255,0.1); padding-bottom: 15px; }
109
+ .sub-tab { padding: 12px 30px; background: rgba(255,255,255,0.05); border: 2px solid rgba(255,255,255,0.1); border-radius: 12px; color: #888; cursor: pointer; font-size: 1em; font-weight: 700; transition: all 0.3s; }
110
+ .sub-tab:hover { background: rgba(255,255,255,0.1); color: #fff; border-color: rgba(255,255,255,0.3); }
111
+ .sub-tab.active { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; border-color: transparent; }
112
+
113
+ /* ์Šคํฌ๋ฆฌ๋‹ ์„œ๋ธŒ ์ฝ˜ํ…์ธ  */
114
+ .screening-sub-content { display: none; }
115
+ .screening-sub-content.active { display: block; }
116
+
117
+ /* ์Šคํฌ๋ฆฌ๋‹ ์„น์…˜ */
118
+ .screening-section { background: rgba(255,255,255,0.02); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); }
119
+
120
+ /* ๋‰ด์Šค/๋ฆฌ์„œ์น˜ ์นด๋“œ */
121
+ .news-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 20px; border-left: 4px solid #00d9ff; }
122
+ .news-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 15px; flex-wrap: wrap; gap: 10px; }
123
+ .news-title { font-size: 1.15em; font-weight: 700; color: #fff; flex: 1; line-height: 1.5; }
124
+ .news-meta { display: flex; gap: 10px; flex-wrap: wrap; }
125
+ .badge { padding: 5px 12px; border-radius: 20px; font-size: 0.8em; font-weight: 600; }
126
+ .ticker-badge { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; }
127
+ .market-badge { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: #fff; }
128
+ .impact-high { background: #ff4757; color: white; }
129
+ .impact-medium { background: #ffa502; color: #1a1a2e; }
130
+ .impact-low { background: #2ed573; color: #1a1a2e; }
131
+ .sentiment-positive { background: rgba(0,255,136,0.2); color: #00ff88; border: 1px solid #00ff88; }
132
+ .sentiment-negative { background: rgba(255,71,87,0.2); color: #ff4757; border: 1px solid #ff4757; }
133
+ .sentiment-neutral { background: rgba(255,165,2,0.2); color: #ffa502; border: 1px solid #ffa502; }
134
+
135
+ .news-summary { color: #bbb; line-height: 1.8; margin-bottom: 15px; font-size: 0.95em; }
136
+ .news-content { color: #999; line-height: 1.7; margin-bottom: 15px; font-size: 0.9em; padding: 15px; background: rgba(0,0,0,0.2); border-radius: 10px; }
137
+ .analysis-box { background: rgba(0,217,255,0.08); border-radius: 10px; padding: 15px; margin-top: 15px; }
138
+ .analysis-label { display: inline-block; background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 5px 12px; border-radius: 15px; font-size: 0.85em; font-weight: 600; margin-bottom: 10px; }
139
+ .news-link { display: inline-block; background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 10px 20px; border-radius: 8px; text-decoration: none; font-size: 0.9em; font-weight: 600; margin-top: 10px; transition: all 0.3s; border: none; cursor: pointer; }
140
+ .news-link:hover { transform: scale(1.05); box-shadow: 0 5px 15px rgba(0,217,255,0.3); }
141
+
142
+ .research-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 25px; margin-bottom: 20px; border: 1px solid rgba(255,255,255,0.1); border-left: 4px solid #00ff88; }
143
+ .research-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 10px; }
144
+ .research-ticker { font-size: 1.8em; font-weight: 800; background: linear-gradient(135deg, #00d9ff, #00ff88); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
145
+ .rating-badge { padding: 10px 20px; border-radius: 25px; font-size: 1em; font-weight: 700; }
146
+ .rating-buy { background: #2ed573; color: #1a1a2e; }
147
+ .rating-hold { background: #ffa502; color: #1a1a2e; }
148
+ .rating-sell { background: #ff4757; color: white; }
149
+
150
+ .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 15px; margin-bottom: 20px; }
151
+ .metric-card { background: rgba(255,255,255,0.05); padding: 15px; border-radius: 10px; text-align: center; }
152
+ .metric-label { color: #888; font-size: 0.85em; margin-bottom: 5px; }
153
+ .metric-value { font-size: 1.2em; font-weight: 700; color: #fff; }
154
+
155
+ /* ์Šค์ผ€์ค„๋Ÿฌ ์ƒํƒœ ํ‘œ์‹œ */
156
+ .scheduler-status {
157
+ display: flex; align-items: center; justify-content: center; gap: 10px;
158
+ background: rgba(155, 89, 182, 0.1); border: 1px solid rgba(155, 89, 182, 0.3);
159
+ border-radius: 25px; padding: 10px 20px; margin: 15px auto; max-width: 500px;
160
+ }
161
+ .scheduler-indicator { font-size: 1.2em; animation: pulse 2s infinite; }
162
+ .scheduler-indicator.active { color: #00ff88; }
163
+ .scheduler-indicator.inactive { color: #ff4757; }
164
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
165
+ #schedulerText { color: #bbb; font-size: 0.9em; }
166
+
167
+ .soma-agent { background: rgba(255,255,255,0.02); border-radius: 12px; padding: 20px; margin-bottom: 15px; border-left: 3px solid; }
168
+ .soma-supervisor { border-color: #9b59b6; }
169
+ .soma-researcher { border-color: #3498db; }
170
+ .soma-critic { border-color: #e74c3c; }
171
+ .soma-analyst { border-color: #00ff88; }
172
+ .soma-header { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
173
+ .soma-icon { width: 35px; height: 35px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.1em; }
174
+ .soma-name { font-weight: 700; font-size: 0.95em; }
175
+ .soma-content { color: #bbb; line-height: 1.8; font-size: 0.95em; }
176
+
177
+ .deep-analysis-btn { background: linear-gradient(135deg, #9b59b6, #8e44ad); color: white; border: none; padding: 12px 24px; border-radius: 25px; font-size: 0.9em; font-weight: 700; cursor: pointer; transition: all 0.3s; }
178
+ .deep-analysis-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(155, 89, 182, 0.4); }
179
+
180
+ .download-btn { background: linear-gradient(135deg, #27ae60, #2ecc71); color: white; border: none; padding: 12px 24px; border-radius: 25px; font-size: 0.9em; font-weight: 700; cursor: pointer; transition: all 0.3s; text-decoration: none; margin: 5px; display: inline-flex; align-items: center; gap: 8px; }
181
+ .download-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(39, 174, 96, 0.4); }
182
+ .download-btn.pdf { background: linear-gradient(135deg, #e74c3c, #c0392b); }
183
+
184
+ /* ์Šคํฌ๋ฆฌ๋‹ ํ…Œ์ด๋ธ” */
185
+ .screening-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
186
+ .stock-card { background: rgba(255,255,255,0.03); border-radius: 15px; padding: 20px; border: 1px solid rgba(255,255,255,0.1); transition: all 0.3s; }
187
+ .stock-card:hover { transform: translateY(-5px); box-shadow: 0 10px 30px rgba(0,217,255,0.2); border-color: #00d9ff; }
188
+ .stock-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
189
+ .stock-card-ticker { font-size: 1.4em; font-weight: 800; color: #00d9ff; }
190
+ .stock-card-rank { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; padding: 5px 12px; border-radius: 15px; font-weight: 700; font-size: 0.85em; }
191
+ .stock-card-name { color: #888; font-size: 0.9em; margin-bottom: 15px; }
192
+ .stock-card-price { font-size: 1.5em; font-weight: 700; margin-bottom: 5px; }
193
+ .stock-card-change { font-size: 1.1em; font-weight: 600; margin-bottom: 15px; }
194
+ .mini-chart { width: 100%; height: 60px; background: rgba(0,0,0,0.2); border-radius: 8px; margin-bottom: 15px; overflow: hidden; }
195
+ .stock-card-metrics { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; font-size: 0.85em; }
196
+ .stock-card-metric { display: flex; justify-content: space-between; padding: 8px; background: rgba(255,255,255,0.03); border-radius: 5px; }
197
+ .stock-card-metric-label { color: #888; }
198
+ .stock-card-metric-value { font-weight: 600; }
199
+ .view-chart-btn { width: 100%; margin-top: 15px; padding: 10px; background: rgba(0,217,255,0.2); border: 1px solid #00d9ff; color: #00d9ff; border-radius: 8px; cursor: pointer; font-weight: 600; transition: all 0.3s; }
200
+ .view-chart-btn:hover { background: #00d9ff; color: #1a1a2e; }
201
+
202
+ /* ๋ชจ๋‹ฌ */
203
+ .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); overflow: auto; }
204
+ .modal.active { display: flex; justify-content: center; align-items: flex-start; padding: 30px 20px; }
205
+ .modal-content { background: #1a1a2e; border-radius: 20px; padding: 30px; max-width: 1200px; width: 100%; border: 1px solid rgba(0,217,255,0.3); max-height: 95vh; overflow-y: auto; }
206
+ .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; position: sticky; top: -30px; background: #1a1a2e; padding: 10px 0; z-index: 10; }
207
+ .modal-close { background: none; border: none; color: #888; font-size: 2em; cursor: pointer; }
208
+ .modal-close:hover { color: #ff4757; }
209
+
210
+ /* ์‹ฌ์ธต ๋ถ„์„ ์„น์…˜ */
211
+ .deep-section { margin-bottom: 20px; padding: 20px; background: rgba(255,255,255,0.03); border-radius: 12px; }
212
+ .deep-section h3 { color: #00d9ff; margin-bottom: 15px; font-size: 1.05em; }
213
+ .deep-section .content { color: #bbb; line-height: 1.8; font-size: 0.95em; }
214
+ .deep-section .content table { width: 100%; border-collapse: collapse; margin: 15px 0; }
215
+ .deep-section .content th { background: rgba(0,217,255,0.2); padding: 10px; text-align: left; color: #00d9ff; }
216
+ .deep-section .content td { padding: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); }
217
+ .deep-section .content ul, .deep-section .content ol { margin: 10px 0 10px 20px; }
218
+ .deep-section .content li { margin-bottom: 8px; }
219
+ .deep-section .content strong { color: #fff; }
220
+ .deep-section .content p { margin-bottom: 10px; }
221
+
222
+ .chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin: 20px 0; }
223
+ .chart-box { background: rgba(255,255,255,0.03); border-radius: 12px; padding: 15px; }
224
+ .chart-box h4 { color: #00d9ff; margin-bottom: 10px; font-size: 0.9em; text-align: center; }
225
+ .chart-box canvas { max-height: 200px; }
226
+
227
+ .loading { display: none; text-align: center; padding: 40px; }
228
+ .loading.active { display: block; }
229
+ .spinner { border: 4px solid rgba(255,255,255,0.1); border-top: 4px solid #00d9ff; border-radius: 50%; width: 50px; height: 50px; animation: spin 1s linear infinite; margin: 0 auto 20px; }
230
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
231
+
232
+ /* TradingView ์ฐจํŠธ ๋ชจ๋‹ฌ */
233
+ .chart-modal-content { max-width: 1000px; }
234
+ #tradingview-chart { width: 100%; height: 500px; border-radius: 10px; overflow: hidden; }
235
+
236
+ .button-group { text-align: center; margin: 40px 0; display: flex; justify-content: center; gap: 15px; flex-wrap: wrap; }
237
+ .refresh-btn { background: linear-gradient(135deg, #00d9ff, #00ff88); color: #1a1a2e; border: none; padding: 15px 35px; font-size: 1em; font-weight: 700; border-radius: 50px; cursor: pointer; transition: all 0.3s; }
238
+ .refresh-btn:hover { transform: scale(1.05); box-shadow: 0 10px 30px rgba(0,217,255,0.4); }
239
+ .refresh-btn.secondary { background: transparent; border: 2px solid #00d9ff; color: #00d9ff; }
240
+
241
+ .timestamp { text-align: center; color: #666; margin-top: 30px; font-size: 0.9em; padding: 15px; background: rgba(255,255,255,0.02); border-radius: 10px; }
242
+ .footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.1); color: #666; font-size: 0.85em; }
243
+ .empty-state { text-align: center; padding: 60px; color: #888; }
244
+ .empty-state h3 { margin-bottom: 15px; color: #00d9ff; }
245
+
246
+ @media (max-width: 768px) {
247
+ body { padding: 10px; }
248
+ .container { padding: 20px; }
249
+ h1 { font-size: 1.8em; }
250
+ .tab { padding: 12px 15px; font-size: 0.85em; }
251
+ .screening-grid { grid-template-columns: 1fr; }
252
+ .stock-heatmap { grid-template-columns: repeat(3, 1fr); }
253
+ }
254
+ </style>
255
+ </head>
256
+ <body>
257
+ <div class="container">
258
+ <h1>๐Ÿ“ˆ AI ์ฃผ์‹ ๋ฆฌ์„œ์น˜ ์„ผํ„ฐ v4.2</h1>
259
+ <p class="subtitle">
260
+ TOP 30 ์ข…๋ชฉ ์‹ค์‹œ๊ฐ„ ๋ถ„์„ | ์Šคํƒ ํžˆํŠธ๋งต | ์ฐจํŠธ ๋ถ„์„ | ์‹ฌ์ธต ๋ถ„์„ + PDF/DOCX<br>
261
+ <span style="color: #00d9ff;">AGI SOMA ๋ฉ€ํ‹ฐ์—์ด์ „ํŠธ | ์กฐ์‚ฌ์žโ†’๋น„ํ‰๊ฐ€โ†’๊ฐ๋…์ž ํ˜‘์—… ์‹œ์Šคํ…œ</span>
262
+ </p>
263
+
264
+ <!-- ์ง€์ˆ˜ -->
265
+ <div class="indices-bar">
266
+ {% for idx in indices %}
267
+ <div class="index-card">
268
+ <div class="index-name">{{ idx.name }}</div>
269
+ <div class="index-value">{{ "{:,.2f}".format(idx.price) }}</div>
270
+ <div class="index-change {{ 'positive' if idx.change >= 0 else 'negative' }}">
271
+ {{ "+" if idx.change >= 0 else "" }}{{ "{:.2f}".format(idx.change) }} ({{ "{:.2f}".format(idx.change_percent) }}%)
272
+ </div>
273
+ </div>
274
+ {% endfor %}
275
+ </div>
276
+
277
+ <!-- ํƒญ -->
278
+ <div class="tabs">
279
+ <button class="tab active" onclick="switchTab(event, 'market')">๐ŸŒ ์‹œ์žฅ ๋ถ„์„</button>
280
+ <button class="tab" onclick="switchTab(event, 'breaking')">๐Ÿ”ฅ ์†๋ณด ({{ breaking_news|length }})</button>
281
+ <button class="tab" onclick="switchTab(event, 'research')">๐Ÿ“ˆ ๋ฆฌ์„œ์น˜ ({{ research_reports|length }})</button>
282
+ <button class="tab" onclick="switchTab(event, 'screening')">๐Ÿ“Š ์Šคํฌ๋ฆฌ๋‹ ({{ screening_data|length }})</button>
283
+ </div>
284
+
285
+ <!-- ์‹œ์žฅ ๋ถ„์„ ํƒญ -->
286
+ <div id="market-content" class="tab-content active">
287
+
288
+ <!-- ํ‹ฐ์ปค ํ…Œ์ดํ”„ (์ƒ๋‹จ ํ๋ฅด๋Š” ์ฃผ๊ฐ€) -->
289
+ <div class="ticker-tape-container">
290
+ <div id="ticker-tape-widget"></div>
291
+ </div>
292
+
293
+ <h2 style="color: #00d9ff; margin-bottom: 20px; margin-top: 20px;">๐ŸŒ ์‹œ์žฅ ์ข…ํ•ฉ ๋ถ„์„</h2>
294
+
295
+ <!-- 3์—ด ๊ทธ๋ฆฌ๋“œ: ์„ผํ‹ฐ๋จผํŠธ + ์„นํ„ฐ ํผํฌ๋จผ์Šค + ์ƒ์Šน/ํ•˜๋ฝ ๋น„์œจ -->
296
+ <div class="market-overview-grid">
297
+ <!-- ์„ผํ‹ฐ๋จผํŠธ ๊ฒŒ์ด์ง€ -->
298
+ <div class="market-card">
299
+ <div class="market-card-title">๐Ÿ“Š ์‹œ์žฅ ์„ผํ‹ฐ๋จผํŠธ</div>
300
+ <div class="gauge-container">
301
+ <div class="gauge-wrapper" style="width: 200px; height: 120px;">
302
+ <canvas id="sentimentGauge"></canvas>
303
+ </div>
304
+ <div class="gauge-labels" style="font-size: 0.6em;">
305
+ <span>๋งค๋„</span>
306
+ <span>์ค‘๋ฆฝ</span>
307
+ <span>๋งค์ˆ˜</span>
308
+ </div>
309
+ <div class="gauge-value" style="margin-top: 10px;">
310
+ <div class="number" id="sentimentValue" style="font-size: 1.8em;">-</div>
311
+ <div class="label" id="sentimentLabel" style="color: #00d9ff; font-size: 0.95em;">๋ถ„์„ ์ค‘...</div>
312
+ </div>
313
+ </div>
314
+ </div>
315
+
316
+ <!-- ์„นํ„ฐ๋ณ„ ํผํฌ๋จผ์Šค ๋ฐ” ์ฐจํŠธ -->
317
+ <div class="market-card">
318
+ <div class="market-card-title">๐Ÿ“ˆ ์„นํ„ฐ๋ณ„ ํผํฌ๋จผ์Šค</div>
319
+ <div class="chart-container">
320
+ <canvas id="sectorChart"></canvas>
321
+ </div>
322
+ </div>
323
+
324
+ <!-- ์ƒ์Šน/ํ•˜๋ฝ ๋น„์œจ ๋„๋„› -->
325
+ <div class="market-card">
326
+ <div class="market-card-title">๐Ÿฉ ์ƒ์Šน/ํ•˜๋ฝ ๋น„์œจ</div>
327
+ <div class="chart-container">
328
+ <canvas id="upDownChart"></canvas>
329
+ </div>
330
+ <div id="upDownLegend" class="chart-legend"></div>
331
+ </div>
332
+ </div>
333
+
334
+ <!-- ๋งˆ์ผ“ ์˜ค๋ฒ„๋ทฐ ์œ„์ ฏ (์ „์ฒด ๋„ˆ๋น„) -->
335
+ <div class="widget-section">
336
+ <h3 style="color: #00d9ff; margin-bottom: 15px;">๐Ÿ“Š ๊ธ€๋กœ๋ฒŒ ์‹œ์žฅ ํ˜„ํ™ฉ</h3>
337
+ <div class="widget-card-full" id="market-overview-widget" style="height: 450px;"></div>
338
+ </div>
339
+
340
+ <!-- ์ด์Šˆ ํ‚ค์›Œ๋“œ ํƒœ๊ทธ ํด๋ผ์šฐ๋“œ -->
341
+ <h3 style="color: #00d9ff; margin-bottom: 15px; margin-top: 30px;">๐Ÿ”ฅ ์‹œ์žฅ ์ด์Šˆ ํ‚ค์›Œ๋“œ (24h)</h3>
342
+ <div class="keyword-cloud" id="keywordCloud"></div>
343
+
344
+ <!-- ์Šคํƒ ํžˆํŠธ๋งต with ํƒญ -->
345
+ <div class="heatmap-section">
346
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
347
+ <h3 style="color: #00d9ff; margin: 0;">๐Ÿ“ˆ ์‹œ์žฅ ํžˆํŠธ๋งต</h3>
348
+ <div class="heatmap-tabs">
349
+ <button class="heatmap-tab active" onclick="switchHeatmap('SPX500', this)">S&P 500</button>
350
+ <button class="heatmap-tab" onclick="switchHeatmap('NASDAQ100', this)">NASDAQ 100</button>
351
+ <button class="heatmap-tab" onclick="switchHeatmap('DJI', this)">DOW 30</button>
352
+ </div>
353
+ </div>
354
+ <div id="stock-heatmap-widget" style="height: 500px; border-radius: 15px; overflow: hidden;"></div>
355
+ </div>
356
+ </div>
357
+
358
+ <!-- ์†๋ณด ํƒญ -->
359
+ <div id="breaking-content" class="tab-content">
360
+ <h2 style="color: #00d9ff; margin-bottom: 20px;">๐Ÿ”ฅ 24์‹œ๊ฐ„ ์ด๋‚ด ์ฃผ์š” ๋‰ด์Šค</h2>
361
+ {% if breaking_news %}
362
+ {% for news in breaking_news %}
363
+ <div class="news-card">
364
+ <div class="news-header">
365
+ <div class="news-title">{{ news.title }}</div>
366
+ <div class="news-meta">
367
+ {% if news.news_type == 'market' %}
368
+ <span class="badge market-badge">{{ news.ticker }}</span>
369
+ {% else %}
370
+ <span class="badge ticker-badge">{{ news.ticker }}</span>
371
+ {% endif %}
372
+ <span class="badge impact-{{ news.impact_level }}">{{ news.impact_text }}</span>
373
+ <span class="badge sentiment-{{ news.sentiment }}">{{ news.sentiment_text }}</span>
374
+ </div>
375
+ </div>
376
+ {% if news.title_original and news.title_original != news.title %}
377
+ <div style="color: #666; font-size: 0.85em; margin-bottom: 10px; font-style: italic;">๐Ÿ“ {{ news.title_original }}</div>
378
+ {% endif %}
379
+ <div class="news-summary">{{ news.summary }}</div>
380
+ {% if news.content %}
381
+ <div class="news-content">{{ news.content }}</div>
382
+ {% endif %}
383
+ <div class="analysis-box">
384
+ <span class="analysis-label">๐Ÿ’ก ํˆฌ์ž ํฌ์ธํŠธ</span>
385
+ <div style="color: #bbb; line-height: 1.7;">{{ news.investment_point }}</div>
386
+ </div>
387
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px; flex-wrap: wrap; gap: 10px;">
388
+ <div>
389
+ <span style="color: #666; font-size: 0.85em;">๐Ÿ“… {{ news.published_at }}</span>
390
+ {% if news.source %}
391
+ <span style="color: #00d9ff; font-size: 0.85em; margin-left: 10px;">๐Ÿ“ฐ {{ news.source }}</span>
392
+ {% endif %}
393
+ </div>
394
+ <div>
395
+ {% if news.url %}
396
+ <a href="{{ news.url }}" target="_blank" class="news-link" style="margin-right: 5px;">๐Ÿ”— ์›๋ฌธ ๋ณด๊ธฐ</a>
397
+ {% endif %}
398
+ <button class="news-link" onclick="searchNews('{{ news.title|e }}')">๐Ÿ” ๊ด€๋ จ ๊ฒ€์ƒ‰</button>
399
+ </div>
400
+ </div>
401
+ </div>
402
+ {% endfor %}
403
+ {% else %}
404
+ <div class="empty-state">
405
+ <h3>๐Ÿ“ญ 24์‹œ๊ฐ„ ์ด๋‚ด ๋‰ด์Šค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค</h3>
406
+ </div>
407
+ {% endif %}
408
+ </div>
409
+
410
+ <!-- ๋ฆฌ์„œ์น˜ ํƒญ -->
411
+ <div id="research-content" class="tab-content">
412
+ <h2 style="color: #00d9ff; margin-bottom: 20px;">๐Ÿ“ˆ AGI SOMA ๋ฆฌ์„œ์น˜ + ์‹ฌ์ธต ๋ถ„์„</h2>
413
+ {% if research_reports %}
414
+ {% for report in research_reports %}
415
+ <div class="research-card">
416
+ <div class="research-header">
417
+ <div>
418
+ <span class="research-ticker">{{ report.ticker }}</span>
419
+ <span style="color: #888; margin-left: 10px;">{{ report.company_name }}</span>
420
+ </div>
421
+ <span class="rating-badge rating-{{ report.rating_class }}">{{ report.rating }}</span>
422
+ </div>
423
+ <div class="metrics-grid">
424
+ <div class="metric-card">
425
+ <div class="metric-label">ํ˜„์žฌ๊ฐ€</div>
426
+ <div class="metric-value">${{ "{:,.2f}".format(report.current_price) }}</div>
427
+ </div>
428
+ <div class="metric-card">
429
+ <div class="metric-label">๋ชฉํ‘œ๊ฐ€</div>
430
+ <div class="metric-value" style="color: #00ff88;">${{ "{:,.2f}".format(report.target_price) }}</div>
431
+ </div>
432
+ <div class="metric-card">
433
+ <div class="metric-label">์ƒ์Šน์—ฌ๋ ฅ</div>
434
+ <div class="metric-value" style="color: #00d9ff;">+{{ report.upside }}%</div>
435
+ </div>
436
+ <div class="metric-card">
437
+ <div class="metric-label">๋ฆฌ์Šคํฌ</div>
438
+ <div class="metric-value">{{ report.risk_level }}</div>
439
+ </div>
440
+ </div>
441
+ <div class="soma-agent soma-supervisor">
442
+ <div class="soma-header">
443
+ <div class="soma-icon" style="background: #9b59b6;">๐Ÿ‘”</div>
444
+ <span class="soma-name" style="color: #9b59b6;">๊ฐ๋…์ž (Supervisor)</span>
445
+ </div>
446
+ <div class="soma-content">{{ report.supervisor_analysis }}</div>
447
+ </div>
448
+ <div class="soma-agent soma-researcher">
449
+ <div class="soma-header">
450
+ <div class="soma-icon" style="background: #3498db;">๐Ÿ”ฌ</div>
451
+ <span class="soma-name" style="color: #3498db;">์กฐ์‚ฌ์ž (Researcher)</span>
452
+ </div>
453
+ <div class="soma-content">{{ report.researcher_analysis }}</div>
454
+ </div>
455
+ <div style="margin-top: 20px;">
456
+ <button class="deep-analysis-btn" onclick="runDeepAnalysis('{{ report.ticker }}', '{{ report.company_name }}')">๐Ÿ”ฌ ์‹ฌ์ธต ๋ถ„์„</button>
457
+ <span id="deep-status-{{ report.ticker }}" style="margin-left: 15px; color: #888;"></span>
458
+ </div>
459
+ </div>
460
+ {% endfor %}
461
+ {% else %}
462
+ <div class="empty-state"><h3>๐Ÿ“ญ ๋ฆฌ์„œ์น˜ ๋ณด๊ณ ์„œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค</h3></div>
463
+ {% endif %}
464
+ </div>
465
+
466
+ <!-- ์Šคํฌ๋ฆฌ๋‹ ํƒญ -->
467
+ <div id="screening-content" class="tab-content">
468
+
469
+ <!-- ์„œ๋ธŒ ํƒญ (DOW 30 / NASDAQ 100) -->
470
+ <div class="sub-tabs">
471
+ <button class="sub-tab active" onclick="switchScreeningTab('dow30', this)">๐Ÿ›๏ธ DOW 30</button>
472
+ <button class="sub-tab" onclick="switchScreeningTab('nasdaq100', this)">๐Ÿ’ป NASDAQ 100</button>
473
+ </div>
474
+
475
+ <!-- DOW 30 ์ฝ˜ํ…์ธ  -->
476
+ <div id="dow30-content" class="screening-sub-content active">
477
+ <h3 style="color: #00d9ff; margin: 20px 0;">๐Ÿ›๏ธ DOW 30 ์ข…๋ชฉ ์Šคํฌ๋ฆฌ๋‹</h3>
478
+ <div class="screening-grid">
479
+ {% set dow30_tickers = ['UNH', 'GS', 'HD', 'CAT', 'CRM', 'MCD', 'V', 'TRV', 'AXP', 'JPM', 'IBM', 'BA', 'MMM', 'JNJ', 'PG', 'MRK', 'NKE', 'DIS', 'KO', 'WMT', 'VZ', 'DOW', 'SHW', 'AAPL', 'MSFT', 'AMZN', 'NVDA', 'HON', 'AMGN', 'CSCO'] %}
480
+ {% for stock in screening_data %}
481
+ {% if stock.ticker in dow30_tickers %}
482
+ <div class="stock-card">
483
+ <div class="stock-card-header">
484
+ <span class="stock-card-ticker">{{ stock.ticker }}</span>
485
+ <span class="stock-card-rank">#{{ stock.rank }}</span>
486
+ </div>
487
+ <div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div>
488
+ <div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div>
489
+ <div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}">
490
+ {{ "{:+.2f}".format(stock.change_percent) }}%
491
+ </div>
492
+ <!-- TradingView ๋ฏธ๋‹ˆ ์ฐจํŠธ -->
493
+ <div class="mini-chart" id="mini-chart-dow-{{ stock.ticker }}"></div>
494
+ <div class="stock-card-metrics">
495
+ <div class="stock-card-metric">
496
+ <span class="stock-card-metric-label">RSI</span>
497
+ <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span>
498
+ </div>
499
+ <div class="stock-card-metric">
500
+ <span class="stock-card-metric-label">PER</span>
501
+ <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span>
502
+ </div>
503
+ <div class="stock-card-metric">
504
+ <span class="stock-card-metric-label">์‹œ์ด</span>
505
+ <span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span>
506
+ </div>
507
+ </div>
508
+ <button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NYSE') }}')">๐Ÿ“ˆ ์ฐจํŠธ</button>
509
+ </div>
510
+ {% endif %}
511
+ {% endfor %}
512
+ </div>
513
+ </div>
514
+
515
+ <!-- NASDAQ 100 ์ฝ˜ํ…์ธ  -->
516
+ <div id="nasdaq100-content" class="screening-sub-content">
517
+ <div style="display: flex; justify-content: space-between; align-items: center; margin: 20px 0;">
518
+ <h3 style="color: #00d9ff; margin: 0;">๐Ÿ’ป NASDAQ 100 ์ข…๋ชฉ ์Šคํฌ๋ฆฌ๋‹</h3>
519
+ <div class="pagination-tabs">
520
+ <button class="page-tab active" onclick="showNasdaqPage(1, this)">1-35</button>
521
+ <button class="page-tab" onclick="showNasdaqPage(2, this)">36-70</button>
522
+ <button class="page-tab" onclick="showNasdaqPage(3, this)">71-101</button>
523
+ </div>
524
+ </div>
525
+
526
+ <!-- NASDAQ ํŽ˜์ด์ง€ 1: 1-35 -->
527
+ <div class="screening-grid nasdaq-page" id="nasdaq-page-1">
528
+ {% for stock in screening_data[:35] %}
529
+ <div class="stock-card">
530
+ <div class="stock-card-header">
531
+ <span class="stock-card-ticker">{{ stock.ticker }}</span>
532
+ <span class="stock-card-rank">#{{ loop.index }}</span>
533
+ </div>
534
+ <div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div>
535
+ <div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div>
536
+ <div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}">
537
+ {{ "{:+.2f}".format(stock.change_percent) }}%
538
+ </div>
539
+ <!-- TradingView ๋ฏธ๋‹ˆ ์ฐจํŠธ -->
540
+ <div class="mini-chart" id="mini-chart-nq1-{{ stock.ticker }}"></div>
541
+ <div class="stock-card-metrics">
542
+ <div class="stock-card-metric">
543
+ <span class="stock-card-metric-label">RSI</span>
544
+ <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span>
545
+ </div>
546
+ <div class="stock-card-metric">
547
+ <span class="stock-card-metric-label">PER</span>
548
+ <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span>
549
+ </div>
550
+ <div class="stock-card-metric">
551
+ <span class="stock-card-metric-label">์‹œ์ด</span>
552
+ <span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span>
553
+ </div>
554
+ </div>
555
+ <button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐Ÿ“ˆ ์ฐจํŠธ</button>
556
+ </div>
557
+ {% endfor %}
558
+ </div>
559
+
560
+ <!-- NASDAQ ํŽ˜์ด์ง€ 2: 36-70 -->
561
+ <div class="screening-grid nasdaq-page" id="nasdaq-page-2" style="display: none;">
562
+ {% for stock in screening_data[35:70] %}
563
+ <div class="stock-card">
564
+ <div class="stock-card-header">
565
+ <span class="stock-card-ticker">{{ stock.ticker }}</span>
566
+ <span class="stock-card-rank">#{{ loop.index + 35 }}</span>
567
+ </div>
568
+ <div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div>
569
+ <div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div>
570
+ <div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}">
571
+ {{ "{:+.2f}".format(stock.change_percent) }}%
572
+ </div>
573
+ <!-- TradingView ๋ฏธ๋‹ˆ ์ฐจํŠธ -->
574
+ <div class="mini-chart" id="mini-chart-nq2-{{ stock.ticker }}"></div>
575
+ <div class="stock-card-metrics">
576
+ <div class="stock-card-metric">
577
+ <span class="stock-card-metric-label">RSI</span>
578
+ <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span>
579
+ </div>
580
+ <div class="stock-card-metric">
581
+ <span class="stock-card-metric-label">PER</span>
582
+ <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span>
583
+ </div>
584
+ <div class="stock-card-metric">
585
+ <span class="stock-card-metric-label">์‹œ์ด</span>
586
+ <span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span>
587
+ </div>
588
+ </div>
589
+ <button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐Ÿ“ˆ ์ฐจํŠธ</button>
590
+ </div>
591
+ {% endfor %}
592
+ </div>
593
+
594
+ <!-- NASDAQ ํŽ˜์ด์ง€ 3: 71-101 -->
595
+ <div class="screening-grid nasdaq-page" id="nasdaq-page-3" style="display: none;">
596
+ {% for stock in screening_data[70:101] %}
597
+ <div class="stock-card">
598
+ <div class="stock-card-header">
599
+ <span class="stock-card-ticker">{{ stock.ticker }}</span>
600
+ <span class="stock-card-rank">#{{ loop.index + 70 }}</span>
601
+ </div>
602
+ <div class="stock-card-name">{{ stock.name[:20] }}{% if stock.name|length > 20 %}...{% endif %}</div>
603
+ <div class="stock-card-price">${{ "{:,.2f}".format(stock.price) }}</div>
604
+ <div class="stock-card-change {{ 'positive' if stock.change_percent >= 0 else 'negative' }}">
605
+ {{ "{:+.2f}".format(stock.change_percent) }}%
606
+ </div>
607
+ <!-- TradingView ๋ฏธ๋‹ˆ ์ฐจํŠธ -->
608
+ <div class="mini-chart" id="mini-chart-nq3-{{ stock.ticker }}"></div>
609
+ <div class="stock-card-metrics">
610
+ <div class="stock-card-metric">
611
+ <span class="stock-card-metric-label">RSI</span>
612
+ <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.rsi) }}</span>
613
+ </div>
614
+ <div class="stock-card-metric">
615
+ <span class="stock-card-metric-label">PER</span>
616
+ <span class="stock-card-metric-value">{{ "{:.1f}".format(stock.pe_ratio) }}</span>
617
+ </div>
618
+ <div class="stock-card-metric">
619
+ <span class="stock-card-metric-label">์‹œ์ด</span>
620
+ <span class="stock-card-metric-value">${{ "{:.0f}".format(stock.market_cap / 1e9) }}B</span>
621
+ </div>
622
+ </div>
623
+ <button class="view-chart-btn" onclick="openChartModal('{{ stock.ticker }}', '{{ stock.name }}', '{{ stock.exchange|default('NASDAQ') }}')">๐Ÿ“ˆ ์ฐจํŠธ</button>
624
+ </div>
625
+ {% endfor %}
626
+ </div>
627
+ </div>
628
+ </div>
629
+
630
+ <div class="button-group">
631
+ <button class="refresh-btn" onclick="location.reload()">๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ</button>
632
+ <button class="refresh-btn secondary" onclick="location.href='/?refresh=true'">๐Ÿ”ฅ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹ </button>
633
+ <button class="refresh-btn" style="background: linear-gradient(135deg, #9b59b6, #8e44ad);" onclick="triggerAutoRefresh()">โฐ ์ฆ‰์‹œ ์ž๋™๊ฐฑ์‹ </button>
634
+ </div>
635
+
636
+ <!-- ์Šค์ผ€์ค„๋Ÿฌ ์ƒํƒœ ํ‘œ์‹œ -->
637
+ <div class="scheduler-status" id="schedulerStatus">
638
+ <span class="scheduler-indicator">โฐ</span>
639
+ <span id="schedulerText">์ž๋™ ๊ฐฑ์‹  ์ƒํƒœ ํ™•์ธ ์ค‘...</span>
640
+ </div>
641
+
642
+ <div class="timestamp">โฐ ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ: {{ timestamp }}</div>
643
+ <div class="footer">
644
+ <p>๐Ÿ“ˆ AI ์ฃผ์‹ ๋ฆฌ์„œ์น˜ ์„ผํ„ฐ v5.8 | NASDAQ 100 + DOW 30 | ์ž๋™ ๊ฐฑ์‹  ์‹œ์Šคํ…œ | Ginigen AI</p>
645
+ <p style="margin-top: 10px;">โš ๏ธ ๋ณธ ์„œ๋น„์Šค๋Š” ๏ฟฝ๏ฟฝ์ž ์ฐธ๊ณ ์šฉ์ด๋ฉฐ, ํˆฌ์ž ๊ฒฐ์ •์€ ๋ณธ์ธ์˜ ํŒ๋‹จ๊ณผ ์ฑ…์ž„ ํ•˜์— ์ด๋ฃจ์–ด์ ธ์•ผ ํ•ฉ๋‹ˆ๋‹ค.</p>
646
+ </div>
647
+ </div>
648
+
649
+ <!-- ์‹ฌ์ธต ๋ถ„์„ ๋ชจ๋‹ฌ -->
650
+ <div id="deepAnalysisModal" class="modal">
651
+ <div class="modal-content" id="deepAnalysisContent">
652
+ <div class="modal-header">
653
+ <h2 style="color: #00d9ff;">๐Ÿ”ฌ <span id="modal-ticker"></span> ์‹ฌ์ธต ๋ถ„์„</h2>
654
+ <button class="modal-close" onclick="closeModal('deepAnalysisModal')">&times;</button>
655
+ </div>
656
+ <div id="loading-indicator" class="loading">
657
+ <div class="spinner"></div>
658
+ <p>๐Ÿค– SOMA ์—์ด์ „ํŠธ๊ฐ€ ์‹ฌ์ธต ๋ถ„์„ ์ค‘์ž…๋‹ˆ๋‹ค...</p>
659
+ </div>
660
+ <div id="deep-analysis-result" style="display: none;">
661
+ <div class="chart-grid">
662
+ <div class="chart-box"><h4>๐Ÿ“Š ํˆฌ์ž ์ง€ํ‘œ</h4><canvas id="metricsChart"></canvas></div>
663
+ <div class="chart-box"><h4>๐ŸŽฏ ๋ชฉํ‘œ๊ฐ€ ๋ถ„์„</h4><canvas id="priceChart"></canvas></div>
664
+ <div class="chart-box"><h4>โš–๏ธ ๋ฆฌ์Šคํฌ/๊ธฐํšŒ</h4><canvas id="riskChart"></canvas></div>
665
+ <div class="chart-box"><h4>๐Ÿ“ˆ ์„ฑ์žฅ ์ „๋ง</h4><canvas id="growthChart"></canvas></div>
666
+ </div>
667
+ <div class="metrics-grid" style="margin-bottom: 25px;">
668
+ <div class="metric-card"><div class="metric-label">ํ˜„์žฌ๊ฐ€</div><div class="metric-value" id="result-current-price">-</div></div>
669
+ <div class="metric-card"><div class="metric-label">๋ชฉํ‘œ๊ฐ€</div><div class="metric-value" style="color: #00ff88;" id="result-target-price">-</div></div>
670
+ <div class="metric-card"><div class="metric-label">ํˆฌ์ž์˜๊ฒฌ</div><div class="metric-value" style="color: #00d9ff;" id="result-rating">-</div></div>
671
+ <div class="metric-card"><div class="metric-label">์ƒ์Šน์—ฌ๋ ฅ</div><div class="metric-value" style="color: #2ed573;" id="result-upside">-</div></div>
672
+ </div>
673
+ <div class="deep-section" style="background: rgba(0,217,255,0.1); border: 1px solid rgba(0,217,255,0.3);"><h3>๐Ÿ“‹ ํ•ต์‹ฌ ์š”์•ฝ</h3><div class="content" id="result-executive-summary"></div></div>
674
+ <div class="deep-section"><h3>๐Ÿข ํšŒ์‚ฌ ๊ฐœ์š”</h3><div class="content" id="result-company-overview"></div></div>
675
+ <div class="deep-section"><h3>๐Ÿ’ฐ ์žฌ๋ฌด ๋ถ„์„</h3><div class="content" id="result-financial-analysis"></div></div>
676
+ <div class="deep-section"><h3>๐Ÿ“ˆ ๊ธฐ์ˆ ์  ๋ถ„์„</h3><div class="content" id="result-technical-analysis"></div></div>
677
+ <div class="deep-section"><h3>๐Ÿญ ์‚ฐ์—… ๋ถ„์„</h3><div class="content" id="result-industry-analysis"></div></div>
678
+ <div class="deep-section"><h3>โš ๏ธ ๋ฆฌ์Šคํฌ ํ‰๊ฐ€</h3><div class="content" id="result-risk-assessment"></div></div>
679
+ <div class="deep-section"><h3>๐Ÿ’ก ํˆฌ์ž ๋…ผ๋ฆฌ</h3><div class="content" id="result-investment-thesis"></div></div>
680
+ <div class="deep-section"><h3>๐ŸŽฏ ๋ชฉํ‘œ ์ฃผ๊ฐ€</h3><div class="content" id="result-price-targets"></div></div>
681
+ <div class="deep-section"><h3>๐Ÿš€ ์นดํƒˆ๋ฆฌ์ŠคํŠธ</h3><div class="content" id="result-catalysts"></div></div>
682
+ <div class="deep-section" style="background: rgba(0,255,136,0.1); border: 1px solid rgba(0,255,136,0.3);"><h3>โœ… ์ตœ์ข… ๊ถŒ๊ณ </h3><div class="content" id="result-final-recommendation" style="font-weight: 600;"></div></div>
683
+ <div style="text-align: center; margin-top: 30px;">
684
+ <a id="download-docx-btn" class="download-btn" style="display: none;">๐Ÿ“„ DOCX</a>
685
+ <button id="download-pdf-btn" class="download-btn pdf" style="display: none;" onclick="downloadAsPDF()">๐Ÿ“‘ PDF</button>
686
+ </div>
687
+ </div>
688
+ </div>
689
+ </div>
690
+
691
+ <!-- TradingView ์ฐจํŠธ ๋ชจ๋‹ฌ -->
692
+ <div id="chartModal" class="modal">
693
+ <div class="modal-content chart-modal-content">
694
+ <div class="modal-header">
695
+ <h2 style="color: #00d9ff;">๐Ÿ“ˆ <span id="chart-modal-title"></span></h2>
696
+ <button class="modal-close" onclick="closeModal('chartModal')">&times;</button>
697
+ </div>
698
+ <div id="tradingview-chart"></div>
699
+ </div>
700
+ </div>
701
+
702
+ <script>
703
+ // ์ „์—ญ ๋ณ€์ˆ˜
704
+ let currentTicker = '';
705
+ let sentimentGaugeInstance = null;
706
+ let metricsChartInstance = null, priceChartInstance = null, riskChartInstance = null, growthChartInstance = null;
707
+ let tvWidget = null;
708
+
709
+ const newsData = {{ breaking_news | tojson | safe }};
710
+ const screeningData = {{ screening_data | tojson | safe }};
711
+
712
+ if (typeof marked !== 'undefined') {
713
+ marked.setOptions({ breaks: true, gfm: true, tables: true });
714
+ }
715
+
716
+ // ์Šค์ผ€์ค„๋Ÿฌ ์ƒํƒœ ์กฐํšŒ
717
+ async function updateSchedulerStatus() {
718
+ try {
719
+ const response = await fetch('/api/scheduler/status');
720
+ const data = await response.json();
721
+
722
+ if (data.success) {
723
+ const scheduler = data.scheduler;
724
+ const indicator = document.querySelector('.scheduler-indicator');
725
+ const text = document.getElementById('schedulerText');
726
+
727
+ if (scheduler.is_running) {
728
+ indicator.classList.add('active');
729
+ indicator.classList.remove('inactive');
730
+ indicator.textContent = '๐ŸŸข';
731
+
732
+ let statusText = `์ž๋™ ๊ฐฑ์‹  ํ™œ์„ฑ (${scheduler.interval_hours}์‹œ๊ฐ„ ์ฃผ๊ธฐ)`;
733
+ if (scheduler.next_run) {
734
+ const nextRun = new Date(scheduler.next_run);
735
+ statusText += ` | ๋‹ค์Œ: ${nextRun.toLocaleTimeString('ko-KR')}`;
736
+ }
737
+ if (scheduler.refresh_count > 0) {
738
+ statusText += ` | ๊ฐฑ์‹  ${scheduler.refresh_count}ํšŒ`;
739
+ }
740
+ text.textContent = statusText;
741
+ } else {
742
+ indicator.classList.remove('active');
743
+ indicator.classList.add('inactive');
744
+ indicator.textContent = '๐Ÿ”ด';
745
+ text.textContent = '์ž๋™ ๊ฐฑ์‹  ๋น„ํ™œ์„ฑ';
746
+ }
747
+ }
748
+ } catch (e) {
749
+ console.log('์Šค์ผ€์ค„๋Ÿฌ ์ƒํƒœ ์กฐํšŒ ์‹คํŒจ:', e);
750
+ }
751
+ }
752
+
753
+ // ์ฆ‰์‹œ ์ž๋™๊ฐฑ์‹  ํŠธ๋ฆฌ๊ฑฐ
754
+ async function triggerAutoRefresh() {
755
+ if (!confirm('์ง€๊ธˆ ์ฆ‰์‹œ ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?\n(๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹คํ–‰๋˜๋ฉฐ ๋ช‡ ๋ถ„์ด ์†Œ์š”๋ฉ๋‹ˆ๋‹ค)')) {
756
+ return;
757
+ }
758
+
759
+ try {
760
+ const response = await fetch('/api/scheduler/trigger');
761
+ const data = await response.json();
762
+
763
+ if (data.success) {
764
+ alert('โœ… ์ž๋™ ๊ฐฑ์‹ ์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\n\n์™„๋ฃŒ๋˜๋ฉด ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜์„ธ์š”.');
765
+ } else {
766
+ alert('โŒ ๊ฐฑ์‹  ํŠธ๋ฆฌ๊ฑฐ ์‹คํŒจ: ' + (data.error || '์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜'));
767
+ }
768
+ } catch (e) {
769
+ alert('โŒ ์„œ๋ฒ„ ์—ฐ๊ฒฐ ์˜ค๋ฅ˜: ' + e.message);
770
+ }
771
+ }
772
+
773
+ // ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ ์Šค์ผ€์ค„๋Ÿฌ ์ƒํƒœ ์กฐํšŒ
774
+ document.addEventListener('DOMContentLoaded', function() {
775
+ updateSchedulerStatus();
776
+ // 1๋ถ„๋งˆ๋‹ค ์Šค์ผ€์ค„๋Ÿฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
777
+ setInterval(updateSchedulerStatus, 60000);
778
+ });
779
+
780
+ function switchTab(event, tabName) {
781
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
782
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
783
+ event.currentTarget.classList.add('active');
784
+ document.getElementById(tabName + '-content').classList.add('active');
785
+ if (tabName === 'market') setTimeout(initMarketTab, 100);
786
+ if (tabName === 'screening') setTimeout(() => loadMiniChartsForPage('dow'), 500);
787
+ }
788
+
789
+ function searchNews(title) {
790
+ window.open(`https://www.google.com/search?q=${encodeURIComponent(title)}&tbm=nws`, '_blank');
791
+ }
792
+
793
+ // NASDAQ 100 ํŽ˜์ด์ง€ ์ „ํ™˜
794
+ function showNasdaqPage(pageNum, btn) {
795
+ // ๋ชจ๋“  ํŽ˜์ด์ง€ ์ˆจ๊ธฐ๊ธฐ
796
+ document.querySelectorAll('.nasdaq-page').forEach(p => p.style.display = 'none');
797
+ // ์„ ํƒํ•œ ํŽ˜์ด์ง€ ํ‘œ์‹œ
798
+ document.getElementById('nasdaq-page-' + pageNum).style.display = 'grid';
799
+ // ํƒญ ํ™œ์„ฑํ™”
800
+ document.querySelectorAll('.page-tab').forEach(t => t.classList.remove('active'));
801
+ btn.classList.add('active');
802
+ // ํ•ด๋‹น ํŽ˜์ด์ง€ ๋ฏธ๋‹ˆ์ฐจํŠธ ๋กœ๋“œ
803
+ setTimeout(() => loadMiniChartsForPage('nq' + pageNum), 300);
804
+ }
805
+
806
+ // ์Šคํฌ๋ฆฌ๋‹ ์„œ๋ธŒํƒญ ์ „ํ™˜ (DOW 30 / NASDAQ 100)
807
+ function switchScreeningTab(tabName, btn) {
808
+ // ๋ชจ๋“  ์„œ๋ธŒ ์ฝ˜ํ…์ธ  ์ˆจ๊ธฐ๊ธฐ
809
+ document.querySelectorAll('.screening-sub-content').forEach(c => c.classList.remove('active'));
810
+ // ์„ ํƒํ•œ ์ฝ˜ํ…์ธ  ํ‘œ์‹œ
811
+ document.getElementById(tabName + '-content').classList.add('active');
812
+ // ํƒญ ํ™œ์„ฑํ™”
813
+ document.querySelectorAll('.sub-tab').forEach(t => t.classList.remove('active'));
814
+ btn.classList.add('active');
815
+ // ํ•ด๋‹น ํƒญ ๋ฏธ๋‹ˆ์ฐจํŠธ ๋กœ๋“œ
816
+ if (tabName === 'dow30') {
817
+ setTimeout(() => loadMiniChartsForPage('dow'), 300);
818
+ } else {
819
+ setTimeout(() => loadMiniChartsForPage('nq1'), 300);
820
+ }
821
+ }
822
+
823
+ // ํŠน์ • ํŽ˜์ด์ง€์˜ ๋ฏธ๋‹ˆ์ฐจํŠธ๋งŒ ๋กœ๋“œ
824
+ // TradingView ์‹ฌ๋ณผ ๋งคํ•‘ (์‹ค์ œ ๊ฑฐ๋ž˜์†Œ์™€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ)
825
+ // NYSE ์ข…๋ชฉ (TradingView์—์„œ NYSE๋กœ ํ‘œ์‹œ๋˜๋Š” ์ข…๋ชฉ๋งŒ)
826
+ const NYSE_TICKERS = ['UNH', 'GS', 'HD', 'CAT', 'CRM', 'MCD', 'V', 'TRV', 'AXP', 'JPM', 'IBM', 'BA', 'MMM', 'JNJ', 'PG', 'MRK', 'NKE', 'DIS', 'KO', 'WMT', 'VZ', 'DOW', 'SHW', 'TRI', 'LIN', 'HON', 'AEP', 'CEG'];
827
+
828
+ // TradingView ํŠน์ˆ˜ ์‹ฌ๋ณผ ๋งคํ•‘ (์‹ค์ œ ๊ฑฐ๋ž˜์†Œ์™€ TradingView ํ‘œ์‹œ๊ฐ€ ๋‹ค๋ฅธ ๊ฒฝ์šฐ)
829
+ const TRADINGVIEW_SYMBOLS = {
830
+ 'WMT': 'NASDAQ:WMT', // Walmart - ์‹ค์ œ NYSE์ง€๋งŒ TradingView๋Š” NASDAQ์œผ๋กœ ํ‘œ์‹œ
831
+ 'TRI': 'NYSE:TRI', // Thomson Reuters
832
+ 'LIN': 'NYSE:LIN', // Linde
833
+ 'HON': 'NASDAQ:HON', // Honeywell
834
+ 'AEP': 'NASDAQ:AEP', // American Electric Power
835
+ 'CEG': 'NASDAQ:CEG', // Constellation Energy
836
+ 'ASML': 'NASDAQ:ASML', // ASML (๋„ค๋œ๋ž€๋“œ์ง€๋งŒ NASDAQ ADR)
837
+ 'AZN': 'NASDAQ:AZN', // AstraZeneca
838
+ 'PDD': 'NASDAQ:PDD' // PDD Holdings
839
+ };
840
+
841
+ // ํ‹ฐ์ปค์— ๋งž๋Š” TradingView ์‹ฌ๋ณผ ๋ฐ˜ํ™˜
842
+ function getTradingViewSymbol(ticker, defaultExchange) {
843
+ // ํŠน์ˆ˜ ๋งคํ•‘์ด ์žˆ์œผ๋ฉด ์‚ฌ์šฉ
844
+ if (TRADINGVIEW_SYMBOLS[ticker]) {
845
+ return TRADINGVIEW_SYMBOLS[ticker];
846
+ }
847
+ // NYSE ์ข…๋ชฉ์ด๋ฉด NYSE ์‚ฌ์šฉ
848
+ if (NYSE_TICKERS.includes(ticker)) {
849
+ return `NYSE:${ticker}`;
850
+ }
851
+ // ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ
852
+ return `${defaultExchange || 'NASDAQ'}:${ticker}`;
853
+ }
854
+
855
+ function loadMiniChartsForPage(prefix) {
856
+ if (!screeningData) return;
857
+
858
+ const containers = document.querySelectorAll(`[id^="mini-chart-${prefix}-"]`);
859
+ containers.forEach(container => {
860
+ if (container.dataset.loaded) return;
861
+ container.dataset.loaded = 'true';
862
+
863
+ const ticker = container.id.replace(`mini-chart-${prefix}-`, '');
864
+ const stock = screeningData.find(s => s.ticker === ticker);
865
+ if (!stock) return;
866
+
867
+ // TradingView ์‹ฌ๋ณผ ๊ฐ€์ ธ์˜ค๊ธฐ
868
+ const symbol = getTradingViewSymbol(ticker, stock.exchange);
869
+
870
+ try {
871
+ new TradingView.MediumWidget({
872
+ symbols: [[ticker, `${symbol}|3D`]],
873
+ chartOnly: true,
874
+ width: '100%',
875
+ height: 60,
876
+ locale: 'kr',
877
+ colorTheme: 'dark',
878
+ autosize: false,
879
+ showVolume: false,
880
+ hideDateRanges: true,
881
+ scalePosition: 'no',
882
+ scaleMode: 'Normal',
883
+ fontFamily: 'sans-serif',
884
+ noTimeScale: true,
885
+ chartType: 'area',
886
+ lineColor: stock.change_percent >= 0 ? '#00ff88' : '#ff4757',
887
+ bottomColor: stock.change_percent >= 0 ? 'rgba(0,255,136,0.1)' : 'rgba(255,71,87,0.1)',
888
+ lineWidth: 2,
889
+ container_id: container.id
890
+ });
891
+ } catch (e) {
892
+ console.log('๋ฏธ๋‹ˆ์ฐจํŠธ ๋กœ๋“œ ์‹คํŒจ:', ticker, e);
893
+ }
894
+ });
895
+ }
896
+
897
+ function initMarketTab() {
898
+ try {
899
+ initSentimentGauge();
900
+ initSectorChart();
901
+ initUpDownChart();
902
+ initKeywordCloud();
903
+ initTradingViewWidgets();
904
+ } catch (e) {
905
+ console.error('initMarketTab ์˜ค๋ฅ˜:', e);
906
+ }
907
+ }
908
+
909
+ // ์„นํ„ฐ๋ณ„ ํผํฌ๋จผ์Šค ์ฐจํŠธ
910
+ let sectorChartInstance = null;
911
+ function initSectorChart() {
912
+ try {
913
+ const canvas = document.getElementById('sectorChart');
914
+ if (!canvas || !screeningData || screeningData.length === 0) return;
915
+ if (sectorChartInstance) sectorChartInstance.destroy();
916
+
917
+ // ์„นํ„ฐ๋ณ„ ํ‰๊ท  ๋“ฑ๋ฝ๋ฅ  ๊ณ„์‚ฐ
918
+ const sectorData = {};
919
+ screeningData.forEach(s => {
920
+ const sector = s.sector || 'Other';
921
+ if (!sectorData[sector]) sectorData[sector] = { sum: 0, count: 0 };
922
+ sectorData[sector].sum += s.change_percent || 0;
923
+ sectorData[sector].count++;
924
+ });
925
+
926
+ const sectors = Object.keys(sectorData).map(k => ({
927
+ name: k,
928
+ avg: sectorData[k].sum / sectorData[k].count
929
+ })).sort((a, b) => b.avg - a.avg);
930
+
931
+ if (sectors.length === 0) return;
932
+
933
+ const labels = sectors.map(s => s.name.replace('Consumer ', '').replace('Communication', 'Comm'));
934
+ const data = sectors.map(s => s.avg);
935
+ const colors = data.map(v => v >= 0 ? '#00ff88' : '#ff4757');
936
+
937
+ sectorChartInstance = new Chart(canvas, {
938
+ type: 'bar',
939
+ data: {
940
+ labels: labels,
941
+ datasets: [{
942
+ data: data,
943
+ backgroundColor: colors,
944
+ borderRadius: 4,
945
+ barThickness: 18
946
+ }]
947
+ },
948
+ options: {
949
+ indexAxis: 'y',
950
+ responsive: true,
951
+ maintainAspectRatio: false,
952
+ plugins: { legend: { display: false } },
953
+ scales: {
954
+ x: {
955
+ grid: { color: 'rgba(255,255,255,0.1)' },
956
+ ticks: { color: '#888', callback: v => v + '%' }
957
+ },
958
+ y: {
959
+ grid: { display: false },
960
+ ticks: { color: '#ccc', font: { size: 11 } }
961
+ }
962
+ }
963
+ }
964
+ });
965
+ } catch (e) {
966
+ console.error('initSectorChart ์˜ค๋ฅ˜:', e);
967
+ }
968
+ }
969
+
970
+ // ์ƒ์Šน/ํ•˜๋ฝ ๋น„์œจ ๋„๋„› ์ฐจํŠธ
971
+ let upDownChartInstance = null;
972
+ function initUpDownChart() {
973
+ try {
974
+ const canvas = document.getElementById('upDownChart');
975
+ if (!canvas || !screeningData || screeningData.length === 0) return;
976
+ if (upDownChartInstance) upDownChartInstance.destroy();
977
+
978
+ let up = 0, down = 0, flat = 0;
979
+ screeningData.forEach(s => {
980
+ const chg = s.change_percent || 0;
981
+ if (chg > 0.1) up++;
982
+ else if (chg < -0.1) down++;
983
+ else flat++;
984
+ });
985
+
986
+ upDownChartInstance = new Chart(canvas, {
987
+ type: 'doughnut',
988
+ data: {
989
+ labels: ['์ƒ์Šน', 'ํ•˜๋ฝ', '๋ณดํ•ฉ'],
990
+ datasets: [{
991
+ data: [up, down, flat],
992
+ backgroundColor: ['#00ff88', '#ff4757', '#ffa502'],
993
+ borderWidth: 0
994
+ }]
995
+ },
996
+ options: {
997
+ responsive: true,
998
+ maintainAspectRatio: false,
999
+ cutout: '60%',
1000
+ plugins: {
1001
+ legend: { display: false }
1002
+ }
1003
+ }
1004
+ });
1005
+
1006
+ // ๋ ˆ์ „๋“œ ํ‘œ์‹œ
1007
+ const legendEl = document.getElementById('upDownLegend');
1008
+ if (legendEl) {
1009
+ legendEl.innerHTML = `
1010
+ <div class="legend-item"><div class="legend-dot" style="background:#00ff88"></div>์ƒ์Šน ${up}๊ฐœ</div>
1011
+ <div class="legend-item"><div class="legend-dot" style="background:#ff4757"></div>ํ•˜๋ฝ ${down}๊ฐœ</div>
1012
+ <div class="legend-item"><div class="legend-dot" style="background:#ffa502"></div>๋ณดํ•ฉ ${flat}๊ฐœ</div>
1013
+ `;
1014
+ }
1015
+ } catch (e) {
1016
+ console.error('initUpDownChart ์˜ค๋ฅ˜:', e);
1017
+ }
1018
+ }
1019
+
1020
+ // TradingView ์œ„์ ฏ ์ดˆ๊ธฐํ™”
1021
+ function initTradingViewWidgets() {
1022
+ try {
1023
+ // ํ‹ฐ์ปค ํ…Œ์ดํ”„
1024
+ const tickerTape = document.getElementById('ticker-tape-widget');
1025
+ if (tickerTape && !tickerTape.dataset.loaded) {
1026
+ tickerTape.dataset.loaded = 'true';
1027
+ tickerTape.innerHTML = `
1028
+ <div class="tradingview-widget-container">
1029
+ <div class="tradingview-widget-container__widget"></div>
1030
+ </div>
1031
+ `;
1032
+ const script = document.createElement('script');
1033
+ script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-ticker-tape.js';
1034
+ script.async = true;
1035
+ script.innerHTML = JSON.stringify({
1036
+ "symbols": [
1037
+ {"proName": "FOREXCOM:SPXUSD", "title": "S&P 500"},
1038
+ {"proName": "FOREXCOM:NSXUSD", "title": "NASDAQ"},
1039
+ {"proName": "BITSTAMP:BTCUSD", "title": "Bitcoin"},
1040
+ {"proName": "NASDAQ:NVDA", "title": "NVIDIA"},
1041
+ {"proName": "NASDAQ:AAPL", "title": "Apple"},
1042
+ {"proName": "NASDAQ:MSFT", "title": "Microsoft"},
1043
+ {"proName": "NASDAQ:GOOGL", "title": "Alphabet"},
1044
+ {"proName": "NASDAQ:AMZN", "title": "Amazon"},
1045
+ {"proName": "NASDAQ:META", "title": "Meta"},
1046
+ {"proName": "NASDAQ:TSLA", "title": "Tesla"}
1047
+ ],
1048
+ "showSymbolLogo": true,
1049
+ "isTransparent": true,
1050
+ "displayMode": "adaptive",
1051
+ "colorTheme": "dark",
1052
+ "locale": "kr"
1053
+ });
1054
+ tickerTape.querySelector('.tradingview-widget-container').appendChild(script);
1055
+ }
1056
+
1057
+ // ๋งˆ์ผ“ ์˜ค๋ฒ„๋ทฐ
1058
+ const marketOverview = document.getElementById('market-overview-widget');
1059
+ if (marketOverview && !marketOverview.dataset.loaded) {
1060
+ marketOverview.dataset.loaded = 'true';
1061
+ marketOverview.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`;
1062
+ const script = document.createElement('script');
1063
+ script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-market-overview.js';
1064
+ script.async = true;
1065
+ script.innerHTML = JSON.stringify({
1066
+ "colorTheme": "dark",
1067
+ "dateRange": "1D",
1068
+ "showChart": true,
1069
+ "locale": "kr",
1070
+ "largeChartUrl": "",
1071
+ "isTransparent": true,
1072
+ "showSymbolLogo": true,
1073
+ "showFloatingTooltip": true,
1074
+ "width": "100%",
1075
+ "height": "100%",
1076
+ "tabs": [
1077
+ {
1078
+ "title": "์ง€์ˆ˜",
1079
+ "symbols": [
1080
+ {"s": "FOREXCOM:SPXUSD", "d": "S&P 500"},
1081
+ {"s": "FOREXCOM:NSXUSD", "d": "NASDAQ"},
1082
+ {"s": "FOREXCOM:DJI", "d": "Dow Jones"},
1083
+ {"s": "INDEX:VIX", "d": "VIX"}
1084
+ ],
1085
+ "originalTitle": "Indices"
1086
+ },
1087
+ {
1088
+ "title": "๋น…ํ…Œํฌ",
1089
+ "symbols": [
1090
+ {"s": "NASDAQ:NVDA", "d": "NVIDIA"},
1091
+ {"s": "NASDAQ:AAPL", "d": "Apple"},
1092
+ {"s": "NASDAQ:MSFT", "d": "Microsoft"},
1093
+ {"s": "NASDAQ:GOOGL", "d": "Alphabet"},
1094
+ {"s": "NASDAQ:META", "d": "Meta"}
1095
+ ],
1096
+ "originalTitle": "Big Tech"
1097
+ }
1098
+ ]
1099
+ });
1100
+ marketOverview.querySelector('.tradingview-widget-container').appendChild(script);
1101
+ }
1102
+
1103
+ // ํžˆํŠธ๋งต
1104
+ const heatmap = document.getElementById('stock-heatmap-widget');
1105
+ if (heatmap && !heatmap.dataset.loaded) {
1106
+ heatmap.dataset.loaded = 'true';
1107
+ heatmap.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`;
1108
+ const script = document.createElement('script');
1109
+ script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-stock-heatmap.js';
1110
+ script.async = true;
1111
+ script.innerHTML = JSON.stringify({
1112
+ "exchanges": [],
1113
+ "dataSource": "SPX500",
1114
+ "grouping": "sector",
1115
+ "blockSize": "market_cap_basic",
1116
+ "blockColor": "change",
1117
+ "locale": "kr",
1118
+ "symbolUrl": "",
1119
+ "colorTheme": "dark",
1120
+ "hasTopBar": false,
1121
+ "isDataSet498or": true,
1122
+ "isZoomEnabled": true,
1123
+ "hasSymbolTooltip": true,
1124
+ "isMonoSize": false,
1125
+ "width": "100%",
1126
+ "height": "100%"
1127
+ });
1128
+ heatmap.querySelector('.tradingview-widget-container').appendChild(script);
1129
+ }
1130
+ } catch (e) {
1131
+ console.error('initTradingViewWidgets ์˜ค๋ฅ˜:', e);
1132
+ }
1133
+ }
1134
+
1135
+ // ํžˆํŠธ๋งต ์ „ํ™˜ ํ•จ์ˆ˜
1136
+ function switchHeatmap(dataSource, btn) {
1137
+ // ํƒญ ํ™œ์„ฑํ™”
1138
+ document.querySelectorAll('.heatmap-tab').forEach(t => t.classList.remove('active'));
1139
+ btn.classList.add('active');
1140
+
1141
+ // ํžˆํŠธ๋งต ์žฌ๋กœ๋“œ
1142
+ const heatmap = document.getElementById('stock-heatmap-widget');
1143
+ heatmap.innerHTML = `<div class="tradingview-widget-container" style="height:100%;width:100%"><div class="tradingview-widget-container__widget" style="height:100%;width:100%"></div></div>`;
1144
+ const script = document.createElement('script');
1145
+ script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-stock-heatmap.js';
1146
+ script.async = true;
1147
+ script.innerHTML = JSON.stringify({
1148
+ "exchanges": [],
1149
+ "dataSource": dataSource,
1150
+ "grouping": "sector",
1151
+ "blockSize": "market_cap_basic",
1152
+ "blockColor": "change",
1153
+ "locale": "kr",
1154
+ "symbolUrl": "",
1155
+ "colorTheme": "dark",
1156
+ "hasTopBar": false,
1157
+ "isDataSetEnabled": true,
1158
+ "isZoomEnabled": true,
1159
+ "hasSymbolTooltip": true,
1160
+ "isMonoSize": false,
1161
+ "width": "100%",
1162
+ "height": "100%"
1163
+ });
1164
+ heatmap.querySelector('.tradingview-widget-container').appendChild(script);
1165
+ }
1166
+
1167
+ function initSentimentGauge() {
1168
+ const canvas = document.getElementById('sentimentGauge');
1169
+ if (!canvas) return;
1170
+ if (sentimentGaugeInstance) sentimentGaugeInstance.destroy();
1171
+
1172
+ let score = 50;
1173
+ let pos = 0, neg = 0;
1174
+ newsData.forEach(n => { if (n.sentiment === 'positive') pos++; else if (n.sentiment === 'negative') neg++; });
1175
+ if (newsData.length > 0) score = ((pos * 100) + ((newsData.length - pos - neg) * 50)) / newsData.length;
1176
+
1177
+ if (screeningData && screeningData.length > 0) {
1178
+ const avgChange = screeningData.reduce((s, x) => s + (x.change_percent || 0), 0) / screeningData.length;
1179
+ score += avgChange * 5;
1180
+ }
1181
+ score = Math.max(0, Math.min(100, score));
1182
+
1183
+ sentimentGaugeInstance = new Chart(canvas, {
1184
+ type: 'doughnut',
1185
+ data: { datasets: [{ data: [score, 100 - score], backgroundColor: [score < 20 ? '#ff4757' : score < 40 ? '#ffa502' : score < 60 ? '#78909c' : score < 80 ? '#2ed573' : '#00ff88', 'rgba(255,255,255,0.05)'], borderWidth: 0 }] },
1186
+ options: { circumference: 180, rotation: -90, cutout: '75%', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false }, tooltip: { enabled: false } } }
1187
+ });
1188
+
1189
+ document.getElementById('sentimentValue').textContent = score.toFixed(1);
1190
+ let label = 'Neutral', color = '#78909c';
1191
+ if (score < 20) { label = 'Strong Sell'; color = '#ff4757'; }
1192
+ else if (score < 40) { label = 'Sell'; color = '#ffa502'; }
1193
+ else if (score < 60) { label = 'Neutral'; color = '#78909c'; }
1194
+ else if (score < 80) { label = 'Buy'; color = '#2ed573'; }
1195
+ else { label = 'Strong Buy'; color = '#00ff88'; }
1196
+ document.getElementById('sentimentLabel').textContent = label;
1197
+ document.getElementById('sentimentLabel').style.color = color;
1198
+ }
1199
+
1200
+ function initKeywordCloud() {
1201
+ const container = document.getElementById('keywordCloud');
1202
+ if (!container) return;
1203
+ container.innerHTML = '';
1204
+ const keywords = {};
1205
+
1206
+ // ํ•œ๊ธ€ + ์˜์–ด ํ‚ค์›Œ๋“œ ํ™•์žฅ
1207
+ const kwList = [
1208
+ // ๊ฑฐ์‹œ๊ฒฝ์ œ
1209
+ '๊ธˆ๋ฆฌ', '์ธํ”Œ๋ ˆ์ด์…˜', 'Fed', 'FOMC', '๊ธˆ๋ฆฌ์ธํ•˜', '๊ธˆ๋ฆฌ๋™๊ฒฐ', 'CPI', 'GDP', '๊ณ ์šฉ', '์‹ค์—…๋ฅ ',
1210
+ 'interest rate', 'inflation', 'rate cut', 'rate hike', 'economy',
1211
+ // ๋ฌด์—ญ/์ง€์ •ํ•™
1212
+ '๊ด€์„ธ', '๋ฌด์—ญ', '์ˆ˜์ถœ', '์ˆ˜์ž…', '์ œ์žฌ', 'ํŠธ๋Ÿผํ”„', 'Trump', '์ค‘๊ตญ', 'China', '๋Ÿฌ์‹œ์•„',
1213
+ '์šฐํฌ๋ผ์ด๋‚˜', '์ „์Ÿ', 'tariff', 'trade war',
1214
+ // ์—๋„ˆ์ง€
1215
+ '์œ ๊ฐ€', 'OPEC', '์›์œ ', '์ฒœ์—ฐ๊ฐ€์Šค', 'oil', 'crude', 'energy',
1216
+ // ๊ธฐ์ˆ /AI
1217
+ 'AI', '์ธ๊ณต์ง€๋Šฅ', '๋ฐ˜๋„์ฒด', 'GPU', 'chip', 'semiconductor', 'NVIDIA', 'AMD', 'Intel',
1218
+ 'OpenAI', 'ChatGPT', 'Gemini', '๋ฐ์ดํ„ฐ์„ผํ„ฐ', 'cloud',
1219
+ // ์ „๊ธฐ์ฐจ/๋ฐฐํ„ฐ๋ฆฌ
1220
+ '๋ฐฐํ„ฐ๋ฆฌ', 'EV', '์ „๊ธฐ์ฐจ', 'Tesla', '๋ฆฌํŠฌ', '์ถฉ์ „', 'electric vehicle',
1221
+ // ์•”ํ˜ธํ™”ํ
1222
+ 'Bitcoin', 'BTC', 'crypto', '์•”ํ˜ธํ™”ํ', 'ETF', 'Ethereum',
1223
+ // ๊ธฐ์—… ์‹ค์ 
1224
+ '์‹ค์ ', '๋งค์ถœ', '์ˆœ์ด์ต', 'earnings', 'revenue', 'profit', 'beat', 'miss', 'guidance',
1225
+ // M&A/IPO
1226
+ 'M&A', 'IPO', '์ธ์ˆ˜', 'ํ•ฉ๋ณ‘', 'acquisition', 'merger',
1227
+ // ๊ทœ์ œ
1228
+ '๊ทœ์ œ', 'SEC', 'FTC', 'antitrust', '๋ฐ˜๋…์ ', '์†Œ์†ก', 'lawsuit',
1229
+ // ๏ฟฝ๏ฟฝ๏ฟฝํƒ€ ์ด์Šˆ
1230
+ '๋ฐฐ๋‹น', 'dividend', '์ž์‚ฌ์ฃผ', 'buyback', '๋ฆฌ์ฝœ', 'recall', 'ํŒŒ์—…', 'strike',
1231
+ // ๋น…ํ…Œํฌ
1232
+ 'Apple', 'Microsoft', 'Google', 'Amazon', 'Meta', 'Netflix'
1233
+ ];
1234
+
1235
+ // ๋‰ด์Šค ์ œ๋ชฉ + ๋‚ด์šฉ์—์„œ ํ‚ค์›Œ๋“œ ์ถ”์ถœ
1236
+ newsData.forEach(n => {
1237
+ const text = ((n.title || '') + ' ' + (n.content || '') + ' ' + (n.title_original || '')).toLowerCase();
1238
+ kwList.forEach(k => {
1239
+ const kLower = k.toLowerCase();
1240
+ if (text.includes(kLower)) {
1241
+ // ๋Œ€์†Œ๋ฌธ์ž ํ†ต์ผ (ํ‘œ์‹œ์šฉ)
1242
+ const displayKey = k.charAt(0).toUpperCase() + k.slice(1);
1243
+ keywords[displayKey] = (keywords[displayKey] || 0) + 1;
1244
+ }
1245
+ });
1246
+ });
1247
+
1248
+ // ์Šคํฌ๋ฆฌ๋‹ ๋ฐ์ดํ„ฐ์—์„œ ์„นํ„ฐ ํ‚ค์›Œ๋“œ ์ถ”๊ฐ€
1249
+ if (screeningData) {
1250
+ screeningData.forEach(s => {
1251
+ if (s.sector) {
1252
+ const sectorKey = s.sector.replace('Consumer ', '');
1253
+ keywords[sectorKey] = (keywords[sectorKey] || 0) + 1;
1254
+ }
1255
+ });
1256
+ }
1257
+
1258
+ const sorted = Object.entries(keywords).sort((a, b) => b[1] - a[1]).slice(0, 20);
1259
+ const max = sorted[0] ? sorted[0][1] : 1;
1260
+ const colors = ['#ff4757', '#ffa502', '#2ed573', '#00d9ff', '#9b59b6', '#e74c3c', '#3498db', '#1abc9c', '#f39c12', '#e91e63', '#00ff88', '#ff6b81', '#7bed9f', '#70a1ff', '#5352ed'];
1261
+
1262
+ sorted.forEach(([kw, cnt], i) => {
1263
+ const tag = document.createElement('span');
1264
+ tag.className = 'keyword-tag';
1265
+ const ratio = cnt / max;
1266
+ tag.classList.add(ratio > 0.7 ? 'size-xl' : ratio > 0.4 ? 'size-lg' : ratio > 0.2 ? 'size-md' : 'size-sm');
1267
+ tag.style.backgroundColor = colors[i % colors.length];
1268
+ tag.style.color = '#fff';
1269
+ tag.textContent = `${kw} (${cnt})`;
1270
+ tag.onclick = () => window.open(`https://www.google.com/search?q=${encodeURIComponent(kw + ' stock market news')}&tbm=nws`, '_blank');
1271
+ container.appendChild(tag);
1272
+ });
1273
+
1274
+ if (sorted.length === 0) {
1275
+ // ๊ธฐ๋ณธ ํŠธ๋ Œ๋“œ ํ‚ค์›Œ๋“œ ํ‘œ์‹œ
1276
+ const defaultKeywords = ['AI', 'Fed', '๋ฐ˜๋„์ฒด', 'Tesla', 'NVIDIA', 'Bitcoin', '์‹ค์ ', '๊ธˆ๋ฆฌ'];
1277
+ defaultKeywords.forEach((kw, i) => {
1278
+ const tag = document.createElement('span');
1279
+ tag.className = 'keyword-tag size-md';
1280
+ tag.style.backgroundColor = colors[i % colors.length];
1281
+ tag.style.color = '#fff';
1282
+ tag.textContent = kw;
1283
+ tag.onclick = () => window.open(`https://www.google.com/search?q=${encodeURIComponent(kw + ' stock market news')}&tbm=nws`, '_blank');
1284
+ container.appendChild(tag);
1285
+ });
1286
+ }
1287
+ }
1288
+
1289
+ function initStockHeatmap() {
1290
+ const container = document.getElementById('stockHeatmap');
1291
+ container.innerHTML = '';
1292
+ if (!screeningData || screeningData.length === 0) {
1293
+ container.innerHTML = '<div style="color:#888;padding:20px;">๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.</div>';
1294
+ return;
1295
+ }
1296
+
1297
+ // ์‹œ๊ฐ€์ด์•ก ๊ธฐ์ค€ ์ตœ๋Œ€๊ฐ’ ๊ณ„์‚ฐ
1298
+ const maxMarketCap = Math.max(...screeningData.map(s => s.market_cap || 0));
1299
+
1300
+ screeningData.forEach(stock => {
1301
+ const cell = document.createElement('div');
1302
+ cell.className = 'heatmap-cell';
1303
+ const chg = stock.change_percent || 0;
1304
+
1305
+ // ์‹œ๊ฐ€์ด์•ก ๋น„์ค‘์— ๋”ฐ๋ฅธ ํฌ๊ธฐ ์กฐ์ ˆ
1306
+ const mcapRatio = (stock.market_cap || 0) / maxMarketCap;
1307
+ let cellSize = 80; // ๊ธฐ๋ณธ ํฌ๊ธฐ
1308
+ let fontSize = 0.85;
1309
+ if (mcapRatio > 0.8) { cellSize = 140; fontSize = 1.2; }
1310
+ else if (mcapRatio > 0.5) { cellSize = 120; fontSize = 1.05; }
1311
+ else if (mcapRatio > 0.3) { cellSize = 100; fontSize = 0.95; }
1312
+ else if (mcapRatio > 0.15) { cellSize = 90; fontSize = 0.9; }
1313
+
1314
+ cell.style.minWidth = cellSize + 'px';
1315
+ cell.style.minHeight = (cellSize * 0.7) + 'px';
1316
+ cell.style.flex = `0 0 ${cellSize}px`;
1317
+
1318
+ // ๋“ฑ๋ฝ๋ฅ ์— ๋”ฐ๋ฅธ ์ƒ‰์ƒ
1319
+ if (chg > 3) cell.classList.add('strong-up');
1320
+ else if (chg > 1) cell.classList.add('up');
1321
+ else if (chg > 0) cell.classList.add('slight-up');
1322
+ else if (chg > -1) cell.classList.add('neutral');
1323
+ else if (chg > -3) cell.classList.add('slight-down');
1324
+ else if (chg > -5) cell.classList.add('down');
1325
+ else cell.classList.add('strong-down');
1326
+
1327
+ cell.innerHTML = `<div class="ticker" style="font-size:${fontSize}em">${stock.ticker}</div><div class="change">${chg >= 0 ? '+' : ''}${chg.toFixed(2)}%</div><div class="price">$${stock.price.toFixed(2)}</div>`;
1328
+ cell.onclick = () => openChartModal(stock.ticker, stock.name, stock.exchange || 'NASDAQ');
1329
+ container.appendChild(cell);
1330
+ });
1331
+ }
1332
+
1333
+ function initMiniCharts() {
1334
+ if (!screeningData) return;
1335
+ screeningData.forEach((stock, i) => {
1336
+ const container = document.getElementById(`mini-chart-${stock.ticker}`);
1337
+ if (!container || container.dataset.loaded) return;
1338
+ container.dataset.loaded = 'true';
1339
+ container.innerHTML = '';
1340
+
1341
+ // ๊ฑฐ๋ž˜์†Œ์— ๋”ฐ๋ฅธ ์‹ฌ๋ณผ ์„ค์ •
1342
+ const exchange = stock.exchange || 'NASDAQ';
1343
+ const symbol = `${exchange}:${stock.ticker}`;
1344
+
1345
+ new TradingView.MediumWidget({
1346
+ symbols: [[stock.ticker, `${symbol}|3D`]],
1347
+ chartOnly: true,
1348
+ width: '100%',
1349
+ height: 60,
1350
+ locale: 'kr',
1351
+ colorTheme: 'dark',
1352
+ autosize: false,
1353
+ showVolume: false,
1354
+ hideDateRanges: true,
1355
+ scalePosition: 'no',
1356
+ scaleMode: 'Normal',
1357
+ fontFamily: 'sans-serif',
1358
+ noTimeScale: true,
1359
+ chartType: 'area',
1360
+ lineColor: stock.change_percent >= 0 ? '#00ff88' : '#ff4757',
1361
+ bottomColor: stock.change_percent >= 0 ? 'rgba(0,255,136,0.1)' : 'rgba(255,71,87,0.1)',
1362
+ lineWidth: 2,
1363
+ container_id: `mini-chart-${stock.ticker}`
1364
+ });
1365
+ });
1366
+ }
1367
+
1368
+ function openChartModal(ticker, name, exchange) {
1369
+ // TradingView ์‹ฌ๋ณผ ๊ฐ€์ ธ์˜ค๊ธฐ
1370
+ const symbol = getTradingViewSymbol(ticker, exchange);
1371
+
1372
+ document.getElementById('chart-modal-title').textContent = `${ticker} - ${name}`;
1373
+ document.getElementById('chartModal').classList.add('active');
1374
+ document.getElementById('tradingview-chart').innerHTML = '';
1375
+ new TradingView.widget({
1376
+ autosize: true,
1377
+ symbol: symbol,
1378
+ interval: 'D',
1379
+ timezone: 'Asia/Seoul',
1380
+ theme: 'dark',
1381
+ style: '1',
1382
+ locale: 'kr',
1383
+ toolbar_bg: '#1a1a2e',
1384
+ enable_publishing: false,
1385
+ allow_symbol_change: true,
1386
+ container_id: 'tradingview-chart',
1387
+ hide_side_toolbar: false,
1388
+ studies: ['RSI@tv-basicstudies', 'MASimple@tv-basicstudies']
1389
+ });
1390
+ }
1391
+
1392
+ function closeModal(modalId) {
1393
+ document.getElementById(modalId).classList.remove('active');
1394
+ }
1395
+
1396
+ function renderMarkdown(text) {
1397
+ if (!text) return '-';
1398
+ if (typeof marked !== 'undefined') return marked.parse(text);
1399
+ return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\n/g, '<br>');
1400
+ }
1401
+
1402
+ function initAnalysisCharts(report) {
1403
+ [metricsChartInstance, priceChartInstance, riskChartInstance, growthChartInstance].forEach(c => c && c.destroy());
1404
+ const cp = report.current_price || 100, tp = report.target_price || 112;
1405
+ const mc = document.getElementById('metricsChart');
1406
+ if (mc) metricsChartInstance = new Chart(mc, { type: 'radar', data: { labels: ['์ˆ˜์ต์„ฑ', '์„ฑ์žฅ์„ฑ', '์•ˆ์ •์„ฑ', '๋ฐธ๋ฅ˜์—์ด์…˜', '๋ชจ๋ฉ˜ํ…€'], datasets: [{ data: [75+Math.random()*20, 70+Math.random()*25, 65+Math.random()*20, 60+Math.random()*25, 72+Math.random()*20], backgroundColor: 'rgba(0,217,255,0.2)', borderColor: '#00d9ff', borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { r: { beginAtZero: true, max: 100, ticks: { color: '#888', backdropColor: 'transparent' }, grid: { color: 'rgba(255,255,255,0.1)' }, pointLabels: { color: '#ccc', font: { size: 10 } } } } } });
1407
+ const pc = document.getElementById('priceChart');
1408
+ if (pc) priceChartInstance = new Chart(pc, { type: 'bar', data: { labels: ['ํ˜„์žฌ๊ฐ€', '๋ชฉํ‘œ๊ฐ€', '์ปจ์„ผ์„œ์Šค'], datasets: [{ data: [cp, tp, tp*0.95], backgroundColor: ['#3498db', '#00ff88', '#9b59b6'], borderRadius: 5 }] }, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', plugins: { legend: { display: false } }, scales: { x: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#888' } }, y: { grid: { display: false }, ticks: { color: '#ccc' } } } } });
1409
+ const rc = document.getElementById('riskChart');
1410
+ if (rc) riskChartInstance = new Chart(rc, { type: 'doughnut', data: { labels: ['๊ธฐํšŒ', '๋ฆฌ์Šคํฌ', '์ค‘๋ฆฝ'], datasets: [{ data: [55+Math.random()*15, 25+Math.random()*10, 20], backgroundColor: ['#00ff88', '#ff4757', '#ffa502'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#ccc', font: { size: 10 } } } } } });
1411
+ const gc = document.getElementById('growthChart');
1412
+ if (gc) growthChartInstance = new Chart(gc, { type: 'line', data: { labels: ['2024', '2025E', '2026E', '2027E'], datasets: [{ label: '๋งค์ถœ', data: [8,12,15,18].map(v => v+Math.random()*5), borderColor: '#00d9ff', fill: true, backgroundColor: 'rgba(0,217,255,0.1)', tension: 0.4 }, { label: 'EPS', data: [10,15,20,25].map(v => v+Math.random()*5), borderColor: '#00ff88', fill: true, backgroundColor: 'rgba(0,255,136,0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: '#ccc', font: { size: 10 } } } }, scales: { y: { grid: { color: 'rgba(255,255,255,0.1)' }, ticks: { color: '#888' } }, x: { grid: { display: false }, ticks: { color: '#ccc' } } } } });
1413
+ }
1414
+
1415
+ async function runDeepAnalysis(ticker, companyName) {
1416
+ currentTicker = ticker;
1417
+ document.getElementById(`deep-status-${ticker}`).innerHTML = 'โณ ๋ถ„์„ ์ค‘...';
1418
+ document.getElementById('modal-ticker').textContent = `${ticker} (${companyName})`;
1419
+ document.getElementById('deepAnalysisModal').classList.add('active');
1420
+ document.getElementById('loading-indicator').classList.add('active');
1421
+ document.getElementById('deep-analysis-result').style.display = 'none';
1422
+ try {
1423
+ const res = await fetch(`/api/deep-analysis/${ticker}`);
1424
+ const data = await res.json();
1425
+ if (data.success) {
1426
+ displayDeepAnalysisResult(data.report, ticker);
1427
+ document.getElementById(`deep-status-${ticker}`).innerHTML = 'โœ… ์™„๋ฃŒ';
1428
+ } else {
1429
+ document.getElementById(`deep-status-${ticker}`).innerHTML = 'โŒ ์‹คํŒจ';
1430
+ alert('๋ถ„์„ ์‹คํŒจ: ' + (data.error || '์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜'));
1431
+ }
1432
+ } catch (e) {
1433
+ document.getElementById(`deep-status-${ticker}`).innerHTML = 'โŒ ์˜ค๋ฅ˜';
1434
+ alert('์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.');
1435
+ }
1436
+ document.getElementById('loading-indicator').classList.remove('active');
1437
+ }
1438
+
1439
+ function displayDeepAnalysisResult(report, ticker) {
1440
+ ['executive_summary', 'company_overview', 'financial_analysis', 'technical_analysis', 'industry_analysis', 'risk_assessment', 'investment_thesis', 'price_targets', 'catalysts', 'final_recommendation'].forEach(key => {
1441
+ document.getElementById(`result-${key.replace(/_/g, '-')}`).innerHTML = renderMarkdown(report[key]);
1442
+ });
1443
+ document.getElementById('result-current-price').textContent = report.current_price ? `$${report.current_price.toLocaleString()}` : '-';
1444
+ document.getElementById('result-target-price').textContent = report.target_price ? `$${report.target_price.toLocaleString()}` : '-';
1445
+ document.getElementById('result-rating').textContent = report.rating || 'Buy';
1446
+ document.getElementById('result-upside').textContent = report.upside ? `+${report.upside}%` : '-';
1447
+ setTimeout(() => initAnalysisCharts(report), 100);
1448
+ document.getElementById('download-docx-btn').href = `/api/download-docx/${ticker}`;
1449
+ document.getElementById('download-docx-btn').style.display = 'inline-flex';
1450
+ document.getElementById('download-pdf-btn').style.display = 'inline-flex';
1451
+ document.getElementById('deep-analysis-result').style.display = 'block';
1452
+ }
1453
+
1454
+ async function downloadAsPDF() {
1455
+ const btn = document.getElementById('download-pdf-btn');
1456
+ const originalText = btn.textContent;
1457
+ btn.textContent = 'โณ ์ƒ์„ฑ ์ค‘...';
1458
+ btn.disabled = true;
1459
+
1460
+ try {
1461
+ // ์ „์ฒด ๊ฒฐ๊ณผ ์˜์—ญ ์บก์ฒ˜
1462
+ const resultArea = document.getElementById('deep-analysis-result');
1463
+ if (!resultArea) {
1464
+ throw new Error('๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
1465
+ }
1466
+
1467
+ // ์ „์ฒด๋ฅผ ํ•˜๋‚˜์˜ ์บ”๋ฒ„์Šค๋กœ ์บก์ฒ˜
1468
+ const canvas = await html2canvas(resultArea, {
1469
+ scale: 2,
1470
+ backgroundColor: '#1a1a2e',
1471
+ logging: false,
1472
+ useCORS: true,
1473
+ allowTaint: true,
1474
+ scrollY: -window.scrollY
1475
+ });
1476
+
1477
+ const imgData = canvas.toDataURL('image/jpeg', 0.9);
1478
+
1479
+ const { jsPDF } = window.jspdf;
1480
+ const pdf = new jsPDF('p', 'mm', 'a4');
1481
+ const pdfWidth = pdf.internal.pageSize.getWidth();
1482
+ const pdfHeight = pdf.internal.pageSize.getHeight();
1483
+
1484
+ const imgWidth = pdfWidth - 20;
1485
+ const imgHeight = (canvas.height * imgWidth) / canvas.width;
1486
+
1487
+ // ์—ฌ๋Ÿฌ ํŽ˜์ด์ง€์— ๊ฑธ์ณ ์ด๋ฏธ์ง€ ๋ถ„ํ• 
1488
+ let heightLeft = imgHeight;
1489
+ let position = 10;
1490
+ let page = 1;
1491
+
1492
+ // ์ฒซ ํŽ˜์ด์ง€
1493
+ pdf.addImage(imgData, 'JPEG', 10, position, imgWidth, imgHeight);
1494
+ heightLeft -= (pdfHeight - 20);
1495
+
1496
+ // ์ถ”๊ฐ€ ํŽ˜์ด์ง€
1497
+ while (heightLeft > 0) {
1498
+ position = heightLeft - imgHeight + 10;
1499
+ pdf.addPage();
1500
+ pdf.addImage(imgData, 'JPEG', 10, position, imgWidth, imgHeight);
1501
+ heightLeft -= (pdfHeight - 20);
1502
+ page++;
1503
+ }
1504
+
1505
+ // ํŒŒ์ผ ์ €์žฅ
1506
+ const filename = `${currentTicker}_Analysis_${new Date().toISOString().split('T')[0]}.pdf`;
1507
+ pdf.save(filename);
1508
+
1509
+ btn.textContent = 'โœ… ์™„๋ฃŒ!';
1510
+ setTimeout(() => { btn.textContent = originalText; }, 2000);
1511
+
1512
+ } catch (e) {
1513
+ console.error('PDF ์ƒ์„ฑ ์˜ค๋ฅ˜:', e);
1514
+ alert('PDF ์ƒ์„ฑ ์‹คํŒจ: ' + e.message);
1515
+ btn.textContent = originalText;
1516
+ }
1517
+ btn.disabled = false;
1518
+ }
1519
+
1520
+ document.addEventListener('keydown', e => { if (e.key === 'Escape') { closeModal('deepAnalysisModal'); closeModal('chartModal'); } });
1521
+ document.getElementById('deepAnalysisModal').addEventListener('click', e => { if (e.target.id === 'deepAnalysisModal') closeModal('deepAnalysisModal'); });
1522
+ document.getElementById('chartModal').addEventListener('click', e => { if (e.target.id === 'chartModal') closeModal('chartModal'); });
1523
+
1524
+ document.addEventListener('DOMContentLoaded', () => setTimeout(initMarketTab, 100));
1525
+ </script>
1526
+ </body>
1527
+ </html>