Fix critical and high priority issues from code review

Critical fixes:
- Add defer to app.js for non-blocking loading
- Add skip link and main landmark for accessibility
- Fix ARIA tab state dynamic updates

High priority fixes:
- Add rel="noopener noreferrer" to external links
- Implement non-blocking Google Fonts loading
- Improve color contrast (text-slate-500 → text-slate-400)
- Consolidate price data to single source (PRICING_DATA)
- Add TELEGRAM_BOT_URL constant

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-20 23:27:21 +09:00
parent cd6ce130aa
commit 4c3bb09913
2 changed files with 52 additions and 27 deletions

42
app.js
View File

@@ -3,6 +3,9 @@
* 대화형 서버 런처 및 가격 데이터 관리
*/
// 텔레그램 봇 URL 상수
const TELEGRAM_BOT_URL = 'https://t.me/AnvilForgeBot';
// 단일 가격 데이터 소스 (VAT 포함, 월간 기준)
const PRICING_DATA = {
global: [
@@ -20,21 +23,18 @@ const PRICING_DATA = {
]
};
// 런처 모달용 가격 (plan명 기준)
const LAUNCHER_PRICES = {
'Micro': { base: 8500, seoul: 8500 },
'Starter': { base: 20400, seoul: 17000 },
'Pro': { base: 40700, seoul: 33900 },
'Business': { base: 67800, seoul: 67800 }
};
// 런처 모달용 가격 - PRICING_DATA에서 파생
const LAUNCHER_PRICES = Object.fromEntries(
PRICING_DATA.global.map(p => [p.plan, {
base: p.price,
seoul: PRICING_DATA.seoul.find(s => s.plan === p.plan)?.price || p.price
}])
);
// 플랜별 스펙 정보
const PLAN_SPECS = {
'Micro': '1vCPU / 1GB RAM',
'Starter': '1vCPU / 2GB RAM',
'Pro': '2vCPU / 4GB RAM',
'Business': '4vCPU / 8GB RAM'
};
// 플랜별 스펙 정보 - PRICING_DATA에서 파생
const PLAN_SPECS = Object.fromEntries(
PRICING_DATA.global.map(p => [p.plan, `${p.vcpu} / ${p.ram} RAM`])
);
// 리전 정보
const REGIONS = [
@@ -276,11 +276,25 @@ function switchTab(tab) {
const panelTf = document.getElementById('panel-tf');
if (tab === 'n8n') {
// Update ARIA states for n8n tab
btnN8n.setAttribute('aria-selected', 'true');
btnN8n.setAttribute('tabindex', '0');
btnTf.setAttribute('aria-selected', 'false');
btnTf.setAttribute('tabindex', '-1');
// Update classes
btnN8n.className = 'px-4 py-2 rounded-lg bg-purple-600 text-white text-sm font-bold transition shadow-lg shadow-purple-500/20';
btnTf.className = 'px-4 py-2 rounded-lg bg-slate-800 text-slate-400 text-sm font-bold border border-slate-700 hover:text-white transition';
panelN8n.classList.remove('hidden');
panelTf.classList.add('hidden');
} else {
// Update ARIA states for Terraform tab
btnTf.setAttribute('aria-selected', 'true');
btnTf.setAttribute('tabindex', '0');
btnN8n.setAttribute('aria-selected', 'false');
btnN8n.setAttribute('tabindex', '-1');
// Update classes
btnN8n.className = 'px-4 py-2 rounded-lg bg-slate-800 text-slate-400 text-sm font-bold border border-slate-700 hover:text-white transition';
btnTf.className = 'px-4 py-2 rounded-lg bg-blue-600 text-white text-sm font-bold transition shadow-lg shadow-blue-500/20';
panelN8n.classList.add('hidden');

View File

@@ -36,16 +36,21 @@
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Pretendard:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Pretendard:wght@400;600;700&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Pretendard:wght@400;600;700&display=swap" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Pretendard:wght@400;600;700&display=swap"></noscript>
<!-- App JavaScript (must load before Alpine.js) -->
<script src="app.js"></script>
<script defer src="app.js"></script>
<!-- Alpine.js (pinned version with SRI) -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js" integrity="sha384-iZD2X8o1Zdq0HR5H/7oa8W30WS4No+zWCKUPD7fHRay9I1Gf+C4F8sVmw7zec1wW" crossorigin="anonymous"></script>
</head>
<body x-data="anvilApp()" @keydown.window="handleKeydown($event)" class="font-sans antialiased selection:bg-brand-500/30">
<!-- Skip Link for Keyboard Users -->
<a href="#main-content" class="skip-link">메인 콘텐츠로 건너뛰기</a>
<!-- Navbar -->
<nav class="fixed w-full z-50 top-0 border-b border-white/5 bg-dark-900/80 backdrop-blur-md">
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
@@ -67,6 +72,9 @@
</div>
</nav>
<!-- Main Content -->
<main id="main-content">
<!-- Hero Section -->
<section class="relative pt-32 pb-20 overflow-hidden grid-bg gradient-bg noise-bg">
<!-- Animated Blobs -->
@@ -159,7 +167,7 @@
📍 <span class="text-white/90">위치:</span> 🇯🇵 도쿄<br>
💿 <span class="text-white/90">OS:</span> Debian 12<br>
<span class="text-white/90">사양:</span> Pro (2 vCPU / 4GB)
<div class="text-[10px] text-slate-500 text-right mt-1">오후 3:42</div>
<div class="text-[10px] text-slate-400 text-right mt-1">오후 3:42</div>
</div>
</div>
@@ -179,7 +187,7 @@
🌐 <span class="text-white/90">IP:</span> <code class="bg-black/30 px-1 rounded text-[13px]">45.12.89.121</code><br>
🔑 <span class="text-white/90">Root PW:</span> <span class="text-slate-400">(보안 전송됨)</span><br><br>
<span class="text-slate-400 text-[13px]">지금 바로 SSH 접속이 가능합니다.</span>
<div class="text-[10px] text-slate-500 text-right mt-1">오후 3:43</div>
<div class="text-[10px] text-slate-400 text-right mt-1">오후 3:43</div>
</div>
</div>
</div>
@@ -281,7 +289,7 @@
</div>
<div>
<div class="font-bold text-white">Webhook Integration</div>
<div class="text-sm text-slate-500">Github Actions, GitLab CI/CD 연동</div>
<div class="text-sm text-slate-400">Github Actions, GitLab CI/CD 연동</div>
</div>
</li>
<li class="flex items-center gap-4">
@@ -290,7 +298,7 @@
</div>
<div>
<div class="font-bold text-white">Full API Access</div>
<div class="text-sm text-slate-500">서버 생성, 제어, 모니터링 100% API 지원</div>
<div class="text-sm text-slate-400">서버 생성, 제어, 모니터링 100% API 지원</div>
</div>
</li>
</ul>
@@ -496,7 +504,7 @@
<span class="text-white">.it.com / .dev</span>
<div class="text-right">
<span class="text-brand-400 font-bold">₩8,700 / ₩27,800</span>
<div class="text-[10px] text-slate-500 mt-0.5">(.it.com 갱신 ₩67,900)</div>
<div class="text-[10px] text-slate-400 mt-0.5">(.it.com 갱신 ₩67,900)</div>
</div>
</div>
<div class="flex justify-between items-center p-3 border-b border-white/5">
@@ -508,7 +516,7 @@
<span class="text-brand-400 font-bold">₩27,800 / ₩31,300</span>
</div>
</div>
<a href="https://t.me/AnvilForgeBot" target="_blank" class="mt-8 w-full py-3 bg-brand-600 hover:bg-brand-500 text-white font-bold rounded-lg transition text-center block">
<a href="https://t.me/AnvilForgeBot" target="_blank" rel="noopener noreferrer" class="mt-8 w-full py-3 bg-brand-600 hover:bg-brand-500 text-white font-bold rounded-lg transition text-center block">
🌐 도메인 검색 시작하기
</a>
</div>
@@ -657,9 +665,9 @@
<span class="text-sm text-slate-400">Live Inventory Pricing</span>
<div class="text-4xl md:text-5xl font-bold text-white mt-1">₩169,000<span class="text-lg text-slate-500 font-normal">~</span></div>
<p class="text-xs text-brand-400 mt-2">🇸🇬 싱가포르 최저가 재고 보유 중</p>
<p class="text-[10px] text-slate-500 mt-1">※ 서울/도쿄 리전 재고 및 가격은 봇으로 확인하세요.</p>
<p class="text-[10px] text-slate-400 mt-1">※ 서울/도쿄 리전 재고 및 가격은 봇으로 확인하세요.</p>
</div>
<a href="https://t.me/AnvilForgeBot" target="_blank" class="btn-glow px-8 py-4 bg-gradient-to-r from-brand-600 to-brand-500 text-white font-bold rounded-xl hover:shadow-lg hover:shadow-brand-500/30 transition-all flex items-center gap-3">
<a href="https://t.me/AnvilForgeBot" target="_blank" rel="noopener noreferrer" class="btn-glow px-8 py-4 bg-gradient-to-r from-brand-600 to-brand-500 text-white font-bold rounded-xl hover:shadow-lg hover:shadow-brand-500/30 transition-all flex items-center gap-3">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4.64 6.8c-.15 1.58-.8 5.42-1.13 7.19-.14.75-.42 1-.68 1.03-.58.05-1.02-.38-1.58-.75-.88-.58-1.38-.94-2.23-1.5-.99-.65-.35-1.01.22-1.59.15-.15 2.71-2.48 2.76-2.69a.2.2 0 00-.05-.18c-.06-.05-.14-.03-.21-.02-.09.02-1.49.95-4.22 2.79-.4.27-.76.41-1.08.4-.36-.01-1.04-.2-1.55-.37-.62-.2-1.12-.31-1.15-.63.03-.37.59-.75 1.5-.95 6.07-2.64 10.12-4.38 12.15-5.21 2.91-1.2 3.51-1.4 3.91-1.41.09 0 .28.02.41.09.11.06.23.14.3.24.08.12.12.33.09.57z"/></svg>
실시간 재고/가격 조회 (Bot)
</a>
@@ -669,6 +677,9 @@
</div>
</section>
</main>
<!-- End Main Content -->
<!-- Footer -->
<footer class="py-16 relative overflow-hidden">
<!-- Background -->
@@ -696,13 +707,13 @@
<!-- Company Info -->
<div class="text-center md:text-right text-sm">
<div class="text-slate-400">© 2026 LIBEHAIM Inc. | Taro Tanaka</div>
<div class="text-xs text-slate-600 mt-1">#202 K-Flat, 3-1-13 Higashioi, Shinagawa-ku, Tokyo 140-0011, Japan</div>
<div class="text-xs text-slate-500 mt-1">#202 K-Flat, 3-1-13 Higashioi, Shinagawa-ku, Tokyo 140-0011, Japan</div>
</div>
</div>
<!-- Bottom Decoration -->
<div class="mt-12 pt-8 border-t border-white/5 text-center">
<p class="text-xs text-slate-600">Powered by <span class="text-brand-400">Cloud Infrastructure</span> across 4 global regions</p>
<p class="text-xs text-slate-500">Powered by <span class="text-brand-400">Cloud Infrastructure</span> across 4 global regions</p>
</div>
</div>
</footer>
@@ -906,7 +917,7 @@
<p class="text-slate-400 text-xs mb-3">인스턴스가 활성화되었습니다</p>
<div class="flex gap-2">
<button @click="resetLauncher" class="flex-1 py-2.5 bg-slate-800 hover:bg-slate-700 text-white font-semibold rounded-xl transition text-sm">닫기</button>
<a href="https://t.me/AnvilForgeBot" target="_blank" class="flex-1 py-2.5 bg-gradient-to-r from-brand-600 to-brand-500 text-white font-semibold rounded-xl transition text-center text-sm">Console →</a>
<a href="https://t.me/AnvilForgeBot" target="_blank" rel="noopener noreferrer" class="flex-1 py-2.5 bg-gradient-to-r from-brand-600 to-brand-500 text-white font-semibold rounded-xl transition text-center text-sm">Console →</a>
</div>
</div>
</div>