openfree commited on
Commit
117395e
·
verified ·
1 Parent(s): af41c72

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +353 -589
index.html CHANGED
@@ -10,7 +10,9 @@
10
  margin: 0;
11
  padding: 0;
12
  box-sizing: border-box;
13
- }
 
 
14
 
15
  body {
16
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
@@ -101,197 +103,12 @@
101
  to { opacity: 1; }
102
  }
103
 
104
- .methods-grid {
105
- display: grid;
106
- grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
107
- gap: 2rem;
108
- margin-bottom: 3rem;
109
- }
110
-
111
- .method-card {
112
- background: rgba(255, 255, 255, 0.05);
113
- backdrop-filter: blur(10px);
114
- border: 1px solid rgba(255, 255, 255, 0.1);
115
- border-radius: 20px;
116
- padding: 2rem;
117
- transition: all 0.3s ease;
118
- animation: fadeInUp 0.8s ease-out;
119
- position: relative;
120
- overflow: hidden;
121
- }
122
-
123
- .method-card::before {
124
- content: '';
125
- position: absolute;
126
- top: -50%;
127
- left: -50%;
128
- width: 200%;
129
- height: 200%;
130
- background: radial-gradient(circle, rgba(58, 123, 213, 0.1) 0%, transparent 70%);
131
- opacity: 0;
132
- transition: opacity 0.3s ease;
133
- }
134
-
135
- .method-card:hover::before {
136
- opacity: 1;
137
- }
138
-
139
- .method-card:hover {
140
- transform: translateY(-10px);
141
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
142
- border-color: rgba(58, 123, 213, 0.5);
143
- }
144
-
145
- .method-header {
146
- display: flex;
147
- align-items: center;
148
- margin-bottom: 1.5rem;
149
- }
150
-
151
- .method-icon {
152
- width: 60px;
153
- height: 60px;
154
- margin-right: 1rem;
155
- display: flex;
156
- align-items: center;
157
- justify-content: center;
158
- border-radius: 15px;
159
- font-size: 2rem;
160
- }
161
-
162
- .outsource-icon { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
163
- .inhouse-icon { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); }
164
- .platform-icon { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); }
165
-
166
- .method-title {
167
- font-size: 1.5rem;
168
- font-weight: 700;
169
- }
170
-
171
- .method-description {
172
- color: #a8a8b3;
173
- margin-bottom: 1.5rem;
174
- line-height: 1.6;
175
- }
176
-
177
- .cost-breakdown {
178
- background: rgba(0, 0, 0, 0.2);
179
- border-radius: 10px;
180
- padding: 1rem;
181
- margin-bottom: 1.5rem;
182
- }
183
-
184
- .cost-item {
185
- display: flex;
186
- justify-content: space-between;
187
- margin-bottom: 0.5rem;
188
- padding: 0.5rem 0;
189
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
190
- }
191
-
192
- .cost-item:last-child {
193
- border-bottom: none;
194
- }
195
-
196
- .pros-cons {
197
- display: grid;
198
- grid-template-columns: 1fr 1fr;
199
- gap: 1rem;
200
- margin-top: 1.5rem;
201
- }
202
-
203
- .pros, .cons {
204
- padding: 1rem;
205
- border-radius: 10px;
206
- font-size: 0.9rem;
207
- }
208
-
209
- .pros {
210
- background: rgba(46, 213, 115, 0.1);
211
- border: 1px solid rgba(46, 213, 115, 0.3);
212
- }
213
-
214
- .cons {
215
- background: rgba(255, 71, 87, 0.1);
216
- border: 1px solid rgba(255, 71, 87, 0.3);
217
- }
218
-
219
- .pros h4, .cons h4 {
220
- margin-bottom: 0.5rem;
221
- font-size: 1rem;
222
- }
223
-
224
- .pros ul, .cons ul {
225
- list-style: none;
226
- padding-left: 0;
227
- }
228
-
229
- .pros li::before {
230
- content: "✓ ";
231
- color: #2ed573;
232
- font-weight: bold;
233
- }
234
-
235
- .cons li::before {
236
- content: "✗ ";
237
- color: #ff4757;
238
- font-weight: bold;
239
- }
240
-
241
- .calculator-section {
242
- background: rgba(255, 255, 255, 0.05);
243
- backdrop-filter: blur(10px);
244
- border: 1px solid rgba(255, 255, 255, 0.1);
245
- border-radius: 20px;
246
- padding: 2rem;
247
- margin-bottom: 3rem;
248
- animation: fadeInUp 1s ease-out;
249
- }
250
-
251
  .calculator-title {
252
  font-size: 2rem;
253
  margin-bottom: 2rem;
254
  text-align: center;
255
  }
256
 
257
- .input-group {
258
- display: grid;
259
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
260
- gap: 1.5rem;
261
- margin-bottom: 2rem;
262
- }
263
-
264
- .input-field {
265
- display: flex;
266
- flex-direction: column;
267
- }
268
-
269
- .input-field label {
270
- margin-bottom: 0.5rem;
271
- color: #a8a8b3;
272
- font-size: 0.9rem;
273
- }
274
-
275
- .input-field input, .input-field select {
276
- background: rgba(255, 255, 255, 0.1);
277
- border: 1px solid rgba(255, 255, 255, 0.2);
278
- border-radius: 10px;
279
- padding: 0.75rem 1rem;
280
- color: #ffffff;
281
- font-size: 1rem;
282
- transition: all 0.3s ease;
283
- }
284
-
285
- .input-field input:focus, .input-field select:focus {
286
- outline: none;
287
- border-color: #3a7bd5;
288
- background: rgba(255, 255, 255, 0.15);
289
- }
290
-
291
- .input-field select option {
292
- background: #24243e;
293
- }
294
-
295
  .calculate-btn {
296
  background: linear-gradient(45deg, #00d2ff, #3a7bd5);
297
  border: none;
@@ -311,34 +128,6 @@
311
  box-shadow: 0 10px 20px rgba(58, 123, 213, 0.3);
312
  }
313
 
314
- .results-section {
315
- margin-top: 3rem;
316
- display: none;
317
- }
318
-
319
- .results-grid {
320
- display: grid;
321
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
322
- gap: 2rem;
323
- margin-bottom: 2rem;
324
- }
325
-
326
- .result-card {
327
- background: rgba(0, 0, 0, 0.3);
328
- border-radius: 15px;
329
- padding: 1.5rem;
330
- text-align: center;
331
- }
332
-
333
- .result-value {
334
- font-size: 2.5rem;
335
- font-weight: 700;
336
- margin: 1rem 0;
337
- background: linear-gradient(45deg, #00d2ff, #3a7bd5);
338
- -webkit-background-clip: text;
339
- -webkit-text-fill-color: transparent;
340
- }
341
-
342
  .chart-container {
343
  position: relative;
344
  height: 400px;
@@ -396,6 +185,12 @@
396
  position: relative;
397
  overflow: hidden;
398
  user-select: none;
 
 
 
 
 
 
399
  }
400
 
401
  .option-card.selected {
@@ -419,12 +214,14 @@
419
  align-items: center;
420
  justify-content: center;
421
  font-weight: bold;
 
422
  }
423
 
424
  .option-card:hover:not(.selected) {
425
  border-color: #00d2ff;
426
  background: rgba(255, 255, 255, 0.1);
427
  transform: translateY(-2px);
 
428
  }
429
 
430
  .option-name {
@@ -458,27 +255,6 @@
458
  color: #00d2ff;
459
  }
460
 
461
- .period-tabs {
462
- display: flex;
463
- justify-content: center;
464
- gap: 1rem;
465
- margin-bottom: 2rem;
466
- }
467
-
468
- .period-tab {
469
- padding: 0.5rem 1.5rem;
470
- background: rgba(255, 255, 255, 0.1);
471
- border: 1px solid rgba(255, 255, 255, 0.2);
472
- border-radius: 20px;
473
- cursor: pointer;
474
- transition: all 0.3s ease;
475
- }
476
-
477
- .period-tab.active {
478
- background: linear-gradient(45deg, #00d2ff, #3a7bd5);
479
- border-color: transparent;
480
- }
481
-
482
  .custom-summary {
483
  display: grid;
484
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
@@ -618,14 +394,6 @@
618
  font-size: 2rem;
619
  }
620
 
621
- .methods-grid {
622
- grid-template-columns: 1fr;
623
- }
624
-
625
- .pros-cons {
626
- grid-template-columns: 1fr;
627
- }
628
-
629
  .tabs {
630
  flex-direction: column;
631
  }
@@ -1392,360 +1160,356 @@
1392
  <span id="platformTotal">$0K</span>
1393
  </div>
1394
  </div>
 
 
1395
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1396
 
 
 
 
 
 
 
 
 
 
 
 
 
1397
 
1398
- </div>
1399
- </div>
1400
-
1401
- <!-- 기간별 비용 차트 -->
1402
- <div class="chart-container">
1403
- <canvas id="customChart"></canvas>
1404
- </div>
1405
- </div>
1406
- </div>
1407
- </div>
1408
- </div>
1409
-
1410
- <script>
1411
- // 전역 변수
1412
- let selectedComponents = {
1413
- service: [],
1414
- db: null,
1415
- auth: null,
1416
- credit: null,
1417
- payment: null,
1418
- marketing: null,
1419
- support: null,
1420
- monitoring: null,
1421
- additional: []
1422
- };
1423
-
1424
- let customChart = null;
1425
- let currentPeriod = 3;
1426
-
1427
- // 페이지 로드 이벤트 리스너 등록
1428
- // 페이지 로드 시 이벤트 리스너 등록
1429
- document.addEventListener('DOMContentLoaded', function() {
1430
- console.log('DOM loaded, registering click events...'); // 디버깅용
1431
-
1432
- // 옵션 카드 클릭 이벤트 - 이벤트 위임 방식으로 변경
1433
- document.body.addEventListener('click', function(e) {
1434
- // option-card 또는 그 하위 요소를 클릭했는지 확인
1435
- const card = e.target.closest('.option-card');
1436
- if (!card) return;
1437
-
1438
- e.preventDefault();
1439
- e.stopPropagation();
1440
-
1441
- const category = card.dataset.category;
1442
- const id = card.dataset.id;
1443
-
1444
- console.log('Card clicked:', category, id); // 디버깅용
1445
-
1446
- if (category === 'service' || category === 'additional') {
1447
- // 복수 선택 가능
1448
- card.classList.toggle('selected');
1449
-
1450
- if (card.classList.contains('selected')) {
1451
- if (!selectedComponents[category].includes(id)) {
1452
- selectedComponents[category].push(id);
1453
- }
1454
- } else {
1455
- const index = selectedComponents[category].indexOf(id);
1456
- if (index > -1) {
1457
- selectedComponents[category].splice(index, 1);
1458
- }
1459
- }
1460
- } else {
1461
- // 단일 선택
1462
- document.querySelectorAll(`[data-category="${category}"]`).forEach(c => {
1463
- c.classList.remove('selected');
1464
- });
1465
-
1466
- card.classList.add('selected');
1467
- selectedComponents[category] = id;
1468
- }
1469
-
1470
- console.log('Selected components:', selectedComponents); // 디버깅용
1471
- });
1472
- });
1473
-
1474
-
1475
-
1476
- // 탭 전환 함수
1477
- function switchTab(tabId) {
1478
- document.querySelectorAll('.tab').forEach(tab => {
1479
- tab.classList.remove('active');
1480
- });
1481
- document.querySelectorAll('.tab-content').forEach(content => {
1482
- content.classList.remove('active');
1483
- });
1484
-
1485
- event.target.classList.add('active');
1486
- document.getElementById(tabId).classList.add('active');
1487
- }
1488
-
1489
- // 커스텀 비용 계산 함수
1490
- // 커스텀 비용 계산 함수
1491
- function calculateCustomCost() {
1492
- let totalInitialCost = 0;
1493
- let totalResourceCost = 0;
1494
- let maxTime = 0;
1495
- let componentDetails = [];
1496
-
1497
- // 서비스 비용 계산
1498
- selectedComponents.service.forEach(serviceId => {
1499
- const card = document.querySelector(`[data-category="service"][data-id="${serviceId}"]`);
1500
- if (!card) return;
1501
-
1502
- const cost = parseFloat(card.dataset.cost);
1503
- const time = parseFloat(card.dataset.time);
1504
- const resource = parseFloat(card.dataset.resource) || 0;
1505
- const name = card.querySelector('.option-name').textContent;
1506
-
1507
- totalInitialCost += cost;
1508
- totalResourceCost += resource;
1509
- maxTime = Math.max(maxTime, time);
1510
-
1511
- componentDetails.push({
1512
- name: name,
1513
- cost: cost,
1514
- type: 'service'
1515
- });
1516
- });
1517
-
1518
- // 기타 구성 요소 비용 계산
1519
- ['db', 'auth', 'credit', 'payment', 'marketing', 'monitoring'].forEach(category => {
1520
- if (selectedComponents[category]) {
1521
- const card = document.querySelector(`[data-category="${category}"][data-id="${selectedComponents[category]}"]`);
1522
- if (!card) return;
1523
 
1524
- const cost = parseFloat(card.dataset.cost);
1525
- const time = parseFloat(card.dataset.time);
1526
- const name = card.querySelector('.option-name').textContent;
1527
 
1528
- totalInitialCost += cost;
1529
- maxTime = Math.max(maxTime, time);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1530
 
1531
- componentDetails.push({
1532
- name: name,
1533
- cost: cost,
1534
- type: category
 
 
 
 
 
 
 
 
 
 
1535
  });
1536
  }
1537
- });
1538
-
1539
- // 지원 서비스 (월간 비용)
1540
- let monthlySupport = 0;
1541
- if (selectedComponents.support) {
1542
- const card = document.querySelector(`[data-category="support"][data-id="${selectedComponents.support]}"]`);
1543
- if (card) {
1544
- monthlySupport = parseFloat(card.dataset.cost);
1545
- const name = card.querySelector('.option-name').textContent;
1546
 
1547
- componentDetails.push({
1548
- name: name,
1549
- cost: monthlySupport,
1550
- type: 'monthly'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1551
  });
1552
  }
1553
- }
1554
-
1555
- // 추가 기능 비용 계산
1556
- selectedComponents.additional.forEach(additionalId => {
1557
- const card = document.querySelector(`[data-category="additional"][data-id="${additionalId}"]`);
1558
- if (!card) return;
1559
-
1560
- const cost = parseFloat(card.dataset.cost);
1561
- const time = parseFloat(card.dataset.time);
1562
- const name = card.querySelector('.option-name').textContent;
1563
-
1564
- totalInitialCost += cost;
1565
- maxTime = Math.max(maxTime, time);
1566
-
1567
- componentDetails.push({
1568
- name: name,
1569
- cost: cost,
1570
- type: 'additional'
1571
- });
1572
- });
1573
-
1574
- // 월간 운영 비용 계산
1575
- const serverCost = totalInitialCost > 100 ? 5 : totalInitialCost > 50 ? 3 : totalInitialCost > 20 ? 2 : 1;
1576
- const resourceCost = totalResourceCost;
1577
- const maintenanceCost = totalInitialCost * 0.05; // 초기 비용의 5%
1578
- const totalMonthlyCost = serverCost + resourceCost + maintenanceCost + monthlySupport;
1579
-
1580
- // 결과 표시
1581
- document.getElementById('initialCost').textContent = `$${totalInitialCost}K`;
1582
- document.getElementById('devTime').textContent = `${maxTime}개월`;
1583
- document.getElementById('serverCost').textContent = `$${serverCost}K`;
1584
- document.getElementById('resourceCost').textContent = `$${resourceCost.toFixed(1)}K`;
1585
- document.getElementById('maintenanceCost').textContent = `$${maintenanceCost.toFixed(1)}K`;
1586
- document.getElementById('totalMonthlyCost').textContent = `$${totalMonthlyCost.toFixed(1)}K`;
1587
-
1588
- // 총 비용 계산 (기간별)
1589
- updateTotalCost(totalInitialCost, totalMonthlyCost);
1590
-
1591
- // 선택한 구성 요소 목록 표시
1592
- displayComponentList(componentDetails);
1593
-
1594
- // 구축 방식별 비교
1595
- compareBuilderMethods(totalInitialCost, totalMonthlyCost, maxTime);
1596
-
1597
- // 차트 업데이트
1598
- updateCustomChart(totalInitialCost, totalMonthlyCost);
1599
-
1600
- // 결과 섹션 표시
1601
- document.getElementById('customResult').style.display = 'block';
1602
- }
1603
-
1604
- // 총 비용 업데이트
1605
- function updateTotalCost(initialCost, monthlyCost) {
1606
- const totalCost = initialCost + (monthlyCost * currentPeriod);
1607
- document.getElementById('totalCost').textContent = `$${totalCost.toFixed(1)}K`;
1608
- }
1609
-
1610
- // 구성 요소 목록 표시
1611
- function displayComponentList(components) {
1612
- const listContainer = document.getElementById('componentList');
1613
- listContainer.innerHTML = '';
1614
-
1615
- components.forEach(component => {
1616
- const item = document.createElement('div');
1617
- item.className = 'component-list-item';
1618
-
1619
- let costDisplay = component.type === 'monthly'
1620
- ? `월 $${component.cost}K`
1621
- : `$${component.cost}K`;
1622
-
1623
- item.innerHTML = `
1624
- <span>${component.name}</span>
1625
- <span style="color: #00d2ff;">${costDisplay}</span>
1626
- `;
1627
-
1628
- listContainer.appendChild(item);
1629
- });
1630
- }
1631
-
1632
- // 구축 방식별 비교
1633
- function compareBuilderMethods(initialCost, monthlyCost, devTime) {
1634
- // 외주 용역 개발
1635
- const outsourceInitial = initialCost;
1636
- const outsourceMonthly = monthlyCost;
1637
- const outsourceTotal = outsourceInitial + (outsourceMonthly * currentPeriod);
1638
-
1639
- // 자체 인력 개발
1640
- const developerSalary = 10; // 월 $10K
1641
- const teamSize = Math.ceil(initialCost / 50); // $50K당 1명
1642
- const inhouseDevTime = Math.max(devTime * 1.5, 3); // 최소 3개월
1643
- const inhouseInitial = developerSalary * teamSize * inhouseDevTime;
1644
- const inhouseMonthly = monthlyCost + (developerSalary * Math.ceil(teamSize / 2)); // 유지보수 인력
1645
- const inhouseTotal = inhouseInitial + (inhouseMonthly * currentPeriod);
1646
-
1647
- // 플랫폼 대여
1648
- const platformInitial = 1; // 설정 비용
1649
- const platformMonthly = monthlyCost * 2 + 5; // 플랫폼 수수료 포함
1650
- const platformTotal = platformInitial + (platformMonthly * currentPeriod);
1651
-
1652
- // 결과 표시
1653
- document.getElementById('outsourceInitial').textContent = `$${outsourceInitial}K`;
1654
- document.getElementById('outsourceMonthly').textContent = `$${outsourceMonthly.toFixed(1)}K`;
1655
- document.getElementById('outsourceTotal').textContent = `$${outsourceTotal.toFixed(1)}K`;
1656
-
1657
- document.getElementById('inhouseInitial').textContent = `$${inhouseInitial}K`;
1658
- document.getElementById('inhouseMonthly').textContent = `$${inhouseMonthly.toFixed(1)}K`;
1659
- document.getElementById('inhouseTotal').textContent = `$${inhouseTotal.toFixed(1)}K`;
1660
-
1661
- document.getElementById('platformInitial').textContent = `$${platformInitial}K`;
1662
- document.getElementById('platformMonthly').textContent = `$${platformMonthly.toFixed(1)}K`;
1663
- document.getElementById('platformTotal').textContent = `$${platformTotal.toFixed(1)}K`;
1664
- }
1665
-
1666
- // 차트 업데이트
1667
- function updateCustomChart(initialCost, monthlyCost) {
1668
- const ctx = document.getElementById('customChart').getContext('2d');
1669
-
1670
- if (customChart) {
1671
- customChart.destroy();
1672
- }
1673
-
1674
- const months = Array.from({length: 12}, (_, i) => `${i + 1}개월`);
1675
- const outsourceData = months.map((_, i) => initialCost + (monthlyCost * (i + 1)));
1676
- const inhouseData = months.map((_, i) => {
1677
- const teamSize = Math.ceil(initialCost / 50);
1678
- const inhouseInitial = 10 * teamSize * 3;
1679
- const inhouseMonthly = monthlyCost + (10 * Math.ceil(teamSize / 2));
1680
- return inhouseInitial + (inhouseMonthly * (i + 1));
1681
- });
1682
- const platformData = months.map((_, i) => 1 + ((monthlyCost * 2 + 5) * (i + 1)));
1683
-
1684
- customChart = new Chart(ctx, {
1685
- type: 'line',
1686
- data: {
1687
- labels: months,
1688
- datasets: [{
1689
- label: '외주 용역 개발',
1690
- data: outsourceData,
1691
- borderColor: '#764ba2',
1692
- backgroundColor: 'rgba(118, 75, 162, 0.1)',
1693
- tension: 0.4
1694
- }, {
1695
- label: '자체 인력 개발',
1696
- data: inhouseData,
1697
- borderColor: '#f5576c',
1698
- backgroundColor: 'rgba(245, 87, 108, 0.1)',
1699
- tension: 0.4
1700
- }, {
1701
- label: '플랫폼 대여',
1702
- data: platformData,
1703
- borderColor: '#4facfe',
1704
- backgroundColor: 'rgba(79, 172, 254, 0.1)',
1705
- tension: 0.4
1706
- }]
1707
- },
1708
- options: {
1709
- responsive: true,
1710
- maintainAspectRatio: false,
1711
- plugins: {
1712
- legend: {
1713
- labels: {
1714
- color: '#ffffff'
1715
- }
1716
- },
1717
- tooltip: {
1718
- callbacks: {
1719
- label: function(context) {
1720
- return context.dataset.label + ': $' + context.parsed.y.toFixed(1) + 'K';
1721
- }
1722
- }
1723
- }
1724
- },
1725
- scales: {
1726
- x: {
1727
- grid: {
1728
- color: 'rgba(255, 255, 255, 0.1)'
1729
- },
1730
- ticks: {
1731
- color: '#a8a8b3'
1732
- }
1733
- },
1734
- y: {
1735
- grid: {
1736
- color: 'rgba(255, 255, 255, 0.1)'
1737
- },
1738
- ticks: {
1739
- color: '#a8a8b3',
1740
- callback: function(value) {
1741
- return '$' + value + 'K';
1742
- }
1743
- }
1744
- }
1745
- }
1746
- }
1747
- });
1748
- }
1749
- </script>
1750
- </body>
1751
- </html>
 
10
  margin: 0;
11
  padding: 0;
12
  box-sizing: border-box;
13
+ </script>
14
+ </body>
15
+ </html>
16
 
17
  body {
18
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
 
103
  to { opacity: 1; }
104
  }
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  .calculator-title {
107
  font-size: 2rem;
108
  margin-bottom: 2rem;
109
  text-align: center;
110
  }
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  .calculate-btn {
113
  background: linear-gradient(45deg, #00d2ff, #3a7bd5);
114
  border: none;
 
128
  box-shadow: 0 10px 20px rgba(58, 123, 213, 0.3);
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  .chart-container {
132
  position: relative;
133
  height: 400px;
 
185
  position: relative;
186
  overflow: hidden;
187
  user-select: none;
188
+ pointer-events: auto;
189
+ }
190
+
191
+ /* 중요: 하위 요소들이 클릭 이벤트를 방해하지 않도록 설정 */
192
+ .option-card * {
193
+ pointer-events: none;
194
  }
195
 
196
  .option-card.selected {
 
214
  align-items: center;
215
  justify-content: center;
216
  font-weight: bold;
217
+ pointer-events: none;
218
  }
219
 
220
  .option-card:hover:not(.selected) {
221
  border-color: #00d2ff;
222
  background: rgba(255, 255, 255, 0.1);
223
  transform: translateY(-2px);
224
+ z-index: 10;
225
  }
226
 
227
  .option-name {
 
255
  color: #00d2ff;
256
  }
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  .custom-summary {
259
  display: grid;
260
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 
394
  font-size: 2rem;
395
  }
396
 
 
 
 
 
 
 
 
 
397
  .tabs {
398
  flex-direction: column;
399
  }
 
1160
  <span id="platformTotal">$0K</span>
1161
  </div>
1162
  </div>
1163
+ </div>
1164
+ </div>
1165
 
1166
+ <!-- 기간별 비용 차트 -->
1167
+ <div class="chart-container">
1168
+ <canvas id="customChart"></canvas>
1169
+ </div>
1170
+ </div>
1171
+ </div>
1172
+ </div>
1173
+ </div>
1174
+
1175
+ <script>
1176
+ // 전역 변수
1177
+ let selectedComponents = {
1178
+ service: [],
1179
+ db: null,
1180
+ auth: null,
1181
+ credit: null,
1182
+ payment: null,
1183
+ marketing: null,
1184
+ support: null,
1185
+ monitoring: null,
1186
+ additional: []
1187
+ };
1188
+
1189
+ let customChart = null;
1190
+ let currentPeriod = 3;
1191
+
1192
+ // 페이지 로드 시 이벤트 리스너 등록
1193
+ document.addEventListener('DOMContentLoaded', function() {
1194
+ console.log('DOM loaded, registering click events...'); // 디버깅용
1195
+
1196
+ // 모든 옵션 카드에 직접 이벤트 리스너 추가
1197
+ const optionCards = document.querySelectorAll('.option-card');
1198
+ console.log('Found option cards:', optionCards.length); // 디버깅용
1199
+
1200
+ optionCards.forEach(card => {
1201
+ card.addEventListener('click', function(e) {
1202
+ e.preventDefault();
1203
+ e.stopPropagation();
1204
+
1205
+ const category = this.dataset.category;
1206
+ const id = this.dataset.id;
1207
+
1208
+ console.log('Card clicked:', category, id); // 디버깅용
1209
+
1210
+ if (!category || !id) {
1211
+ console.error('Missing category or id:', category, id);
1212
+ return;
1213
+ }
1214
+
1215
+ if (category === 'service' || category === 'additional') {
1216
+ // 복수 선택 가능
1217
+ this.classList.toggle('selected');
1218
+
1219
+ if (this.classList.contains('selected')) {
1220
+ if (!selectedComponents[category].includes(id)) {
1221
+ selectedComponents[category].push(id);
1222
+ }
1223
+ } else {
1224
+ const index = selectedComponents[category].indexOf(id);
1225
+ if (index > -1) {
1226
+ selectedComponents[category].splice(index, 1);
1227
+ }
1228
+ }
1229
+ } else {
1230
+ // 단일 선택
1231
+ document.querySelectorAll(`[data-category="${category}"]`).forEach(c => {
1232
+ c.classList.remove('selected');
1233
+ });
1234
+
1235
+ this.classList.add('selected');
1236
+ selectedComponents[category] = id;
1237
+ }
1238
+
1239
+ console.log('Selected components:', selectedComponents); // 디버깅용
1240
+ });
1241
+ });
1242
+ });
1243
 
1244
+ // 탭 전환 함수
1245
+ function switchTab(tabId) {
1246
+ document.querySelectorAll('.tab').forEach(tab => {
1247
+ tab.classList.remove('active');
1248
+ });
1249
+ document.querySelectorAll('.tab-content').forEach(content => {
1250
+ content.classList.remove('active');
1251
+ });
1252
+
1253
+ event.target.classList.add('active');
1254
+ document.getElementById(tabId).classList.add('active');
1255
+ }
1256
 
1257
+ // 커스텀 비용 계산 함수
1258
+ function calculateCustomCost() {
1259
+ let totalInitialCost = 0;
1260
+ let totalResourceCost = 0;
1261
+ let maxTime = 0;
1262
+ let componentDetails = [];
1263
+
1264
+ // 서비스 비용 계산
1265
+ selectedComponents.service.forEach(serviceId => {
1266
+ const card = document.querySelector(`[data-category="service"][data-id="${serviceId}"]`);
1267
+ if (!card) return;
1268
+
1269
+ const cost = parseFloat(card.dataset.cost);
1270
+ const time = parseFloat(card.dataset.time);
1271
+ const resource = parseFloat(card.dataset.resource) || 0;
1272
+ const name = card.querySelector('.option-name').textContent;
1273
+
1274
+ totalInitialCost += cost;
1275
+ totalResourceCost += resource;
1276
+ maxTime = Math.max(maxTime, time);
1277
+
1278
+ componentDetails.push({
1279
+ name: name,
1280
+ cost: cost,
1281
+ type: 'service'
1282
+ });
1283
+ });
1284
+
1285
+ // 기타 구성 요소 비용 계산
1286
+ ['db', 'auth', 'credit', 'payment', 'marketing', 'monitoring'].forEach(category => {
1287
+ if (selectedComponents[category]) {
1288
+ const card = document.querySelector(`[data-category="${category}"][data-id="${selectedComponents[category]}"]`);
1289
+ if (!card) return;
1290
+
1291
+ const cost = parseFloat(card.dataset.cost);
1292
+ const time = parseFloat(card.dataset.time);
1293
+ const name = card.querySelector('.option-name').textContent;
1294
+
1295
+ totalInitialCost += cost;
1296
+ maxTime = Math.max(maxTime, time);
1297
+
1298
+ componentDetails.push({
1299
+ name: name,
1300
+ cost: cost,
1301
+ type: category
1302
+ });
1303
+ }
1304
+ });
1305
+
1306
+ // 지원 서비스 (월간 비용)
1307
+ let monthlySupport = 0;
1308
+ if (selectedComponents.support) {
1309
+ const card = document.querySelector(`[data-category="support"][data-id="${selectedComponents.support]}"]`);
1310
+ if (card) {
1311
+ monthlySupport = parseFloat(card.dataset.cost);
1312
+ const name = card.querySelector('.option-name').textContent;
1313
+
1314
+ componentDetails.push({
1315
+ name: name,
1316
+ cost: monthlySupport,
1317
+ type: 'monthly'
1318
+ });
1319
+ }
1320
+ }
1321
+
1322
+ // 추가 기능 비용 계산
1323
+ selectedComponents.additional.forEach(additionalId => {
1324
+ const card = document.querySelector(`[data-category="additional"][data-id="${additionalId}"]`);
1325
+ if (!card) return;
1326
+
1327
+ const cost = parseFloat(card.dataset.cost);
1328
+ const time = parseFloat(card.dataset.time);
1329
+ const name = card.querySelector('.option-name').textContent;
1330
+
1331
+ totalInitialCost += cost;
1332
+ maxTime = Math.max(maxTime, time);
1333
+
1334
+ componentDetails.push({
1335
+ name: name,
1336
+ cost: cost,
1337
+ type: 'additional'
1338
+ });
1339
+ });
1340
+
1341
+ // 월간 운영 비용 계산
1342
+ const serverCost = totalInitialCost > 100 ? 5 : totalInitialCost > 50 ? 3 : totalInitialCost > 20 ? 2 : 1;
1343
+ const resourceCost = totalResourceCost;
1344
+ const maintenanceCost = totalInitialCost * 0.05; // 초기 비용의 5%
1345
+ const totalMonthlyCost = serverCost + resourceCost + maintenanceCost + monthlySupport;
1346
+
1347
+ // 결과 표시
1348
+ document.getElementById('initialCost').textContent = `${totalInitialCost}K`;
1349
+ document.getElementById('devTime').textContent = `${maxTime}개월`;
1350
+ document.getElementById('serverCost').textContent = `${serverCost}K`;
1351
+ document.getElementById('resourceCost').textContent = `${resourceCost.toFixed(1)}K`;
1352
+ document.getElementById('maintenanceCost').textContent = `${maintenanceCost.toFixed(1)}K`;
1353
+ document.getElementById('totalMonthlyCost').textContent = `${totalMonthlyCost.toFixed(1)}K`;
1354
+
1355
+ // 총 비용 계산 (기간별)
1356
+ updateTotalCost(totalInitialCost, totalMonthlyCost);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1357
 
1358
+ // 선택한 구성 요소 목록 표시
1359
+ displayComponentList(componentDetails);
 
1360
 
1361
+ // 구축 방식별 비교
1362
+ compareBuilderMethods(totalInitialCost, totalMonthlyCost, maxTime);
1363
+
1364
+ // 차트 업데이트
1365
+ updateCustomChart(totalInitialCost, totalMonthlyCost);
1366
+
1367
+ // 결과 섹션 표시
1368
+ document.getElementById('customResult').style.display = 'block';
1369
+ }
1370
+
1371
+ // 총 비용 업데이트
1372
+ function updateTotalCost(initialCost, monthlyCost) {
1373
+ const totalCost = initialCost + (monthlyCost * currentPeriod);
1374
+ document.getElementById('totalCost').textContent = `${totalCost.toFixed(1)}K`;
1375
+ }
1376
+
1377
+ // 구성 요소 목록 표시
1378
+ function displayComponentList(components) {
1379
+ const listContainer = document.getElementById('componentList');
1380
+ listContainer.innerHTML = '';
1381
 
1382
+ components.forEach(component => {
1383
+ const item = document.createElement('div');
1384
+ item.className = 'component-list-item';
1385
+
1386
+ let costDisplay = component.type === 'monthly'
1387
+ ? `월 ${component.cost}K`
1388
+ : `${component.cost}K`;
1389
+
1390
+ item.innerHTML = `
1391
+ <span>${component.name}</span>
1392
+ <span style="color: #00d2ff;">${costDisplay}</span>
1393
+ `;
1394
+
1395
+ listContainer.appendChild(item);
1396
  });
1397
  }
1398
+
1399
+ // 구축 방식별 비교
1400
+ function compareBuilderMethods(initialCost, monthlyCost, devTime) {
1401
+ // 외주 용역 개발
1402
+ const outsourceInitial = initialCost;
1403
+ const outsourceMonthly = monthlyCost;
1404
+ const outsourceTotal = outsourceInitial + (outsourceMonthly * currentPeriod);
 
 
1405
 
1406
+ // 자체 인력 개발
1407
+ const developerSalary = 10; // 월 $10K
1408
+ const teamSize = Math.ceil(initialCost / 50); // $50K당 1명
1409
+ const inhouseDevTime = Math.max(devTime * 1.5, 3); // 최소 3개월
1410
+ const inhouseInitial = developerSalary * teamSize * inhouseDevTime;
1411
+ const inhouseMonthly = monthlyCost + (developerSalary * Math.ceil(teamSize / 2)); // 유지보수 인력
1412
+ const inhouseTotal = inhouseInitial + (inhouseMonthly * currentPeriod);
1413
+
1414
+ // 플랫폼 대여
1415
+ const platformInitial = 1; // 설정 비용
1416
+ const platformMonthly = monthlyCost * 2 + 5; // 플랫폼 수수료 포함
1417
+ const platformTotal = platformInitial + (platformMonthly * currentPeriod);
1418
+
1419
+ // 결과 표시
1420
+ document.getElementById('outsourceInitial').textContent = `${outsourceInitial}K`;
1421
+ document.getElementById('outsourceMonthly').textContent = `${outsourceMonthly.toFixed(1)}K`;
1422
+ document.getElementById('outsourceTotal').textContent = `${outsourceTotal.toFixed(1)}K`;
1423
+
1424
+ document.getElementById('inhouseInitial').textContent = `${inhouseInitial}K`;
1425
+ document.getElementById('inhouseMonthly').textContent = `${inhouseMonthly.toFixed(1)}K`;
1426
+ document.getElementById('inhouseTotal').textContent = `${inhouseTotal.toFixed(1)}K`;
1427
+
1428
+ document.getElementById('platformInitial').textContent = `${platformInitial}K`;
1429
+ document.getElementById('platformMonthly').textContent = `${platformMonthly.toFixed(1)}K`;
1430
+ document.getElementById('platformTotal').textContent = `${platformTotal.toFixed(1)}K`;
1431
+ }
1432
+
1433
+ // 차트 업데이트
1434
+ function updateCustomChart(initialCost, monthlyCost) {
1435
+ const ctx = document.getElementById('customChart').getContext('2d');
1436
+
1437
+ if (customChart) {
1438
+ customChart.destroy();
1439
+ }
1440
+
1441
+ const months = Array.from({length: 12}, (_, i) => `${i + 1}개월`);
1442
+ const outsourceData = months.map((_, i) => initialCost + (monthlyCost * (i + 1)));
1443
+ const inhouseData = months.map((_, i) => {
1444
+ const teamSize = Math.ceil(initialCost / 50);
1445
+ const inhouseInitial = 10 * teamSize * 3;
1446
+ const inhouseMonthly = monthlyCost + (10 * Math.ceil(teamSize / 2));
1447
+ return inhouseInitial + (inhouseMonthly * (i + 1));
1448
+ });
1449
+ const platformData = months.map((_, i) => 1 + ((monthlyCost * 2 + 5) * (i + 1)));
1450
+
1451
+ customChart = new Chart(ctx, {
1452
+ type: 'line',
1453
+ data: {
1454
+ labels: months,
1455
+ datasets: [{
1456
+ label: '외주 용역 개발',
1457
+ data: outsourceData,
1458
+ borderColor: '#764ba2',
1459
+ backgroundColor: 'rgba(118, 75, 162, 0.1)',
1460
+ tension: 0.4
1461
+ }, {
1462
+ label: '자체 인력 개발',
1463
+ data: inhouseData,
1464
+ borderColor: '#f5576c',
1465
+ backgroundColor: 'rgba(245, 87, 108, 0.1)',
1466
+ tension: 0.4
1467
+ }, {
1468
+ label: '플랫폼 대여',
1469
+ data: platformData,
1470
+ borderColor: '#4facfe',
1471
+ backgroundColor: 'rgba(79, 172, 254, 0.1)',
1472
+ tension: 0.4
1473
+ }]
1474
+ },
1475
+ options: {
1476
+ responsive: true,
1477
+ maintainAspectRatio: false,
1478
+ plugins: {
1479
+ legend: {
1480
+ labels: {
1481
+ color: '#ffffff'
1482
+ }
1483
+ },
1484
+ tooltip: {
1485
+ callbacks: {
1486
+ label: function(context) {
1487
+ return context.dataset.label + ': + context.parsed.y.toFixed(1) + 'K';
1488
+ }
1489
+ }
1490
+ }
1491
+ },
1492
+ scales: {
1493
+ x: {
1494
+ grid: {
1495
+ color: 'rgba(255, 255, 255, 0.1)'
1496
+ },
1497
+ ticks: {
1498
+ color: '#a8a8b3'
1499
+ }
1500
+ },
1501
+ y: {
1502
+ grid: {
1503
+ color: 'rgba(255, 255, 255, 0.1)'
1504
+ },
1505
+ ticks: {
1506
+ color: '#a8a8b3',
1507
+ callback: function(value) {
1508
+ return ' + value + 'K';
1509
+ }
1510
+ }
1511
+ }
1512
+ }
1513
+ }
1514
  });
1515
  }