Files
anvil-hosting/index.html
kappa 05b8cf5e3a style: display KRW price prominently, USD smaller
- KRW: primary green color, text-lg, bold
- USD: muted gray, text-xs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 07:41:36 +09:00

536 lines
25 KiB
HTML

<!DOCTYPE html>
<html class="dark scroll-smooth" lang="ko">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Anvil Hosting - 개발자를 위한 컨테이너 클라우드</title>
<!-- SEO Meta Tags -->
<meta name="description" content="Incus/LXD 기반 초경량 컨테이너 호스팅. VM 오버헤드 없이 네이티브 성능을 제공하며, n8n, Ansible 자동화 파이프라인과 즉시 연동됩니다.">
<meta name="keywords" content="컨테이너 호스팅, LXD, Incus, 클라우드 서버, VPS, 도쿄 서버, 서울 서버, 개발자 호스팅">
<meta name="author" content="Anvil Hosting">
<meta name="robots" content="index, follow">
<link rel="canonical" href="https://hosting.anvil.it.com">
<!-- Open Graph -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://hosting.anvil.it.com">
<meta property="og:title" content="Anvil Hosting - 개발자를 위한 컨테이너 클라우드">
<meta property="og:description" content="Incus/LXD 기반 초경량 컨테이너 호스팅. 네이티브 성능과 자동화 파이프라인을 경험하세요.">
<meta property="og:locale" content="ko_KR">
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<!-- Fonts -->
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
"primary": "#3fb950",
"background-dark": "#0d1117",
"terminal-bg": "#010409",
"terminal-border": "#30363d",
"terminal-text": "#c9d1d9",
"terminal-muted": "#8b949e",
"terminal-cyan": "#58a6ff",
"terminal-amber": "#d29922",
"terminal-red": "#ff7b72",
"terminal-blue": "#79c0ff",
"terminal-purple": "#d2a8ff",
},
fontFamily: {
"display": ["Space Grotesk", "sans-serif"],
"mono": ["Fira Code", "ui-monospace", "SFMono-Regular", "Menlo", "Monaco", "Consolas", "monospace"],
},
},
},
}
</script>
<style>
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: #0d1117; }
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: #8b949e; }
.cursor-blink { animation: blink 1s step-end infinite; }
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
.syntax-key { color: #58a6ff; }
.syntax-string { color: #3fb950; }
.syntax-number { color: #79c0ff; }
.syntax-colon { color: #c9d1d9; }
.line-num { color: #484f58; user-select: none; text-align: right; padding-right: 1rem; }
.typing-effect { overflow: hidden; white-space: nowrap; animation: typing 2s steps(40, end); }
@keyframes typing { from { width: 0 } to { width: 100% } }
</style>
</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">
<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 -->
<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 -->
<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="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-[#27c93f]"></div>
</div>
<div class="flex items-center gap-2 opacity-70">
<span class="material-symbols-outlined text-[14px]">terminal</span>
<span class="text-xs md:text-sm text-terminal-muted font-display">root@anvil-cloud:~</span>
</div>
<a href="https://t.me/AnvilForgeBot" target="_blank" class="text-xs text-terminal-muted hover:text-primary transition flex items-center gap-1">
<span>@AnvilForgeBot</span>
<span class="material-symbols-outlined text-sm">open_in_new</span>
</a>
</div>
<!-- Terminal Content -->
<div class="p-6 md:p-10 flex flex-col gap-12 relative bg-gradient-to-br from-background-dark to-[#0f141a]">
<!-- Background Grid -->
<div class="absolute inset-0 opacity-[0.03]" style="background-image: linear-gradient(#30363d 1px, transparent 1px), linear-gradient(90deg, #30363d 1px, transparent 1px); background-size: 40px 40px;"></div>
<!-- Prompt -->
<div class="flex flex-wrap items-center gap-2 text-sm z-10 font-bold">
<span class="text-primary">anvil@hosting</span>
<span class="text-terminal-muted">:</span>
<span class="text-terminal-blue">~</span>
<span class="text-terminal-muted">$</span>
<span class="text-terminal-text">cat README.md</span>
</div>
<!-- Hero Section -->
<div class="flex flex-col gap-6 z-10">
<!-- ASCII Art Logo -->
<pre class="text-primary font-bold leading-[0.9] text-[10px] sm:text-xs md:text-sm select-none">
_ _ ___ _____ _
/ \ | \ | \ \ / /_ _| |
/ _ \ | \| |\ \ / / | || |
/ ___ \| |\ | \ V / | || |___
/_/ \_\_| \_| \_/ |___|_____|
</pre>
<!-- Main Headline -->
<div class="space-y-4 max-w-4xl mt-4">
<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">&gt;</span>Incus/LXD 기반<br/>
<span class="text-terminal-muted mr-2">&gt;</span>초경량 컨테이너 호스팅<span class="text-primary cursor-blink text-3xl md:text-5xl align-middle ml-1"></span>
</h1>
<p class="text-terminal-muted text-base md:text-lg max-w-2xl border-l-2 border-terminal-border pl-4 mt-4">
# VM 오버헤드 없이 네이티브 성능<br/>
# 하이퍼바이저 없는 순수 Linux 컨테이너<br/>
# 도쿄 · 서울 · 싱가포르 · 홍콩 리전
</p>
</div>
<!-- Interactive Command -->
<div class="w-full max-w-2xl mt-6 group">
<div class="bg-terminal-bg border border-terminal-border rounded flex items-center p-3 md:p-4 gap-3 shadow-lg group-hover:border-primary/50 transition-colors">
<span class="text-primary font-bold select-none">$</span>
<div class="flex-1 text-sm md:text-base flex items-center gap-2 overflow-x-auto">
<span class="text-terminal-purple">incus</span>
<span class="text-terminal-text">launch</span>
<span class="text-terminal-cyan">ubuntu:24.04</span>
<span class="text-terminal-amber">tokyo-1</span>
<span class="text-terminal-muted">--vm</span>
</div>
<span class="text-xs text-terminal-muted bg-terminal-border/30 px-2 py-1 rounded hidden sm:inline-block">ENTER ↵</span>
</div>
<div class="pl-4 mt-2 text-xs text-terminal-muted flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-primary animate-pulse"></span>
Instance creating... (0.4s)
</div>
</div>
</div>
<!-- Features Grid -->
<div class="grid md:grid-cols-3 gap-6 z-10 border-t border-terminal-border/50 pt-12" id="features">
<div class="bg-terminal-bg/50 border border-terminal-border rounded-lg p-6 hover:border-primary/50 transition-colors">
<div class="text-primary text-2xl mb-3"></div>
<h3 class="font-display font-bold text-white mb-2">즉시 프로비저닝</h3>
<p class="text-terminal-muted text-sm">0.4초 내 인스턴스 생성. 하이퍼바이저 부팅 대기 없음.</p>
</div>
<div class="bg-terminal-bg/50 border border-terminal-border rounded-lg p-6 hover:border-terminal-cyan/50 transition-colors">
<div class="text-terminal-cyan text-2xl mb-3">🛡️</div>
<h3 class="font-display font-bold text-white mb-2">1Tbps+ DDoS 방어</h3>
<p class="text-terminal-muted text-sm">ForgeShield 통합 보호. L3/L4/L7 완전 방어.</p>
</div>
<div class="bg-terminal-bg/50 border border-terminal-border rounded-lg p-6 hover:border-terminal-amber/50 transition-colors">
<div class="text-terminal-amber text-2xl mb-3">🤖</div>
<h3 class="font-display font-bold text-white mb-2">텔레그램 봇 관리</h3>
<p class="text-terminal-muted text-sm">@AnvilForgeBot으로 서버 생성, 모니터링, 도메인 등록.</p>
</div>
</div>
<!-- Pricing Section -->
<div class="z-10 border-t border-terminal-border/50 pt-12" id="pricing" x-data="pricingTerminal()">
<!-- Section Header -->
<div class="flex items-center gap-2 text-sm text-terminal-muted mb-6">
<span class="text-primary">$</span> kubectl get instances --all-regions
</div>
<!-- Region Tabs -->
<div class="flex flex-wrap gap-2 mb-6">
<template x-for="region in regions" :key="region.id">
<button
@click="selectedRegion = region.id; filterInstances()"
:class="selectedRegion === region.id ? 'bg-primary text-background-dark' : 'bg-terminal-bg text-terminal-text hover:bg-terminal-border'"
class="px-3 py-1.5 rounded text-xs font-bold transition-colors border border-terminal-border flex items-center gap-2"
>
<span x-text="region.flag"></span>
<span x-text="region.name"></span>
<span class="opacity-60" x-text="'(' + getRegionCount(region.id) + ')'"></span>
</button>
</template>
</div>
<!-- Command Line -->
<div class="bg-terminal-bg border border-terminal-border rounded-t-lg px-4 py-2 text-xs text-terminal-muted flex items-center justify-between">
<span>
<span class="text-primary">$</span> kubectl get instances --region=<span class="text-terminal-cyan" x-text="selectedRegion"></span> --sort-by=<span class="text-terminal-amber" x-text="sortBy"></span>
</span>
<span x-show="loading" class="text-terminal-amber animate-pulse">fetching...</span>
<span x-show="!loading && lastUpdate" class="text-terminal-muted" x-text="'synced ' + getLastUpdateText()"></span>
</div>
<!-- Instances Table -->
<div class="bg-terminal-bg border-x border-b border-terminal-border rounded-b-lg overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<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')">
NAME <span x-show="sortBy === 'name'" x-text="sortOrder === 'asc' ? '↑' : '↓'"></span>
</th>
<th 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>
</th>
<th 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>
</th>
<th 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 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>
</th>
</tr>
</thead>
<tbody>
<!-- Loading State -->
<template x-if="loading">
<tr>
<td colspan="6" class="px-4 py-8 text-center text-terminal-muted">
<span class="animate-pulse">Fetching instances from API...</span>
</td>
</tr>
</template>
<!-- Empty State -->
<template x-if="!loading && filteredInstances.length === 0">
<tr>
<td colspan="6" class="px-4 py-8 text-center text-terminal-muted">
No instances found for this region.
</td>
</tr>
</template>
<!-- Instance Rows -->
<template x-for="(inst, index) in filteredInstances" :key="inst.id + '-' + inst.region?.display_name">
<tr class="border-b border-terminal-border/50 hover:bg-terminal-border/20 transition-colors">
<td class="px-4 py-3">
<span class="text-terminal-cyan" x-text="inst.id || inst.instance_name"></span>
</td>
<td class="px-4 py-3 text-center">
<span class="text-terminal-amber" x-text="inst.vcpu"></span>
</td>
<td class="px-4 py-3 text-center">
<span class="text-primary" x-text="formatMemory(inst.memory_mb)"></span>
</td>
<td class="px-4 py-3 text-center text-terminal-muted hidden sm:table-cell" x-text="inst.storage_gb + 'GB'"></td>
<td class="px-4 py-3 text-center text-terminal-muted hidden md:table-cell" x-text="inst.transfer_tb ? inst.transfer_tb + 'TB' : '-'"></td>
<td class="px-4 py-3 text-right">
<span class="text-primary font-bold text-lg" x-text="'₩' + (inst.pricing?.monthly_price_krw || 0).toLocaleString()"></span>
<span class="text-terminal-muted text-xs block" x-text="'$' + inst.pricing?.monthly_price?.toFixed(2)"></span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<!-- Table Footer -->
<div class="px-4 py-3 border-t border-terminal-border bg-[#161b22] text-xs text-terminal-muted flex justify-between items-center">
<span>
<span class="text-primary" x-text="filteredInstances.length"></span> instances
<span x-show="fromCache" class="text-terminal-amber ml-2">(cached)</span>
</span>
<button @click="forceRefresh()" class="hover:text-primary transition-colors flex items-center gap-1" :disabled="loading">
<span class="material-symbols-outlined text-sm" :class="loading && 'animate-spin'">refresh</span>
refresh
</button>
</div>
</div>
<!-- Server Specs -->
<div class="mt-8 grid md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-terminal-bg/50 border border-terminal-border rounded p-4">
<div class="text-terminal-muted text-xs mb-1"># Storage</div>
<div class="text-white font-bold">NVMe Gen 4</div>
</div>
<div class="bg-terminal-bg/50 border border-terminal-border rounded p-4">
<div class="text-terminal-muted text-xs mb-1"># Network</div>
<div class="text-white font-bold">25Gbps Private</div>
</div>
<div class="bg-terminal-bg/50 border border-terminal-border rounded p-4">
<div class="text-terminal-muted text-xs mb-1"># CPU</div>
<div class="text-white font-bold">AMD EPYC / Xeon</div>
</div>
<div class="bg-terminal-bg/50 border border-terminal-border rounded p-4">
<div class="text-terminal-muted text-xs mb-1"># IP</div>
<div class="text-white font-bold">IPv4 + IPv6</div>
</div>
</div>
</div>
<!-- CTA Section -->
<div class="flex flex-col items-center justify-center py-12 z-10 gap-8 border-t border-terminal-border/50">
<div class="text-center space-y-2">
<h2 class="text-3xl font-display font-bold text-white">$ ./deploy --now</h2>
<p class="text-terminal-muted">텔레그램에서 바로 서버를 생성하세요</p>
</div>
<a href="https://t.me/AnvilForgeBot" target="_blank" class="group relative flex items-center justify-center">
<div class="absolute -inset-1 rounded-lg bg-gradient-to-r from-primary via-terminal-cyan to-primary opacity-75 blur transition duration-500 group-hover:opacity-100 animate-pulse"></div>
<div class="relative flex items-center bg-terminal-bg rounded-lg px-6 py-4 border border-primary">
<span class="text-primary font-bold mr-3">$</span>
<span class="text-terminal-text font-bold text-lg">curl -sSL t.me/AnvilForgeBot | start</span>
<span class="ml-4 material-symbols-outlined text-primary group-hover:translate-x-1 transition-transform">arrow_forward</span>
</div>
</a>
<div class="text-xs text-terminal-muted flex gap-6">
<a class="hover:text-primary underline decoration-primary/30 underline-offset-4" href="/terms.html">--terms</a>
<a class="hover:text-primary underline decoration-primary/30 underline-offset-4" href="/privacy.html">--privacy</a>
<a class="hover:text-primary underline decoration-primary/30 underline-offset-4" href="/sla.html">--sla</a>
</div>
</div>
</div>
<!-- Footer / Vim Status Bar -->
<div class="bg-primary text-background-dark text-xs font-bold py-1 px-4 flex justify-between items-center select-none">
<div class="flex gap-4">
<span class="bg-background-dark/20 px-2 py-0.5 rounded text-white/90">NORMAL</span>
<span>index.html</span>
</div>
<div class="flex gap-4">
<span class="hidden sm:inline">utf-8</span>
<span class="hidden sm:inline">unix</span>
<span>100%</span>
<span>Ln 1, Col 1</span>
</div>
</div>
</div>
<footer class="mt-8 text-terminal-muted text-xs text-center opacity-50">
© 2024 Anvil Hosting. All containers running. PID 1 healthy.
</footer>
</div>
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.3/dist/cdn.min.js"></script>
<script>
function pricingTerminal() {
return {
// State
instances: [],
filteredInstances: [],
selectedRegion: 'tokyo-1',
sortBy: 'price',
sortOrder: 'asc',
loading: false,
lastUpdate: null,
fromCache: false,
// Region definitions
regions: [
{ id: 'tokyo-1', name: 'Tokyo 1', flag: '🇯🇵', filter: ['tokyo 1'] },
{ id: 'tokyo-2', name: 'Tokyo 2', flag: '🇯🇵', filter: ['tokyo 2'] },
{ id: 'tokyo-3', name: 'Tokyo 3', flag: '🇯🇵', filter: ['tokyo 3'] },
{ id: 'osaka-1', name: 'Osaka 1', flag: '🇯🇵', filter: ['osaka 1'] },
{ id: 'osaka-2', name: 'Osaka 2', flag: '🇯🇵', filter: ['osaka 2'] },
{ id: 'seoul-1', name: 'Seoul 1', flag: '🇰🇷', filter: ['seoul'] },
{ id: 'singapore-1', name: 'Singapore 1', flag: '🇸🇬', filter: ['singapore'] },
],
// Initialize
async init() {
await this.loadInstances();
},
// Load from API
async loadInstances() {
// Check cache first
const cached = this.getCache();
if (cached) {
this.instances = cached.instances;
this.lastUpdate = new Date(cached.timestamp);
this.fromCache = true;
this.filterInstances();
return;
}
await this.fetchFromApi();
},
// Fetch from API
async fetchFromApi() {
this.loading = true;
this.fromCache = false;
try {
const res = await fetch('/api/pricing');
const data = await res.json();
if (data.success && data.instances) {
this.instances = data.instances;
this.setCache(data.instances);
this.lastUpdate = new Date();
}
} catch (e) {
console.error('[Pricing] Fetch error:', e);
// Use fallback data
this.instances = this.getFallbackData();
} finally {
this.loading = false;
this.filterInstances();
}
},
// Filter instances by region
filterInstances() {
const region = this.regions.find(r => r.id === this.selectedRegion);
if (!region) return;
this.filteredInstances = this.instances.filter(inst => {
const displayName = (inst.region?.display_name || '').toLowerCase();
const hasGpu = inst.has_gpu || inst.category === 'gpu';
// GPU 제외, 리전 필터 매칭
const matchesFilter = region.filter.some(f => displayName.includes(f));
return matchesFilter && !hasGpu;
});
// Sort
this.sortInstances();
},
// Sort instances
sortInstances() {
const order = this.sortOrder === 'asc' ? 1 : -1;
this.filteredInstances.sort((a, b) => {
let diff = 0;
switch (this.sortBy) {
case 'price':
diff = (a.pricing?.monthly_price || 0) - (b.pricing?.monthly_price || 0);
break;
case 'vcpu':
diff = (a.vcpu || 0) - (b.vcpu || 0);
break;
case 'memory':
diff = (a.memory_mb || 0) - (b.memory_mb || 0);
break;
case 'name':
diff = (a.id || '').localeCompare(b.id || '');
break;
}
return diff * order;
});
},
// Toggle sort
toggleSort(column) {
if (this.sortBy === column) {
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
} else {
this.sortBy = column;
this.sortOrder = column === 'price' ? 'asc' : 'desc';
}
this.sortInstances();
},
// Get region count
getRegionCount(regionId) {
const region = this.regions.find(r => r.id === regionId);
if (!region) return 0;
return this.instances.filter(inst => {
const displayName = (inst.region?.display_name || '').toLowerCase();
const hasGpu = inst.has_gpu || inst.category === 'gpu';
const matchesFilter = region.filter.some(f => displayName.includes(f));
return matchesFilter && !hasGpu;
}).length;
},
// Format memory
formatMemory(mb) {
if (!mb) return '-';
return mb >= 1024 ? (mb / 1024) + 'GB' : mb + 'MB';
},
// Last update text
getLastUpdateText() {
if (!this.lastUpdate) return '';
const diff = Math.floor((Date.now() - this.lastUpdate) / 1000);
if (diff < 60) return 'just now';
if (diff < 3600) return Math.floor(diff / 60) + 'm ago';
return this.lastUpdate.toLocaleTimeString();
},
// Force refresh
async forceRefresh() {
localStorage.removeItem('anvil_pricing_cache_v6');
await this.fetchFromApi();
},
// Cache helpers
getCache() {
try {
const data = localStorage.getItem('anvil_pricing_cache_v6');
if (!data) return null;
const parsed = JSON.parse(data);
if (Date.now() - parsed.timestamp > 3600000) return null; // 1h TTL
return parsed;
} catch { return null; }
},
setCache(instances) {
try {
localStorage.setItem('anvil_pricing_cache_v6', JSON.stringify({
instances, timestamp: Date.now()
}));
} catch {}
},
// Fallback data
getFallbackData() {
return [
{ id: 'anvil-1g-1c', vcpu: 1, memory_mb: 1024, storage_gb: 25, transfer_tb: 1, region: { display_name: 'Tokyo 1' }, pricing: { monthly_price: 6, monthly_price_krw: 8500 } },
{ id: 'anvil-2g-1c', vcpu: 1, memory_mb: 2048, storage_gb: 50, transfer_tb: 2, region: { display_name: 'Tokyo 1' }, pricing: { monthly_price: 12, monthly_price_krw: 17000 } },
{ id: 'anvil-4g-2c', vcpu: 2, memory_mb: 4096, storage_gb: 80, transfer_tb: 4, region: { display_name: 'Tokyo 1' }, pricing: { monthly_price: 24, monthly_price_krw: 34000 } },
];
}
};
}
</script>
</body>
</html>