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:
kappa
2026-01-20 23:14:58 +09:00
parent 22650fb5bd
commit cd6ce130aa
2 changed files with 157 additions and 27 deletions

View File

@@ -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">

File diff suppressed because one or more lines are too long