Apply accessibility patches for WCAG compliance
- Add CSS focus indicators for keyboard navigation - Add high contrast mode and reduced motion support - Improve text color contrast (slate-400 → slate-300) - Add ARIA labels to modal, tabs, pricing table - Add keyboard navigation to tab component - Improve image alt text descriptions - Add sr-only utility class and screen reader descriptions - Make region selection buttons accessible with role="radio" Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
93
index.html
93
index.html
@@ -54,7 +54,7 @@
|
|||||||
<div class="w-8 h-8 rounded bg-brand-500 flex items-center justify-center text-white font-bold font-mono text-xl shadow-lg shadow-brand-500/20">A</div>
|
<div class="w-8 h-8 rounded bg-brand-500 flex items-center justify-center text-white font-bold font-mono text-xl shadow-lg shadow-brand-500/20">A</div>
|
||||||
<span class="font-bold text-lg tracking-tight">Anvil<span class="text-brand-400">.Hosting</span></span>
|
<span class="font-bold text-lg tracking-tight">Anvil<span class="text-brand-400">.Hosting</span></span>
|
||||||
</a>
|
</a>
|
||||||
<div class="hidden md:flex gap-6 text-sm font-medium text-slate-400">
|
<div class="hidden md:flex gap-6 text-sm font-medium text-slate-300">
|
||||||
<a href="#features" class="hover:text-white transition">기능</a>
|
<a href="#features" class="hover:text-white transition">기능</a>
|
||||||
<a href="#pricing" class="hover:text-white transition">요금제</a>
|
<a href="#pricing" class="hover:text-white transition">요금제</a>
|
||||||
<a href="#automation" class="hover:text-white transition">자동화</a>
|
<a href="#automation" class="hover:text-white transition">자동화</a>
|
||||||
@@ -298,13 +298,27 @@
|
|||||||
|
|
||||||
<div class="lg:w-1/2 w-full" x-data="{ tab: 'n8n' }">
|
<div class="lg:w-1/2 w-full" x-data="{ tab: 'n8n' }">
|
||||||
<!-- Tab Buttons -->
|
<!-- Tab Buttons -->
|
||||||
<div class="flex gap-2 mb-4">
|
<div class="flex gap-2 mb-4" role="tablist" aria-label="코드 예제 선택">
|
||||||
<button onclick="switchTab('n8n')" id="btn-n8n" class="px-4 py-2 rounded-lg bg-purple-600 text-white text-sm font-bold transition">n8n Workflow</button>
|
<button role="tab"
|
||||||
<button onclick="switchTab('tf')" id="btn-tf" class="px-4 py-2 rounded-lg bg-slate-800 text-slate-400 text-sm font-bold border border-slate-700 hover:text-white transition">Terraform</button>
|
aria-selected="true"
|
||||||
|
aria-controls="panel-n8n"
|
||||||
|
onclick="switchTab('n8n')"
|
||||||
|
id="btn-n8n"
|
||||||
|
class="px-4 py-2 rounded-lg bg-purple-600 text-white text-sm font-bold transition">
|
||||||
|
n8n Workflow
|
||||||
|
</button>
|
||||||
|
<button role="tab"
|
||||||
|
aria-selected="false"
|
||||||
|
aria-controls="panel-tf"
|
||||||
|
onclick="switchTab('tf')"
|
||||||
|
id="btn-tf"
|
||||||
|
class="px-4 py-2 rounded-lg bg-slate-800 text-slate-400 text-sm font-bold border border-slate-700 hover:text-white transition">
|
||||||
|
Terraform
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- n8n Window -->
|
<!-- n8n Window -->
|
||||||
<div id="panel-n8n" class="rounded-2xl overflow-hidden shadow-2xl border border-slate-700 relative group">
|
<div id="panel-n8n" role="tabpanel" aria-labelledby="btn-n8n" class="rounded-2xl overflow-hidden shadow-2xl border border-slate-700 relative group">
|
||||||
<!-- Window Header -->
|
<!-- Window Header -->
|
||||||
<div class="flex items-center px-4 py-3 bg-[#1a1a2e] border-b border-slate-700/50 gap-2">
|
<div class="flex items-center px-4 py-3 bg-[#1a1a2e] border-b border-slate-700/50 gap-2">
|
||||||
<div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div>
|
<div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div>
|
||||||
@@ -314,12 +328,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Screenshot Image (Cropped diagram) -->
|
<!-- Screenshot Image (Cropped diagram) -->
|
||||||
<div class="relative overflow-hidden rounded-b-xl">
|
<div class="relative overflow-hidden rounded-b-xl">
|
||||||
<img src="images/n8n-workflow.png" alt="n8n 워크플로우 자동화 예시" class="w-full h-auto block transition-transform duration-500 group-hover:scale-[1.02]" loading="lazy">
|
<img src="images/n8n-workflow.png"
|
||||||
|
alt="n8n 워크플로우 자동화 예시: GitHub Actions 트리거 → Anvil API 호출 → 서버 자동 생성 프로세스"
|
||||||
|
class="w-full h-auto block transition-transform duration-500 group-hover:scale-[1.02]"
|
||||||
|
loading="lazy"
|
||||||
|
crossorigin="anonymous">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Terraform Window (Hidden by default) -->
|
<!-- Terraform Window (Hidden by default) -->
|
||||||
<div id="panel-tf" class="hidden bg-[#1e1e1e] rounded-xl overflow-hidden shadow-2xl border border-slate-700 h-[320px] font-mono text-sm">
|
<div id="panel-tf" role="tabpanel" aria-labelledby="btn-tf" class="hidden bg-[#1e1e1e] rounded-xl overflow-hidden shadow-2xl border border-slate-700 h-[320px] font-mono text-sm">
|
||||||
<div class="flex items-center px-4 py-2 bg-[#252526] border-b border-[#3e3e42] gap-2">
|
<div class="flex items-center px-4 py-2 bg-[#252526] border-b border-[#3e3e42] gap-2">
|
||||||
<div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div>
|
<div class="w-3 h-3 rounded-full bg-[#ff5f56]"></div>
|
||||||
<div class="w-3 h-3 rounded-full bg-[#ffbd2e]"></div>
|
<div class="w-3 h-3 rounded-full bg-[#ffbd2e]"></div>
|
||||||
@@ -536,7 +554,11 @@
|
|||||||
<!-- Dynamic Pricing Table -->
|
<!-- Dynamic Pricing Table -->
|
||||||
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 transform scale-95" x-transition:enter-end="opacity-100 transform scale-100">
|
<div x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 transform scale-95" x-transition:enter-end="opacity-100 transform scale-100">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full text-left font-sans" aria-label="서버 요금표">
|
<table class="w-full text-left font-sans"
|
||||||
|
aria-label="서버 요금 비교표 - 리전 및 플랜별 가격 정보">
|
||||||
|
<caption class="sr-only">
|
||||||
|
서버 요금은 월간 기준입니다. 글로벌 리전(도쿄, 싱가포르, 홍콩)과 서울 프리미엄 리전 중에서 선택할 수 있습니다.
|
||||||
|
</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="text-slate-500 text-sm border-b border-slate-700">
|
<tr class="text-slate-500 text-sm border-b border-slate-700">
|
||||||
<th class="pb-4 font-medium pl-4">Plan</th>
|
<th class="pb-4 font-medium pl-4">Plan</th>
|
||||||
@@ -701,42 +723,54 @@
|
|||||||
|
|
||||||
x-transition:leave-end="opacity-0"
|
x-transition:leave-end="opacity-0"
|
||||||
|
|
||||||
class="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-dark-900/90 backdrop-blur-sm"
|
class="fixed inset-0 z-[100] flex items-center justify-center p-2 sm:p-4 bg-dark-900/90 backdrop-blur-sm"
|
||||||
|
|
||||||
style="display: none;">
|
style="display: none;">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div @click.away="if(!launching) launcherOpen = false"
|
<div @click.away="if(!launching) launcherOpen = false"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-labelledby="launcher-title"
|
aria-labelledby="modal-main-title"
|
||||||
class="bg-slate-900/95 backdrop-blur-xl border border-white/10 mx-4 rounded-3xl shadow-2xl shadow-black/50 overflow-hidden flex flex-col max-h-[85vh]"
|
aria-describedby="modal-description"
|
||||||
style="width: 95vw; max-width: 800px;">
|
class="bg-slate-900/95 backdrop-blur-xl border border-white/10 mx-2 sm:mx-4 rounded-3xl shadow-2xl shadow-black/50 overflow-hidden flex flex-col"
|
||||||
|
style="width: 100%; max-width: 800px; max-height: 90vh; height: 90vh;">
|
||||||
|
|
||||||
|
<!-- Accessibility: Hidden description -->
|
||||||
|
<div id="modal-description" class="sr-only">
|
||||||
|
단계별로 서버 구성을 선택하세요. 각 단계에서 옵션을 클릭하거나 Tab 키로 네비게이션할 수 있습니다. ESC 키를 누르면 닫을 수 있습니다.
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Modal Header -->
|
<!-- Modal Header -->
|
||||||
<div class="px-4 py-3 border-b border-white/5 flex justify-between items-center flex-shrink-0">
|
<div class="px-4 py-3 border-b border-white/5 flex justify-between items-center flex-shrink-0">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-brand-500 to-purple-500 flex items-center justify-center text-sm">🚀</div>
|
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-brand-500 to-purple-500 flex items-center justify-center text-sm flex-shrink-0">🚀</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 id="launcher-title" class="text-sm font-bold text-white">Server Launcher</h3>
|
<h2 id="modal-main-title" class="text-sm font-bold text-white">서버 생성 마법사</h2>
|
||||||
<p class="text-[10px] text-slate-400">Anvil Forge Bot</p>
|
<p class="text-[10px] text-slate-400">Anvil Forge Bot</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button @click="resetLauncher" aria-label="닫기" class="w-8 h-8 flex items-center justify-center rounded-full bg-white/5 text-slate-400 hover:bg-white/10 hover:text-white transition" x-show="!launching">
|
<button @click="resetLauncher"
|
||||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
aria-label="모달 닫기 (ESC 키 지원)"
|
||||||
|
class="modal-close w-8 h-8 flex items-center justify-center rounded-full bg-white/5 text-slate-400 hover:bg-white/10 hover:text-white transition flex-shrink-0"
|
||||||
|
x-show="!launching">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chat Container -->
|
<!-- Chat Container -->
|
||||||
<div id="chat-container" class="flex-1 overflow-y-auto px-6 py-4 space-y-4" x-show="wizardStep < 5">
|
<div id="chat-container" class="flex-1 overflow-y-auto px-3 sm:px-6 py-3 sm:py-4 space-y-2 sm:space-y-4" x-show="wizardStep < 5">
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
<template x-for="(msg, idx) in messages" :key="idx">
|
<template x-for="(msg, idx) in messages" :key="idx">
|
||||||
<div :class="msg.type === 'bot' ? 'flex justify-start' : 'flex justify-end'">
|
<div :class="msg.type === 'bot' ? 'flex justify-start' : 'flex justify-end'"
|
||||||
|
role="article"
|
||||||
|
:aria-label="(msg.type === 'bot' ? 'Anvil Forge Bot' : '당신') + ': ' + msg.text">
|
||||||
<div :class="msg.type === 'bot'
|
<div :class="msg.type === 'bot'
|
||||||
? 'bg-slate-800 text-slate-200 rounded-2xl rounded-tl-sm'
|
? 'bg-slate-800 text-slate-200 rounded-xl sm:rounded-2xl rounded-tl-sm'
|
||||||
: 'bg-brand-500 text-white rounded-2xl rounded-tr-sm'"
|
: 'bg-brand-500 text-white rounded-xl sm:rounded-2xl rounded-tr-sm'"
|
||||||
class="px-4 py-2.5 text-sm">
|
class="px-3 py-2 sm:px-4 sm:py-2.5 text-xs sm:text-sm max-w-[90%] break-words"
|
||||||
|
role="document">
|
||||||
<span x-text="msg.text"></span>
|
<span x-text="msg.text"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -745,21 +779,28 @@
|
|||||||
<!-- Current Step Options -->
|
<!-- Current Step Options -->
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<!-- Step 0: Region Selection -->
|
<!-- Step 0: Region Selection -->
|
||||||
<div x-show="wizardStep === 0" class="space-y-3">
|
<fieldset x-show="wizardStep === 0" class="space-y-3">
|
||||||
|
<legend class="sr-only">리전 선택 (지역 선택)</legend>
|
||||||
<template x-for="r in regions" :key="r.id">
|
<template x-for="r in regions" :key="r.id">
|
||||||
<button @click="selectRegion(r)"
|
<button @click="selectRegion(r)"
|
||||||
class="w-full flex items-center justify-between px-5 py-4 bg-slate-800/80 hover:bg-slate-700 border border-slate-700/50 hover:border-brand-500/50 rounded-xl transition-all group">
|
role="radio"
|
||||||
|
:aria-checked="config.region === r.id"
|
||||||
|
:tabindex="config.region === r.id ? '0' : '-1'"
|
||||||
|
class="w-full flex items-center justify-between px-5 py-4 bg-slate-800/80 hover:bg-slate-700 border-2 rounded-xl transition-all group"
|
||||||
|
:class="config.region === r.id
|
||||||
|
? 'border-brand-500 bg-brand-500/10'
|
||||||
|
: 'border-slate-700/50 hover:border-brand-500/50'">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span class="text-3xl" x-text="r.flag"></span>
|
<span class="text-3xl" x-text="r.flag"></span>
|
||||||
<div class="text-left">
|
<div class="text-left">
|
||||||
<div class="font-semibold text-white text-base" x-text="r.name"></div>
|
<div class="font-semibold text-white text-base" x-text="r.name"></div>
|
||||||
<div class="text-sm text-slate-400" x-text="r.id"></div>
|
<div class="text-sm text-slate-300" x-text="r.id"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-slate-500 group-hover:text-brand-400 font-mono" x-text="r.ping"></div>
|
<div class="text-sm text-slate-500 group-hover:text-brand-400 font-mono" x-text="r.ping"></div>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</fieldset>
|
||||||
|
|
||||||
<!-- Step 1: Plan Selection -->
|
<!-- Step 1: Plan Selection -->
|
||||||
<div x-show="wizardStep === 1" class="space-y-3">
|
<div x-show="wizardStep === 1" class="space-y-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user