a11y: accessibility improvements (WCAG 2.1 AA)
- Add skip link for keyboard navigation - Add ARIA labels to interactive elements - Add aria-hidden to decorative elements (ASCII logo, icons) - Add aria-live regions for dynamic content - Add scope="col" to table headers - Fix footer contrast (remove opacity-50) - Add prefers-reduced-motion support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
73
index.html
73
index.html
@@ -82,6 +82,29 @@
|
|||||||
/* Font families */
|
/* Font families */
|
||||||
.font-display { font-family: "Space Grotesk", sans-serif; }
|
.font-display { font-family: "Space Grotesk", sans-serif; }
|
||||||
.font-mono { font-family: "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
|
.font-mono { font-family: "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; }
|
||||||
|
|
||||||
|
/* Accessibility - Screen Reader Only */
|
||||||
|
.sr-only {
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
padding: 0;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
clip: rect(0, 0, 0, 0);
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.focus\:not-sr-only:focus {
|
||||||
|
position: absolute;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: visible;
|
||||||
|
clip: auto;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<style>
|
<style>
|
||||||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||||||
@@ -100,28 +123,42 @@
|
|||||||
|
|
||||||
.typing-effect { overflow: hidden; white-space: nowrap; animation: typing 2s steps(40, end); }
|
.typing-effect { overflow: hidden; white-space: nowrap; animation: typing 2s steps(40, end); }
|
||||||
@keyframes typing { from { width: 0 } to { width: 100% } }
|
@keyframes typing { from { width: 0 } to { width: 100% } }
|
||||||
|
|
||||||
|
/* Respect user's motion preferences */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.cursor-blink { animation: none; }
|
||||||
|
* {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background-dark text-terminal-text font-mono min-h-screen flex flex-col overflow-x-hidden selection:bg-terminal-cyan selection:text-background-dark">
|
<body class="bg-background-dark text-terminal-text font-mono min-h-screen flex flex-col overflow-x-hidden selection:bg-terminal-cyan selection:text-background-dark">
|
||||||
|
|
||||||
|
<!-- Skip Link for Accessibility -->
|
||||||
|
<a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary focus:text-background-dark focus:rounded focus:outline-none">
|
||||||
|
메인 콘텐츠로 건너뛰기
|
||||||
|
</a>
|
||||||
|
|
||||||
<div class="layout-container flex h-full grow flex-col items-center justify-center p-4 md:p-8 lg:p-12">
|
<div class="layout-container flex h-full grow flex-col items-center justify-center p-4 md:p-8 lg:p-12">
|
||||||
<!-- Main Terminal Window -->
|
<!-- Main Terminal Window -->
|
||||||
<div class="w-full max-w-[1400px] bg-background-dark border border-terminal-border rounded-lg shadow-[0_0_50px_-12px_rgba(0,0,0,0.7)] overflow-hidden flex flex-col">
|
<div class="w-full max-w-[1400px] bg-background-dark border border-terminal-border rounded-lg shadow-[0_0_50px_-12px_rgba(0,0,0,0.7)] overflow-hidden flex flex-col">
|
||||||
|
|
||||||
<!-- Window Title Bar -->
|
<!-- Window Title Bar -->
|
||||||
<div class="bg-[#161b22] px-4 py-3 flex items-center justify-between border-b border-terminal-border select-none">
|
<div class="bg-[#161b22] px-4 py-3 flex items-center justify-between border-b border-terminal-border select-none">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2" aria-hidden="true">
|
||||||
<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>
|
||||||
<div class="w-3 h-3 rounded-full bg-[#27c93f]"></div>
|
<div class="w-3 h-3 rounded-full bg-[#27c93f]"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 opacity-70">
|
<div class="flex items-center gap-2 opacity-70">
|
||||||
<span class="material-symbols-outlined text-[14px]">terminal</span>
|
<span class="material-symbols-outlined text-[14px]" aria-hidden="true">terminal</span>
|
||||||
<span class="text-xs md:text-sm text-terminal-muted font-display">root@anvil-cloud:~</span>
|
<span class="text-xs md:text-sm text-terminal-muted font-display">root@anvil-cloud:~</span>
|
||||||
</div>
|
</div>
|
||||||
<a href="https://t.me/AnvilForgeBot" target="_blank" class="text-xs text-terminal-muted hover:text-primary transition flex items-center gap-1">
|
<a href="https://t.me/AnvilForgeBot" target="_blank" class="text-xs text-terminal-muted hover:text-primary transition flex items-center gap-1" aria-label="텔레그램 봇 @AnvilForgeBot 열기">
|
||||||
<span>@AnvilForgeBot</span>
|
<span>@AnvilForgeBot</span>
|
||||||
<span class="material-symbols-outlined text-sm">open_in_new</span>
|
<span class="material-symbols-outlined text-sm" aria-hidden="true">open_in_new</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -140,10 +177,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
<div class="flex flex-col gap-6 z-10">
|
<div class="flex flex-col gap-6 z-10" id="main-content">
|
||||||
<!-- ASCII Art Logo -->
|
<!-- ASCII Art Logo -->
|
||||||
<div class="flex items-end gap-3">
|
<div class="flex items-end gap-3">
|
||||||
<pre class="text-primary font-bold leading-[0.85] text-[8px] sm:text-[10px] md:text-xs select-none"> █████╗ ███╗ ██╗██╗ ██╗██╗██╗
|
<pre aria-hidden="true" class="text-primary font-bold leading-[0.85] text-[8px] sm:text-[10px] md:text-xs select-none"> █████╗ ███╗ ██╗██╗ ██╗██╗██╗
|
||||||
██╔══██╗████╗ ██║██║ ██║██║██║
|
██╔══██╗████╗ ██║██║ ██║██║██║
|
||||||
███████║██╔██╗ ██║██║ ██║██║██║
|
███████║██╔██╗ ██║██║ ██║██║██║
|
||||||
██╔══██║██║╚██╗██║╚██╗ ██╔╝██║██║
|
██╔══██║██║╚██╗██║╚██╗ ██╔╝██║██║
|
||||||
@@ -154,7 +191,7 @@
|
|||||||
|
|
||||||
<!-- Main Headline with Rotation -->
|
<!-- Main Headline with Rotation -->
|
||||||
<div class="space-y-6 max-w-4xl mt-4" x-data="headlineRotator()">
|
<div class="space-y-6 max-w-4xl mt-4" x-data="headlineRotator()">
|
||||||
<div class="min-h-[120px] md:min-h-[160px] lg:min-h-[200px] flex flex-col justify-center">
|
<div class="min-h-[120px] md:min-h-[160px] lg:min-h-[200px] flex flex-col justify-center" aria-live="polite" aria-atomic="true">
|
||||||
<h1 class="text-2xl md:text-4xl lg:text-5xl font-display font-bold text-white tracking-tight leading-tight">
|
<h1 class="text-2xl md:text-4xl lg:text-5xl font-display font-bold text-white tracking-tight leading-tight">
|
||||||
<span class="text-terminal-muted mr-2">></span><span x-text="displayedLine1"></span><span x-show="isTypingLine1" class="text-primary cursor-blink">_</span><br/>
|
<span class="text-terminal-muted mr-2">></span><span x-text="displayedLine1"></span><span x-show="isTypingLine1" class="text-primary cursor-blink">_</span><br/>
|
||||||
<span class="text-terminal-muted mr-2">></span><span class="text-primary" x-text="displayedLine2"></span><span x-show="isTypingLine2 || (!isTypingLine1 && !isTypingLine2)" class="text-primary cursor-blink">_</span>
|
<span class="text-terminal-muted mr-2">></span><span class="text-primary" x-text="displayedLine2"></span><span x-show="isTypingLine2 || (!isTypingLine1 && !isTypingLine2)" class="text-primary cursor-blink">_</span>
|
||||||
@@ -181,7 +218,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Result Message -->
|
<!-- Result Message -->
|
||||||
<div class="mt-4 text-terminal-muted text-sm border-l-2 border-primary/50 pl-3 transition-opacity duration-300"
|
<div class="mt-4 text-terminal-muted text-sm border-l-2 border-primary/50 pl-3 transition-opacity duration-300"
|
||||||
:class="showResult ? 'opacity-100' : 'opacity-0'">
|
:class="showResult ? 'opacity-100' : 'opacity-0'" aria-live="polite">
|
||||||
<span class="text-primary">✓</span>
|
<span class="text-primary">✓</span>
|
||||||
<span x-text="commands[currentIndex]?.result"></span>
|
<span x-text="commands[currentIndex]?.result"></span>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,23 +297,23 @@
|
|||||||
<table class="w-full text-sm">
|
<table class="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="border-b border-terminal-border bg-[#161b22] text-left">
|
<tr class="border-b border-terminal-border bg-[#161b22] text-left">
|
||||||
<th class="px-4 py-3 font-bold text-terminal-muted cursor-pointer hover:text-white" @click="toggleSort('name')">
|
<th scope="col" class="px-4 py-3 font-bold text-terminal-muted cursor-pointer hover:text-white" @click="toggleSort('name')">
|
||||||
NAME <span x-show="sortBy === 'name'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
|
NAME <span x-show="sortBy === 'name'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 font-bold text-terminal-muted cursor-pointer hover:text-white text-center" @click="toggleSort('vcpu')">
|
<th scope="col" class="px-4 py-3 font-bold text-terminal-muted cursor-pointer hover:text-white text-center" @click="toggleSort('vcpu')">
|
||||||
CPU <span x-show="sortBy === 'vcpu'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
|
CPU <span x-show="sortBy === 'vcpu'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 font-bold text-terminal-muted cursor-pointer hover:text-white text-center" @click="toggleSort('memory')">
|
<th scope="col" class="px-4 py-3 font-bold text-terminal-muted cursor-pointer hover:text-white text-center" @click="toggleSort('memory')">
|
||||||
RAM <span x-show="sortBy === 'memory'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
|
RAM <span x-show="sortBy === 'memory'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 font-bold text-terminal-muted text-center hidden sm:table-cell">DISK</th>
|
<th scope="col" class="px-4 py-3 font-bold text-terminal-muted text-center hidden sm:table-cell">DISK</th>
|
||||||
<th class="px-4 py-3 font-bold text-terminal-muted text-center hidden md:table-cell">TRAFFIC</th>
|
<th scope="col" class="px-4 py-3 font-bold text-terminal-muted text-center hidden md:table-cell">TRAFFIC</th>
|
||||||
<th class="px-4 py-3 font-bold text-terminal-muted cursor-pointer hover:text-white text-right" @click="toggleSort('price')">
|
<th scope="col" class="px-4 py-3 font-bold text-terminal-muted cursor-pointer hover:text-white text-right" @click="toggleSort('price')">
|
||||||
PRICE/MO <span x-show="sortBy === 'price'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
|
PRICE/MO <span x-show="sortBy === 'price'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody aria-live="polite">
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<template x-if="loading">
|
<template x-if="loading">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -325,8 +362,8 @@
|
|||||||
<span class="text-primary" x-text="filteredInstances.length"></span> instances
|
<span class="text-primary" x-text="filteredInstances.length"></span> instances
|
||||||
<span x-show="fromCache" class="text-terminal-amber ml-2">(cached)</span>
|
<span x-show="fromCache" class="text-terminal-amber ml-2">(cached)</span>
|
||||||
</span>
|
</span>
|
||||||
<button @click="forceRefresh()" class="hover:text-primary transition-colors flex items-center gap-1" :disabled="loading">
|
<button @click="forceRefresh()" class="hover:text-primary transition-colors flex items-center gap-1" :disabled="loading" aria-label="가격 정보 새로고침">
|
||||||
<span class="material-symbols-outlined text-sm" :class="loading && 'animate-spin'">refresh</span>
|
<span class="material-symbols-outlined text-sm" :class="loading && 'animate-spin'" aria-hidden="true">refresh</span>
|
||||||
refresh
|
refresh
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -392,7 +429,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="mt-8 text-terminal-muted text-xs text-center opacity-50">
|
<footer class="mt-8 text-terminal-muted text-xs text-center">
|
||||||
© 2024 Anvil Hosting. All containers running. PID 1 healthy.
|
© 2024 Anvil Hosting. All containers running. PID 1 healthy.
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user