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');