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:
42
app.js
42
app.js
@@ -3,6 +3,9 @@
|
|||||||
* 대화형 서버 런처 및 가격 데이터 관리
|
* 대화형 서버 런처 및 가격 데이터 관리
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// 텔레그램 봇 URL 상수
|
||||||
|
const TELEGRAM_BOT_URL = 'https://t.me/AnvilForgeBot';
|
||||||
|
|
||||||
// 단일 가격 데이터 소스 (VAT 포함, 월간 기준)
|
// 단일 가격 데이터 소스 (VAT 포함, 월간 기준)
|
||||||
const PRICING_DATA = {
|
const PRICING_DATA = {
|
||||||
global: [
|
global: [
|
||||||
@@ -20,21 +23,18 @@ const PRICING_DATA = {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
// 런처 모달용 가격 (plan명 기준)
|
// 런처 모달용 가격 - PRICING_DATA에서 파생
|
||||||
const LAUNCHER_PRICES = {
|
const LAUNCHER_PRICES = Object.fromEntries(
|
||||||
'Micro': { base: 8500, seoul: 8500 },
|
PRICING_DATA.global.map(p => [p.plan, {
|
||||||
'Starter': { base: 20400, seoul: 17000 },
|
base: p.price,
|
||||||
'Pro': { base: 40700, seoul: 33900 },
|
seoul: PRICING_DATA.seoul.find(s => s.plan === p.plan)?.price || p.price
|
||||||
'Business': { base: 67800, seoul: 67800 }
|
}])
|
||||||
};
|
);
|
||||||
|
|
||||||
// 플랜별 스펙 정보
|
// 플랜별 스펙 정보 - PRICING_DATA에서 파생
|
||||||
const PLAN_SPECS = {
|
const PLAN_SPECS = Object.fromEntries(
|
||||||
'Micro': '1vCPU / 1GB RAM',
|
PRICING_DATA.global.map(p => [p.plan, `${p.vcpu} / ${p.ram} RAM`])
|
||||||
'Starter': '1vCPU / 2GB RAM',
|
);
|
||||||
'Pro': '2vCPU / 4GB RAM',
|
|
||||||
'Business': '4vCPU / 8GB RAM'
|
|
||||||
};
|
|
||||||
|
|
||||||
// 리전 정보
|
// 리전 정보
|
||||||
const REGIONS = [
|
const REGIONS = [
|
||||||
@@ -276,11 +276,25 @@ function switchTab(tab) {
|
|||||||
const panelTf = document.getElementById('panel-tf');
|
const panelTf = document.getElementById('panel-tf');
|
||||||
|
|
||||||
if (tab === 'n8n') {
|
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';
|
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';
|
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');
|
panelN8n.classList.remove('hidden');
|
||||||
panelTf.classList.add('hidden');
|
panelTf.classList.add('hidden');
|
||||||
} else {
|
} 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';
|
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';
|
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');
|
panelN8n.classList.add('hidden');
|
||||||
|
|||||||
37
index.html
37
index.html
@@ -36,16 +36,21 @@
|
|||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<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) -->
|
<!-- App JavaScript (must load before Alpine.js) -->
|
||||||
<script src="app.js"></script>
|
<script defer src="app.js"></script>
|
||||||
|
|
||||||
<!-- Alpine.js (pinned version with SRI) -->
|
<!-- 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>
|
<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>
|
</head>
|
||||||
<body x-data="anvilApp()" @keydown.window="handleKeydown($event)" class="font-sans antialiased selection:bg-brand-500/30">
|
<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 -->
|
<!-- Navbar -->
|
||||||
<nav class="fixed w-full z-50 top-0 border-b border-white/5 bg-dark-900/80 backdrop-blur-md">
|
<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">
|
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||||
@@ -67,6 +72,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main id="main-content">
|
||||||
|
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<section class="relative pt-32 pb-20 overflow-hidden grid-bg gradient-bg noise-bg">
|
<section class="relative pt-32 pb-20 overflow-hidden grid-bg gradient-bg noise-bg">
|
||||||
<!-- Animated Blobs -->
|
<!-- Animated Blobs -->
|
||||||
@@ -159,7 +167,7 @@
|
|||||||
📍 <span class="text-white/90">위치:</span> 🇯🇵 도쿄<br>
|
📍 <span class="text-white/90">위치:</span> 🇯🇵 도쿄<br>
|
||||||
💿 <span class="text-white/90">OS:</span> Debian 12<br>
|
💿 <span class="text-white/90">OS:</span> Debian 12<br>
|
||||||
⚡ <span class="text-white/90">사양:</span> Pro (2 vCPU / 4GB)
|
⚡ <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>
|
||||||
</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">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-white/90">Root PW:</span> <span class="text-slate-400">(보안 전송됨)</span><br><br>
|
||||||
<span class="text-slate-400 text-[13px]">지금 바로 SSH 접속이 가능합니다.</span>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -281,7 +289,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-bold text-white">Webhook Integration</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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex items-center gap-4">
|
<li class="flex items-center gap-4">
|
||||||
@@ -290,7 +298,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="font-bold text-white">Full API Access</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>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -496,7 +504,7 @@
|
|||||||
<span class="text-white">.it.com / .dev</span>
|
<span class="text-white">.it.com / .dev</span>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<span class="text-brand-400 font-bold">₩8,700 / ₩27,800</span>
|
<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>
|
</div>
|
||||||
<div class="flex justify-between items-center p-3 border-b border-white/5">
|
<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>
|
<span class="text-brand-400 font-bold">₩27,800 / ₩31,300</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -657,9 +665,9 @@
|
|||||||
<span class="text-sm text-slate-400">Live Inventory Pricing</span>
|
<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>
|
<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-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>
|
</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>
|
<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)
|
실시간 재고/가격 조회 (Bot)
|
||||||
</a>
|
</a>
|
||||||
@@ -669,6 +677,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<!-- End Main Content -->
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="py-16 relative overflow-hidden">
|
<footer class="py-16 relative overflow-hidden">
|
||||||
<!-- Background -->
|
<!-- Background -->
|
||||||
@@ -696,13 +707,13 @@
|
|||||||
<!-- Company Info -->
|
<!-- Company Info -->
|
||||||
<div class="text-center md:text-right text-sm">
|
<div class="text-center md:text-right text-sm">
|
||||||
<div class="text-slate-400">© 2026 LIBEHAIM Inc. | Taro Tanaka</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom Decoration -->
|
<!-- Bottom Decoration -->
|
||||||
<div class="mt-12 pt-8 border-t border-white/5 text-center">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -906,7 +917,7 @@
|
|||||||
<p class="text-slate-400 text-xs mb-3">인스턴스가 활성화되었습니다</p>
|
<p class="text-slate-400 text-xs mb-3">인스턴스가 활성화되었습니다</p>
|
||||||
<div class="flex gap-2">
|
<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>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user