feat(marketing): rebuild landing + layout from new design (multi-game, real content)
All checks were successful
Test Asgard Runner / test (push) Successful in 3s

MarketingLayout + LandingView rebuilt from the delivered design as a multi-game platform site (was Rust-only stub): hero with per-game re-skin + panel mockup, 8-pain problem grid, agent-model shift, 4 self-themed game blueprints (Rust/Dune/Conan/Soulmask), core capabilities, wipe orchestration, built-like-infrastructure, public sites/storefront, pricing, serious-admins, final CTA, footer. REAL pricing (Hobby $9.99 / Community $19.99 / Operator $99.99 / Network $99.99 + $49.99 fleet block) + commercial-use definition + self-service support model ($125/hr prepaid blocks, 'a tool, not a managed service'). marketing.css ported (token-based). 6 icons added to the registry. No fabricated metrics/testimonials. Build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-06-11 04:52:12 -04:00
parent f2b09b281a
commit 1edaaf985d
4 changed files with 1578 additions and 331 deletions

View File

@@ -23,6 +23,8 @@ import {
Ban, Flag,
CircleAlert, ArrowDown, Award, DollarSign, FlaskConical, Mail, Package,
Pencil, Save, ShoppingBag, Target, User,
// Marketing site additions
Route, Timer, Megaphone, DatabaseBackup, Store, Undo2,
} from 'lucide-vue-next'
const props = withDefaults(
@@ -58,6 +60,9 @@ const registry: Record<string, Component> = {
'dollar-sign': DollarSign, 'flask-conical': FlaskConical, mail: Mail,
package: Package, pencil: Pencil, save: Save, 'shopping-bag': ShoppingBag,
target: Target, user: User,
// Marketing site additions
route: Route, timer: Timer, megaphone: Megaphone,
'database-backup': DatabaseBackup, store: Store, 'undo-2': Undo2,
}
const cmp = computed<Component | null>(() => registry[props.name] ?? null)

View File

@@ -1,76 +1,80 @@
<script setup lang="ts">
import { RouterView, RouterLink } from 'vue-router'
import CorrosionMark from '@/components/brand/CorrosionMark.vue'
import '@/styles/marketing.css'
const panelUrl = import.meta.env.VITE_PANEL_URL || ''
const panelUrl = import.meta.env.VITE_PANEL_URL ?? ''
</script>
<template>
<div class="min-h-screen bg-neutral-950 flex flex-col">
<!-- Navigation -->
<nav class="border-b border-neutral-800 bg-neutral-950/80 backdrop-blur-sm sticky top-0 z-50">
<div class="max-w-6xl mx-auto px-6 py-4 flex items-center justify-between">
<RouterLink :to="{ name: 'landing' }" class="flex items-center gap-3">
<img src="/logo.png" alt="Corrosion" class="h-8 w-8" />
<span class="text-lg font-bold text-neutral-100">Corrosion</span>
<div>
<!-- Nav -->
<nav class="mkt-nav">
<div class="wrap mkt-nav__in">
<RouterLink :to="{ name: 'landing' }" class="brand">
<span class="mark"><CorrosionMark :size="26" /></span>
<b>Corrosion</b>
</RouterLink>
<div class="hidden md:flex items-center gap-6">
<RouterLink :to="{ name: 'how-it-works' }" class="text-sm text-neutral-400 hover:text-neutral-100 transition-colors">How It Works</RouterLink>
<RouterLink :to="{ name: 'pricing' }" class="text-sm text-neutral-400 hover:text-neutral-100 transition-colors">Pricing</RouterLink>
<RouterLink :to="{ name: 'roadmap' }" class="text-sm text-neutral-400 hover:text-neutral-100 transition-colors">Roadmap</RouterLink>
<RouterLink :to="{ name: 'faq' }" class="text-sm text-neutral-400 hover:text-neutral-100 transition-colors">FAQ</RouterLink>
<div class="mkt-nav__links">
<RouterLink :to="{ name: 'landing' }" class="scroll-link">Features</RouterLink>
<RouterLink :to="{ name: 'pricing' }">Pricing</RouterLink>
<RouterLink :to="{ name: 'how-it-works' }">How it works</RouterLink>
<RouterLink :to="{ name: 'faq' }">FAQ</RouterLink>
<RouterLink :to="{ name: 'roadmap' }">Roadmap</RouterLink>
</div>
<div class="flex items-center gap-3">
<a :href="panelUrl + '/login'" class="text-sm text-neutral-400 hover:text-neutral-100 transition-colors">Sign In</a>
<a :href="panelUrl + '/register'" class="px-4 py-2 bg-oxide-600 hover:bg-oxide-700 text-white text-sm font-medium rounded-lg transition-colors">Get Started</a>
<div class="mkt-nav__cta">
<a class="mkt-nav__signin" :href="panelUrl + '/login'">Sign in</a>
<RouterLink class="btn btn--primary btn--sm" :to="{ name: 'early-access' }">
Early access
</RouterLink>
</div>
</div>
</nav>
<!-- Page content -->
<main class="flex-1">
<RouterView />
</main>
<RouterView />
<!-- Footer -->
<footer class="border-t border-neutral-800 py-12">
<div class="max-w-6xl mx-auto px-6">
<div class="grid grid-cols-2 md:grid-cols-4 gap-8 mb-8">
<div>
<h4 class="text-sm font-semibold text-neutral-300 mb-3">Product</h4>
<div class="space-y-2">
<RouterLink :to="{ name: 'how-it-works' }" class="block text-sm text-neutral-500 hover:text-neutral-300 transition-colors">How It Works</RouterLink>
<RouterLink :to="{ name: 'pricing' }" class="block text-sm text-neutral-500 hover:text-neutral-300 transition-colors">Pricing</RouterLink>
<RouterLink :to="{ name: 'roadmap' }" class="block text-sm text-neutral-500 hover:text-neutral-300 transition-colors">Roadmap</RouterLink>
</div>
<footer class="mkt-footer">
<div class="wrap">
<div class="footer__cols">
<div class="footer__brand">
<RouterLink :to="{ name: 'landing' }" class="brand">
<span class="mark"><CorrosionMark :size="24" /></span>
<b>Corrosion</b>
</RouterLink>
<p>Game server operations for self-hosted communities.</p>
</div>
<div>
<h4 class="text-sm font-semibold text-neutral-300 mb-3">Support</h4>
<div class="space-y-2">
<RouterLink :to="{ name: 'faq' }" class="block text-sm text-neutral-500 hover:text-neutral-300 transition-colors">FAQ</RouterLink>
<a href="https://discord.gg/corrosion" target="_blank" class="block text-sm text-neutral-500 hover:text-neutral-300 transition-colors">Discord</a>
</div>
<div class="footer__col">
<h5>Product</h5>
<RouterLink :to="{ name: 'landing' }">Supported games</RouterLink>
<RouterLink :to="{ name: 'landing' }">Features</RouterLink>
<RouterLink :to="{ name: 'pricing' }">Pricing</RouterLink>
<RouterLink :to="{ name: 'roadmap' }">Roadmap</RouterLink>
</div>
<div>
<h4 class="text-sm font-semibold text-neutral-300 mb-3">Company</h4>
<div class="space-y-2">
<RouterLink :to="{ name: 'landing' }" class="block text-sm text-neutral-500 hover:text-neutral-300 transition-colors">About</RouterLink>
<RouterLink to="/status" class="block text-sm text-neutral-500 hover:text-neutral-300 transition-colors">Status</RouterLink>
</div>
<div class="footer__col">
<h5>Games</h5>
<RouterLink :to="{ name: 'landing' }">Rust</RouterLink>
<RouterLink :to="{ name: 'landing' }">Dune: Awakening</RouterLink>
<RouterLink :to="{ name: 'landing' }">Soulmask</RouterLink>
<RouterLink :to="{ name: 'landing' }">Conan Exiles</RouterLink>
</div>
<div>
<h4 class="text-sm font-semibold text-neutral-300 mb-3">Legal</h4>
<div class="space-y-2">
<span class="block text-sm text-neutral-600">Terms of Service</span>
<span class="block text-sm text-neutral-600">Privacy Policy</span>
</div>
<div class="footer__col">
<h5>Support</h5>
<RouterLink :to="{ name: 'faq' }">FAQ</RouterLink>
<a href="https://discord.gg/corrosion" target="_blank" rel="noopener">Discord</a>
<RouterLink to="/status">Status</RouterLink>
</div>
<div class="footer__col">
<h5>Company</h5>
<RouterLink :to="{ name: 'landing' }">About</RouterLink>
<RouterLink :to="{ name: 'roadmap' }">Changelog</RouterLink>
<a href="mailto:support@corrosionmgmt.com">Contact</a>
</div>
</div>
<div class="border-t border-neutral-800 pt-6 flex items-center justify-between">
<div class="flex items-center gap-2">
<img src="/logo.png" alt="Corrosion" class="h-4 w-4 opacity-60" />
<span class="text-sm text-neutral-600">&copy; 2026 Corrosion. All rights reserved.</span>
</div>
<span class="text-xs text-neutral-700">The Control Plane for Rust Servers.</span>
<div class="footer__bar">
<span>&copy; 2026 Corrosion. All rights reserved.</span>
<span>One control plane. Every game.</span>
</div>
</div>
</footer>

View File

@@ -0,0 +1,846 @@
/* ============================================================
Corrosion — Marketing site styles
Consumes the design-system tokens already loaded globally
via frontend/src/style.css (tokens/fonts → colors → etc.).
Class names match the design kit exactly.
============================================================ */
.wrap { max-width: 1140px; margin: 0 auto; padding: 0 32px; }
section { position: relative; }
.eyebrow {
font-family: var(--font-mono);
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: var(--tracking-caps);
text-transform: uppercase;
color: var(--accent-text);
}
h2.title {
font-family: var(--font-brand);
font-weight: 700;
font-size: var(--text-4xl);
letter-spacing: 0.01em;
text-align: center;
margin: 0;
line-height: 1.08;
}
.lead {
text-align: center;
color: var(--text-secondary);
font-size: var(--text-lg);
margin: 16px auto 0;
max-width: 660px;
line-height: 1.5;
}
.accent { color: var(--accent-text); }
.mark { display: inline-block; color: var(--accent); }
.mark svg { width: 100%; height: 100%; display: block; }
/* ---- Buttons ---- */
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
height: 46px;
padding: 0 22px;
border-radius: var(--radius-md);
font-family: var(--font-sans);
font-weight: 600;
font-size: var(--text-base);
cursor: pointer;
border: 1px solid transparent;
transition: var(--transition-colors), transform var(--dur-fast) var(--ease-standard);
text-decoration: none;
white-space: nowrap;
}
.btn:active { transform: translateY(1px); }
.btn--primary {
background: var(--accent);
color: var(--accent-contrast);
}
.btn--primary:hover { background: var(--accent-hover); }
.btn--ghost {
background: var(--surface-raised-2);
color: var(--text-primary);
box-shadow: var(--ring-default);
}
.btn--ghost:hover { background: var(--surface-active); }
.btn--sm { height: 36px; padding: 0 14px; font-size: var(--text-sm); }
.btn--lg { height: 52px; padding: 0 28px; font-size: var(--text-md); }
/* ---- Nav ---- */
.mkt-nav {
position: sticky;
top: 0;
z-index: 50;
height: var(--topbar-h);
display: flex;
align-items: center;
background: color-mix(in srgb, var(--surface-canvas) 84%, transparent);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-subtle);
}
.mkt-nav__in { display: flex; align-items: center; gap: 24px; width: 100%; }
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; }
.brand .mark { width: 26px; height: 26px; }
.brand b {
font-family: var(--font-brand);
font-weight: 800;
font-size: 18px;
letter-spacing: 0.01em;
color: var(--text-primary);
}
.mkt-nav__links { display: flex; gap: 24px; margin-left: 14px; }
.mkt-nav__links a {
color: var(--text-secondary);
font-size: var(--text-sm);
font-weight: 500;
transition: color var(--dur-fast);
text-decoration: none;
}
.mkt-nav__links a:hover { color: var(--text-primary); }
.mkt-nav__cta { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.mkt-nav__signin {
color: var(--text-secondary);
font-size: var(--text-sm);
font-weight: 500;
text-decoration: none;
transition: color var(--dur-fast);
}
.mkt-nav__signin:hover { color: var(--text-primary); }
/* ---- Hero ---- */
.hero { overflow: hidden; border-bottom: 1px solid var(--border-subtle); }
.hero__atmo {
position: absolute;
inset: 0;
z-index: 0;
transition: background var(--dur-slower) var(--ease-standard);
background:
radial-gradient(120% 80% at 50% -10%, var(--atmo-haze), transparent 55%),
radial-gradient(70% 50% at 85% 110%, color-mix(in srgb, var(--accent) 9%, transparent), transparent 60%),
linear-gradient(180deg, var(--atmo-1), var(--surface-canvas) 72%);
}
.hero__grain {
position: absolute;
inset: 0;
z-index: 0;
opacity: .5;
mix-blend-mode: overlay;
background-image: radial-gradient(rgba(255,255,255,.05) 1px, transparent 1px);
background-size: 3px 3px;
}
.hero__grid {
position: absolute;
inset: 0;
z-index: 0;
opacity: .32;
-webkit-mask-image: radial-gradient(75% 60% at 50% 24%, #000, transparent 78%);
mask-image: radial-gradient(75% 60% at 50% 24%, #000, transparent 78%);
background-image:
linear-gradient(var(--border-subtle) 1px, transparent 1px),
linear-gradient(90deg, var(--border-subtle) 1px, transparent 1px);
background-size: 46px 46px;
}
.hero__in {
position: relative;
z-index: 1;
text-align: center;
padding: 74px 0 88px;
}
.hero__mark {
width: 72px;
height: 72px;
margin: 0 auto 22px;
color: var(--accent);
filter: drop-shadow(0 0 26px var(--accent-glow));
transition: color var(--dur-slow);
}
.hero h1 {
font-family: var(--font-brand);
font-weight: 800;
font-size: var(--text-6xl);
line-height: 1.04;
letter-spacing: 0.005em;
margin: 0;
}
.hero h1 .accent { display: block; }
.hero__sub {
color: var(--text-secondary);
font-size: var(--text-lg);
margin: 22px auto 0;
max-width: 640px;
line-height: 1.55;
}
.hero__cta {
display: flex;
gap: 14px;
justify-content: center;
margin-top: 30px;
}
.hero__games {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 34px;
flex-wrap: wrap;
}
.gpill {
display: inline-flex;
align-items: center;
gap: 8px;
height: 38px;
padding: 0 16px;
border-radius: var(--radius-pill);
background: var(--surface-raised);
box-shadow: var(--ring-default);
color: var(--text-secondary);
font-size: var(--text-sm);
font-weight: 600;
cursor: pointer;
border: none;
transition: var(--transition-colors);
}
.gpill[data-on="true"] {
color: var(--accent-text);
background: var(--accent-soft);
box-shadow: inset 0 0 0 1px var(--accent-border);
}
.hero__foot {
margin-top: 16px;
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: .04em;
color: var(--text-muted);
}
.notpill {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 18px;
height: 28px;
padding: 0 13px;
border-radius: var(--radius-pill);
background: var(--surface-raised);
box-shadow: var(--ring-default);
font-family: var(--font-mono);
font-size: var(--text-xs);
color: var(--text-secondary);
}
.notpill b { color: var(--accent-text); }
/* ---- Panel mockup ---- */
.mock {
position: relative;
z-index: 1;
max-width: 1000px;
margin: 54px auto 0;
border-radius: var(--radius-xl);
overflow: hidden;
background: var(--surface-base);
box-shadow: 0 50px 130px -34px rgba(0,0,0,.85), var(--ring-default);
}
.mock__bar {
height: 40px;
display: flex;
align-items: center;
gap: 14px;
padding: 0 14px;
background: var(--surface-raised);
border-bottom: 1px solid var(--border-subtle);
}
.mock__dots { display: flex; gap: 7px; }
.mock__dots span {
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--surface-active);
display: inline-block;
}
.mock__url {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-tertiary);
background: var(--surface-inset);
padding: 5px 12px;
border-radius: var(--radius-pill);
}
.mock__body {
display: grid;
grid-template-columns: 188px 1fr;
min-height: 316px;
text-align: left;
}
.mock__side {
border-right: 1px solid var(--border-subtle);
padding: 14px 12px;
display: flex;
flex-direction: column;
gap: 7px;
}
.mock__brand {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
padding: 0 4px;
}
.mock__brand .mark { width: 18px; height: 18px; }
.mock__brand b {
font-family: var(--font-brand);
font-weight: 800;
font-size: 13px;
}
.mock__gs {
display: flex;
gap: 4px;
padding: 4px;
background: var(--surface-inset);
border-radius: var(--radius-md);
box-shadow: var(--ring-default);
margin-bottom: 8px;
}
.mock__gs span {
flex: 1;
height: 24px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
}
.mock__gs .on {
background: var(--surface-raised-2);
box-shadow: var(--ring-default);
color: var(--accent);
}
.mock__nav {
display: flex;
align-items: center;
gap: 9px;
height: 28px;
padding: 0 9px;
border-radius: var(--radius-sm);
color: var(--text-tertiary);
font-size: 12px;
}
.mock__nav.on { background: var(--accent-soft); color: var(--accent-text); }
.mock__main { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.mock__kpis { display: grid; grid-template-columns: repeat(3,1fr); gap: 10px; }
.mock__kpi {
background: var(--surface-raised);
border-radius: var(--radius-md);
box-shadow: var(--ring-default);
padding: 10px 12px;
}
.mock__kpi .l { font-size: 10px; color: var(--text-tertiary); }
.mock__kpi .v {
font-family: var(--font-mono);
font-weight: 600;
font-size: 19px;
color: var(--text-primary);
margin-top: 3px;
}
.mock__kpi .v small { color: var(--text-muted); font-size: 12px; }
.mock__row {
display: flex;
align-items: center;
gap: 10px;
background: var(--surface-raised);
border-radius: var(--radius-md);
box-shadow: var(--ring-default);
padding: 9px 12px;
position: relative;
overflow: hidden;
}
.mock__row::before {
content: '';
position: absolute;
left: 0; top: 0; bottom: 0;
width: 3px;
background: var(--accent);
}
.mock__row .g {
width: 22px;
height: 22px;
flex: none;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
color: var(--accent);
background: var(--accent-soft);
}
.mock__row .nm { flex: 1; font-size: 12px; font-weight: 600; }
.mock__row .nm small {
display: block;
font-family: var(--font-mono);
font-weight: 400;
font-size: 10px;
color: var(--text-muted);
}
.mock__row .st {
font-family: var(--font-mono);
font-size: 10px;
color: var(--status-online);
display: inline-flex;
align-items: center;
gap: 5px;
}
.mock__row .st b {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--status-online);
display: inline-block;
}
/* ---- Section spacing ---- */
.sec { padding: 88px 0; border-bottom: 1px solid var(--border-subtle); }
.sec__head { text-align: center; margin-bottom: 48px; }
.sec__head .eyebrow { display: block; margin-bottom: 12px; }
/* ---- Problem cards ---- */
.pain {
display: grid;
grid-template-columns: repeat(4,1fr);
gap: 12px;
max-width: 1000px;
margin: 0 auto;
}
.pain__item {
display: flex;
align-items: center;
gap: 11px;
padding: 16px;
background: var(--surface-base);
border-radius: var(--radius-lg);
box-shadow: var(--ring-default);
font-size: var(--text-sm);
color: var(--text-primary);
}
.pain__x {
width: 24px;
height: 24px;
flex: none;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
background: var(--status-offline-soft);
color: var(--status-offline);
}
.closing {
text-align: center;
margin: 40px auto 0;
max-width: 720px;
font-size: var(--text-xl);
font-weight: 600;
line-height: 1.4;
}
/* ---- Steps ---- */
.steps {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 16px;
max-width: 900px;
margin: 0 auto;
}
.step {
padding: 28px 24px;
text-align: center;
background: var(--surface-base);
border-radius: var(--radius-xl);
box-shadow: var(--ring-default);
}
.step__n {
width: 38px;
height: 38px;
margin: 0 auto 16px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-mono);
font-weight: 700;
font-size: var(--text-lg);
color: var(--accent-text);
background: var(--accent-soft);
box-shadow: inset 0 0 0 1px var(--accent-border);
}
.step b { font-size: var(--text-md); font-weight: 600; }
.step p { color: var(--text-tertiary); font-size: var(--text-sm); margin: 8px 0 0; }
.nots {
display: flex;
gap: 26px;
justify-content: center;
margin-top: 34px;
flex-wrap: wrap;
}
.nots span {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--text-tertiary);
font-size: var(--text-sm);
font-family: var(--font-mono);
}
/* ---- Blueprints (game cards) ---- */
.blueprints {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
max-width: 1000px;
margin: 0 auto;
}
.bp {
position: relative;
overflow: hidden;
padding: 24px;
border-radius: var(--radius-xl);
background:
radial-gradient(120% 90% at 100% 0%, var(--atmo-haze), transparent 55%),
linear-gradient(160deg, color-mix(in srgb, var(--atmo-1) 80%, transparent), var(--surface-base) 70%);
box-shadow: inset 0 0 0 1px var(--accent-border);
}
.bp__head { display: flex; align-items: center; gap: 12px; margin-bottom: 4px; }
.bp__ic {
width: 40px;
height: 40px;
flex: none;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
color: var(--accent);
background: color-mix(in srgb, var(--accent) 16%, transparent);
box-shadow: inset 0 0 0 1px var(--accent-border);
}
.bp__name { font-family: var(--font-brand); font-weight: 700; font-size: var(--text-xl); }
.bp__accent {
font-family: var(--font-mono);
font-size: 11px;
color: var(--accent-text);
text-transform: uppercase;
letter-spacing: .08em;
}
.bp__role { font-size: var(--text-sm); font-weight: 600; color: var(--text-secondary); margin: 10px 0 14px; }
.bp__list { display: flex; flex-direction: column; gap: 8px; }
.bp__list div { display: flex; align-items: center; gap: 9px; font-size: var(--text-sm); color: var(--text-secondary); }
/* ---- Capabilities (3 col) ---- */
.caps {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 30px;
max-width: 1000px;
margin: 0 auto;
}
.caps__col > .eyebrow { display: block; margin-bottom: 8px; }
.feat { display: flex; gap: 12px; padding: 14px 0; border-top: 1px solid var(--border-subtle); }
.feat:first-of-type { border-top: 0; }
.feat__ic {
width: 32px;
height: 32px;
flex: none;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
color: var(--accent-text);
background: var(--accent-soft);
box-shadow: inset 0 0 0 1px var(--accent-border);
}
.feat b { font-size: var(--text-sm); font-weight: 600; }
/* ---- Pipeline ---- */
.pipe {
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
flex-wrap: wrap;
max-width: 1000px;
margin: 0 auto;
}
.pchip {
height: 38px;
padding: 0 15px;
display: inline-flex;
align-items: center;
border-radius: var(--radius-md);
background: var(--surface-raised-2);
box-shadow: var(--ring-default);
font-family: var(--font-mono);
font-size: var(--text-sm);
font-weight: 500;
color: var(--text-secondary);
}
.pchip--last {
background: var(--accent-soft);
color: var(--accent-text);
box-shadow: inset 0 0 0 1px var(--accent-border);
}
.stack-lines { display: flex; flex-direction: column; gap: 8px; align-items: center; margin-top: 32px; }
.stack-lines span { color: var(--text-tertiary); font-size: var(--text-md); }
.stack-lines .hi { color: var(--accent-text); font-weight: 600; }
/* ---- Infra ---- */
.infra {
display: grid;
grid-template-columns: repeat(5,1fr);
gap: 12px;
max-width: 1040px;
margin: 0 auto;
}
.icard {
padding: 20px 16px;
background: var(--surface-base);
border-radius: var(--radius-lg);
box-shadow: var(--ring-default);
}
.icard__ic {
width: 32px;
height: 32px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
color: var(--accent-text);
background: var(--accent-soft);
box-shadow: inset 0 0 0 1px var(--accent-border);
margin-bottom: 12px;
}
.icard b { font-size: var(--text-sm); font-weight: 600; display: block; }
.icard p { margin: 5px 0 0; color: var(--text-tertiary); font-size: var(--text-xs); line-height: 1.5; }
.techrow {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 30px;
flex-wrap: wrap;
}
.techrow span {
font-family: var(--font-mono);
font-size: var(--text-xs);
color: var(--text-muted);
padding: 6px 12px;
border-radius: var(--radius-pill);
box-shadow: var(--ring-default);
}
/* ---- Store ---- */
.chips {
display: flex;
gap: 12px;
justify-content: center;
flex-wrap: wrap;
max-width: 880px;
margin: 0 auto;
}
.chip-card {
display: flex;
align-items: center;
gap: 9px;
padding: 14px 18px;
background: var(--surface-base);
border-radius: var(--radius-lg);
box-shadow: var(--ring-default);
font-weight: 600;
font-size: var(--text-sm);
}
.chip-card--accent {
color: var(--accent-text);
box-shadow: inset 0 0 0 1px var(--accent-border);
background: var(--accent-soft);
}
/* ---- Pricing ---- */
.pricing {
display: grid;
grid-template-columns: repeat(4,1fr);
gap: 14px;
max-width: 1040px;
margin: 0 auto;
align-items: stretch;
}
.plan {
display: flex;
flex-direction: column;
padding: 24px 22px;
background: var(--surface-base);
border-radius: var(--radius-xl);
box-shadow: var(--ring-default);
}
.plan--feature {
box-shadow: inset 0 0 0 1px var(--accent-border), var(--glow-accent-sm);
background: linear-gradient(180deg, var(--accent-soft), var(--surface-base) 40%);
}
.plan__tag {
font-family: var(--font-mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--accent-text);
margin-bottom: 10px;
height: 14px;
}
.plan__name { font-family: var(--font-brand); font-weight: 700; font-size: var(--text-xl); }
.plan__price {
font-family: var(--font-mono);
font-weight: 600;
font-size: var(--text-3xl);
margin: 12px 0 2px;
letter-spacing: -0.02em;
}
.plan__price small { font-size: var(--text-sm); color: var(--text-muted); font-weight: 400; }
.plan__scope { font-size: var(--text-sm); color: var(--text-tertiary); min-height: 40px; }
.plan .btn { margin-top: 18px; width: 100%; justify-content: center; }
.fleetblock {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
max-width: 1040px;
margin: 14px auto 0;
padding: 16px 22px;
background: var(--surface-base);
border-radius: var(--radius-lg);
box-shadow: var(--ring-default);
flex-wrap: wrap;
}
.fleetblock b { font-family: var(--font-brand); font-weight: 700; }
.fleetblock .p { font-family: var(--font-mono); color: var(--accent-text); font-weight: 600; }
.fleetblock span { color: var(--text-tertiary); font-size: var(--text-sm); }
.commercial {
max-width: 760px;
margin: 26px auto 0;
text-align: center;
color: var(--text-muted);
font-size: var(--text-xs);
line-height: 1.6;
}
.commercial b { color: var(--text-secondary); }
/* ---- Support block (below pricing) ---- */
.support-note {
max-width: 760px;
margin: 20px auto 0;
text-align: center;
color: var(--text-muted);
font-size: var(--text-xs);
line-height: 1.6;
}
.support-note b { color: var(--text-secondary); }
/* ---- Admins ---- */
.admins {
display: flex;
flex-direction: column;
gap: 11px;
align-items: center;
max-width: 560px;
margin: 0 auto;
}
.admins span {
display: flex;
align-items: center;
gap: 11px;
font-size: var(--text-lg);
color: var(--text-secondary);
}
/* ---- Final CTA ---- */
.finalcta {
position: relative;
overflow: hidden;
text-align: center;
padding: 104px 0;
border-bottom: 1px solid var(--border-subtle);
}
.finalcta__atmo {
position: absolute;
inset: 0;
z-index: 0;
background: radial-gradient(60% 100% at 50% 100%, var(--atmo-haze), transparent 60%);
}
.finalcta__in { position: relative; z-index: 1; }
.finalcta h2 {
font-family: var(--font-brand);
font-weight: 800;
font-size: var(--text-5xl);
margin: 0 0 28px;
line-height: 1.05;
}
.finalcta .cta-row { display: flex; gap: 14px; justify-content: center; }
/* ---- Footer ---- */
.mkt-footer { padding: 56px 0 40px; }
.footer__cols { display: grid; grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr; gap: 24px; }
.footer__brand .mark { width: 24px; height: 24px; }
.footer__brand p {
color: var(--text-tertiary);
font-size: var(--text-sm);
margin: 12px 0 0;
max-width: 230px;
}
.footer__col h5 {
font-size: var(--text-xs);
text-transform: uppercase;
letter-spacing: var(--tracking-wider);
color: var(--text-muted);
margin: 0 0 14px;
font-family: var(--font-mono);
}
.footer__col a {
display: block;
color: var(--text-secondary);
font-size: var(--text-sm);
margin-bottom: 9px;
text-decoration: none;
transition: color var(--dur-fast);
}
.footer__col a:hover { color: var(--text-primary); }
.footer__bar {
display: flex;
justify-content: space-between;
margin-top: 44px;
padding-top: 22px;
border-top: 1px solid var(--border-subtle);
color: var(--text-muted);
font-size: var(--text-xs);
}
/* ---- Scroll reveal ---- */
@media (prefers-reduced-motion: no-preference) {
.reveal {
opacity: 0;
transform: translateY(14px);
transition: opacity .6s var(--ease-out), transform .6s var(--ease-out);
}
.reveal.in { opacity: 1; transform: none; }
}
/* ---- Responsive ---- */
@media (max-width: 980px) {
.pain { grid-template-columns: 1fr 1fr; }
.steps, .caps, .blueprints, .pricing { grid-template-columns: 1fr; }
.infra { grid-template-columns: 1fr 1fr; }
.footer__cols { grid-template-columns: 1fr 1fr; }
.mock__body { grid-template-columns: 1fr; }
.mock__side { display: none; }
.hero h1 { font-size: var(--text-5xl); }
.mkt-nav__links { display: none; }
}

View File

@@ -1,288 +1,680 @@
<script setup lang="ts">
import { Shield, Zap, RefreshCw, Terminal, Users, Wifi, Server, ChevronRight } from 'lucide-vue-next'
import { ref, onMounted, onUnmounted } from 'vue'
import { RouterLink } from 'vue-router'
import CorrosionMark from '@/components/brand/CorrosionMark.vue'
import Icon from '@/components/ds/core/Icon.vue'
import { useThemeGame, type Game } from '@/composables/useThemeGame'
const panelUrl = import.meta.env.VITE_PANEL_URL || ''
const { setGame } = useThemeGame()
const panelUrl = import.meta.env.VITE_PANEL_URL ?? ''
// ---- Game pill data ----
interface GameDef {
key: Game
label: string
icon: string
}
const GAMES: GameDef[] = [
{ key: 'rust', label: 'Rust', icon: 'box' },
{ key: 'dune', label: 'Dune: Awakening', icon: 'sun' },
{ key: 'soulmask', label: 'Soulmask', icon: 'drama' },
{ key: 'conan', label: 'Conan Exiles', icon: 'swords' },
]
const activeGame = ref<Game>('rust')
const userPicked = ref(false)
let rotateTimer: ReturnType<typeof setInterval> | null = null
let idx = 0
function pickGame(g: Game): void {
userPicked.value = true
activeGame.value = g
setGame(g)
}
function heroIsVisible(): boolean {
const hero = document.querySelector('.hero')
if (!hero) return false
return hero.getBoundingClientRect().bottom >= 140
}
function startRotation(): void {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
rotateTimer = setInterval(() => {
if (userPicked.value || !heroIsVisible()) return
idx = (idx + 1) % GAMES.length
const next = GAMES[idx]
if (next) {
activeGame.value = next.key
setGame(next.key)
}
}, 3400)
}
// ---- Scroll-reveal via IntersectionObserver ----
let io: IntersectionObserver | null = null
function initReveal(): void {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
io = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add('in')
io?.unobserve(e.target)
}
})
},
{ threshold: 0.1 },
)
document.querySelectorAll('.reveal').forEach((el) => io?.observe(el))
}
onMounted(() => {
// Set initial game on html — stays in sync with the global composable
setGame('rust')
activeGame.value = 'rust'
startRotation()
initReveal()
})
onUnmounted(() => {
if (rotateTimer !== null) clearInterval(rotateTimer)
io?.disconnect()
})
// Mock sidebar game switcher active key mirrors activeGame
const mockActiveGame = activeGame
</script>
<template>
<div>
<!-- Hero -->
<section class="relative overflow-hidden">
<div class="max-w-6xl mx-auto px-6 pt-20 pb-24 text-center">
<h1 class="text-5xl md:text-6xl font-bold text-neutral-100 mb-6 tracking-tight">
The Control Plane<br />
<span class="text-oxide-500">for Rust Servers</span>
</h1>
<p class="text-xl text-neutral-400 max-w-2xl mx-auto mb-10">
Deploy once. Automate everything. Never SSH again.
</p>
<div class="flex items-center justify-center gap-4">
<a :href="panelUrl + '/register'" class="px-8 py-3.5 bg-oxide-600 hover:bg-oxide-700 text-white font-semibold rounded-lg transition-colors text-lg">
Buy License
</a>
<a href="#demo" class="px-8 py-3.5 bg-neutral-800 hover:bg-neutral-700 text-neutral-200 font-semibold rounded-lg border border-neutral-700 transition-colors text-lg">
View Live Demo
</a>
</div>
<!-- HERO -->
<section class="hero">
<div class="hero__atmo" />
<div class="hero__grid" />
<div class="hero__grain" />
<div class="wrap hero__in">
<div class="hero__mark">
<CorrosionMark :size="72" />
</div>
<!-- Gradient glow -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[400px] bg-oxide-500/10 rounded-full blur-3xl pointer-events-none" />
</section>
<!-- The Problem -->
<section class="py-20 border-t border-neutral-800">
<div class="max-w-4xl mx-auto px-6">
<h2 class="text-3xl font-bold text-neutral-100 mb-8 text-center">The Problem</h2>
<p class="text-neutral-400 text-lg mb-8 text-center">Running a Rust server today means:</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-2xl mx-auto mb-10">
<div class="flex items-start gap-3 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<span class="text-red-400 mt-0.5">&#x2717;</span>
<span class="text-neutral-300">Editing JSON configs over SFTP</span>
</div>
<div class="flex items-start gap-3 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<span class="text-red-400 mt-0.5">&#x2717;</span>
<span class="text-neutral-300">Babysitting wipes</span>
</div>
<div class="flex items-start gap-3 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<span class="text-red-400 mt-0.5">&#x2717;</span>
<span class="text-neutral-300">Manually installing and updating plugins</span>
</div>
<div class="flex items-start gap-3 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<span class="text-red-400 mt-0.5">&#x2717;</span>
<span class="text-neutral-300">Restarting servers blindly</span>
</div>
<div class="flex items-start gap-3 p-4 bg-neutral-900 border border-neutral-800 rounded-lg md:col-span-2 md:max-w-sm md:mx-auto">
<span class="text-red-400 mt-0.5">&#x2717;</span>
<span class="text-neutral-300">Staying online when something crashes</span>
</div>
</div>
<p class="text-center text-lg text-neutral-300 font-medium">
Rust servers deserve <span class="text-oxide-400">orchestration</span> not babysitting.
</p>
<div class="notpill">
<b>Not hosting.</b>&nbsp;Not a generic panel. Self-hosted, agent-based.
</div>
</section>
<!-- The Shift -->
<section class="py-20 bg-neutral-900/50 border-t border-neutral-800">
<div class="max-w-4xl mx-auto px-6 text-center">
<h2 class="text-3xl font-bold text-neutral-100 mb-8">The Shift</h2>
<p class="text-lg text-neutral-400 mb-10">
Corrosion moves Rust server administration to a unified cloud control plane.
</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
<div class="p-6 bg-neutral-900 border border-neutral-800 rounded-lg">
<div class="w-10 h-10 bg-oxide-500/10 rounded-lg flex items-center justify-center mx-auto mb-4">
<span class="text-oxide-400 text-xl font-bold">1</span>
</div>
<p class="text-neutral-200 font-medium">Install one plugin</p>
</div>
<div class="p-6 bg-neutral-900 border border-neutral-800 rounded-lg">
<div class="w-10 h-10 bg-oxide-500/10 rounded-lg flex items-center justify-center mx-auto mb-4">
<span class="text-oxide-400 text-xl font-bold">2</span>
</div>
<p class="text-neutral-200 font-medium">Register online</p>
</div>
<div class="p-6 bg-neutral-900 border border-neutral-800 rounded-lg">
<div class="w-10 h-10 bg-oxide-500/10 rounded-lg flex items-center justify-center mx-auto mb-4">
<span class="text-oxide-400 text-xl font-bold">3</span>
</div>
<p class="text-neutral-200 font-medium">Manage everything from your browser</p>
</div>
</div>
<div class="flex flex-col md:flex-row items-center justify-center gap-4 text-neutral-500">
<span>No open firewall ports.</span>
<span class="hidden md:inline">&middot;</span>
<span>No manual file editing.</span>
<span class="hidden md:inline">&middot;</span>
<span>No SSH required.</span>
</div>
</div>
</section>
<!-- Core Capabilities -->
<section class="py-20 border-t border-neutral-800">
<div class="max-w-6xl mx-auto px-6">
<h2 class="text-3xl font-bold text-neutral-100 mb-12 text-center">Core Capabilities</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Operational Control -->
<div>
<h3 class="text-lg font-semibold text-oxide-400 mb-6 uppercase tracking-wider">Operational Control</h3>
<div class="space-y-4">
<div class="flex items-start gap-4 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<RefreshCw class="w-5 h-5 text-oxide-500 mt-0.5 shrink-0" />
<div>
<p class="text-neutral-200 font-medium">Auto-Wiper with Rollback</p>
<p class="text-sm text-neutral-500 mt-1">Full wipe sequences with health verification</p>
</div>
</div>
<div class="flex items-start gap-4 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<Terminal class="w-5 h-5 text-oxide-500 mt-0.5 shrink-0" />
<div>
<p class="text-neutral-200 font-medium">Real-Time Console + Player Control</p>
<p class="text-sm text-neutral-500 mt-1">Execute commands from your browser</p>
</div>
</div>
<div class="flex items-start gap-4 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<Zap class="w-5 h-5 text-oxide-500 mt-0.5 shrink-0" />
<div>
<p class="text-neutral-200 font-medium">Web-Based Plugin Configuration</p>
<p class="text-sm text-neutral-500 mt-1">No more JSON editing over SFTP</p>
</div>
</div>
<div class="flex items-start gap-4 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<Server class="w-5 h-5 text-oxide-500 mt-0.5 shrink-0" />
<div>
<p class="text-neutral-200 font-medium">Automated Steam Updates</p>
<p class="text-sm text-neutral-500 mt-1">Stay current without manual intervention</p>
</div>
</div>
</div>
</div>
<!-- Infrastructure & Scale -->
<div>
<h3 class="text-lg font-semibold text-oxide-400 mb-6 uppercase tracking-wider">Infrastructure & Scale</h3>
<div class="space-y-4">
<div class="flex items-start gap-4 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<Shield class="w-5 h-5 text-oxide-500 mt-0.5 shrink-0" />
<div>
<p class="text-neutral-200 font-medium">Companion Agent No SSH Required</p>
<p class="text-sm text-neutral-500 mt-1">Outbound-only secure connections</p>
</div>
</div>
<div class="flex items-start gap-4 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<Users class="w-5 h-5 text-oxide-500 mt-0.5 shrink-0" />
<div>
<p class="text-neutral-200 font-medium">Multi-Admin Role-Based Access Control</p>
<p class="text-sm text-neutral-500 mt-1">Scale your team without losing order</p>
</div>
</div>
<div class="flex items-start gap-4 p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<Wifi class="w-5 h-5 text-oxide-500 mt-0.5 shrink-0" />
<div>
<p class="text-neutral-200 font-medium">Zero Inbound Ports Required</p>
<p class="text-sm text-neutral-500 mt-1">Your server initiates all connections</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Wipe Orchestration -->
<section class="py-20 bg-neutral-900/50 border-t border-neutral-800">
<div class="max-w-4xl mx-auto px-6">
<h2 class="text-3xl font-bold text-neutral-100 mb-4 text-center">Wipe Orchestration</h2>
<p class="text-lg text-neutral-400 mb-10 text-center">
Wipes aren't just "delete map and restart."
</p>
<div class="flex flex-wrap items-center justify-center gap-3 mb-10">
<span class="px-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-300">Pre-Wipe</span>
<ChevronRight class="w-4 h-4 text-neutral-600" />
<span class="px-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-300">Backup</span>
<ChevronRight class="w-4 h-4 text-neutral-600" />
<span class="px-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-300">Map Rotation</span>
<ChevronRight class="w-4 h-4 text-neutral-600" />
<span class="px-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-300">Steam Update</span>
<ChevronRight class="w-4 h-4 text-neutral-600" />
<span class="px-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-300">Restart</span>
<ChevronRight class="w-4 h-4 text-neutral-600" />
<span class="px-4 py-2 bg-neutral-800 border border-neutral-700 rounded-lg text-sm text-neutral-300">Health Check</span>
<ChevronRight class="w-4 h-4 text-neutral-600" />
<span class="px-4 py-2 bg-oxide-500/15 border border-oxide-500/30 rounded-lg text-sm text-oxide-400">Rollback</span>
</div>
<div class="flex flex-col md:flex-row items-center justify-center gap-6 text-neutral-400">
<span>Every wipe is logged.</span>
<span>Every step is verified.</span>
<span class="text-oxide-400 font-medium">Rollback is one click away.</span>
</div>
</div>
</section>
<!-- Built Like Infrastructure -->
<section class="py-20 border-t border-neutral-800">
<div class="max-w-4xl mx-auto px-6 text-center">
<h2 class="text-3xl font-bold text-neutral-100 mb-4">Built Like Infrastructure</h2>
<p class="text-lg text-neutral-400 mb-10">
Corrosion isn't a UI wrapper. It's a hosted SaaS platform built with:
</p>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-10">
<div class="p-5 bg-neutral-900 border border-neutral-800 rounded-lg">
<p class="text-oxide-400 font-semibold mb-1">NestJS</p>
<p class="text-xs text-neutral-500">TypeScript backend</p>
</div>
<div class="p-5 bg-neutral-900 border border-neutral-800 rounded-lg">
<p class="text-oxide-400 font-semibold mb-1">NATS</p>
<p class="text-xs text-neutral-500">JetStream messaging</p>
</div>
<div class="p-5 bg-neutral-900 border border-neutral-800 rounded-lg">
<p class="text-oxide-400 font-semibold mb-1">PostgreSQL</p>
<p class="text-xs text-neutral-500">Multi-tenant isolation</p>
</div>
<div class="p-5 bg-neutral-900 border border-neutral-800 rounded-lg">
<p class="text-oxide-400 font-semibold mb-1">Outbound-Only</p>
<p class="text-xs text-neutral-500">Secure connections</p>
</div>
</div>
<div class="flex flex-col items-center gap-2 text-neutral-500 text-sm">
<span>Every server is scoped by license.</span>
<span>Every command is namespaced.</span>
<span>Every tenant is isolated.</span>
</div>
</div>
</section>
<!-- Public Server Sites -->
<section class="py-20 bg-neutral-900/50 border-t border-neutral-800">
<div class="max-w-4xl mx-auto px-6 text-center">
<h2 class="text-3xl font-bold text-neutral-100 mb-4">Public Server Sites & Storefront</h2>
<p class="text-lg text-neutral-400 mb-10">Each license includes:</p>
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
<div class="p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<p class="text-sm text-neutral-200">Public server page</p>
</div>
<div class="p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<p class="text-sm text-neutral-200">Wipe countdown</p>
</div>
<div class="p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<p class="text-sm text-neutral-200">Live player count</p>
</div>
<div class="p-4 bg-neutral-900 border border-neutral-800 rounded-lg">
<p class="text-sm text-neutral-200">Plugin/mod list</p>
</div>
<div class="p-4 bg-neutral-900 border border-oxide-500/30 rounded-lg">
<p class="text-sm text-oxide-400">Integrated webstore</p>
</div>
</div>
<p class="text-neutral-400">Monetize your server without third-party complexity.</p>
</div>
</section>
<!-- For Serious Admins -->
<section class="py-20 border-t border-neutral-800">
<div class="max-w-3xl mx-auto px-6 text-center">
<h2 class="text-3xl font-bold text-neutral-100 mb-8">For Serious Admins</h2>
<p class="text-lg text-neutral-400 mb-8">If you:</p>
<div class="space-y-3 max-w-md mx-auto mb-10">
<p class="text-neutral-300 text-lg">Run scheduled wipes</p>
<p class="text-neutral-300 text-lg">Care about uptime</p>
<p class="text-neutral-300 text-lg">Want crash recovery</p>
<p class="text-neutral-300 text-lg">Want automation</p>
<p class="text-neutral-300 text-lg">Manage multiple admins</p>
</div>
<p class="text-xl text-oxide-400 font-semibold">Corrosion was built for you.</p>
</div>
</section>
<!-- CTA -->
<section class="py-24 bg-neutral-900/50 border-t border-neutral-800">
<div class="max-w-3xl mx-auto px-6 text-center">
<p class="text-2xl text-neutral-400 mb-2">Stop babysitting your server.</p>
<p class="text-3xl font-bold text-neutral-100 mb-10">Start orchestrating it.</p>
<a :href="panelUrl + '/register'" class="inline-block px-10 py-4 bg-oxide-600 hover:bg-oxide-700 text-white font-semibold rounded-lg transition-colors text-lg">
Get Corrosion
<h1>Run your game servers<span class="accent">like an operation.</span></h1>
<p class="hero__sub">
Corrosion is a management panel for self-hosted survival game servers. Deploy servers, automate
wipes, manage plugins and mods, schedule maintenance, monitor players, and orchestrate
multi-server worlds from one command center.
</p>
<div class="hero__cta">
<RouterLink class="btn btn--primary btn--lg" :to="{ name: 'early-access' }">
Join early access
</RouterLink>
<a class="btn btn--ghost btn--lg" :href="panelUrl + '/login'">
<Icon name="play" :size="17" />View live demo
</a>
</div>
</section>
</div>
<!-- Game pills -->
<div class="hero__games">
<button
v-for="g in GAMES"
:key="g.key"
class="gpill"
:data-on="String(activeGame === g.key)"
@click="pickGame(g.key)"
>
<Icon :name="g.icon" :size="15" />
<span>{{ g.label }}</span>
</button>
</div>
</div>
<!-- Panel mockup -->
<div class="wrap">
<div class="mock">
<div class="mock__bar">
<div class="mock__dots">
<span /><span /><span />
</div>
<div class="mock__url">panel.corrosionmgmt.com / fleet</div>
</div>
<div class="mock__body">
<aside class="mock__side">
<div class="mock__brand">
<span class="mark"><CorrosionMark :size="18" /></span>
<b>Corrosion</b>
</div>
<div class="mock__gs">
<span :class="{ on: mockActiveGame === 'rust' }">
<Icon name="box" :size="13" />
</span>
<span :class="{ on: mockActiveGame === 'dune' }">
<Icon name="sun" :size="13" />
</span>
<span :class="{ on: mockActiveGame === 'soulmask' }">
<Icon name="drama" :size="13" />
</span>
<span :class="{ on: mockActiveGame === 'conan' }">
<Icon name="swords" :size="13" />
</span>
</div>
<div class="mock__nav on"><Icon name="layout-dashboard" :size="14" />Dashboard</div>
<div class="mock__nav"><Icon name="server" :size="14" />Servers</div>
<div class="mock__nav"><Icon name="terminal" :size="14" />Console</div>
<div class="mock__nav"><Icon name="trash-2" :size="14" />Wipes</div>
<div class="mock__nav"><Icon name="cpu" :size="14" />Agents</div>
</aside>
<main class="mock__main">
<div class="mock__kpis">
<div class="mock__kpi">
<div class="l">Servers running</div>
<div class="v">5<small>/6</small></div>
</div>
<div class="mock__kpi">
<div class="l">Players online</div>
<div class="v">234</div>
</div>
<div class="mock__kpi">
<div class="l">Agent nodes</div>
<div class="v">2<small>/2</small></div>
</div>
</div>
<div class="mock__row">
<span class="g"><Icon name="box" :size="13" /></span>
<span class="nm">
Main · 2x Vanilla
<small>asgard-01 · rust</small>
</span>
<span class="st"><b />online</span>
</div>
<div class="mock__row">
<span class="g"><Icon name="sun" :size="13" /></span>
<span class="nm">
Arrakis · Hardcore
<small>asgard-01 · dune</small>
</span>
<span class="st"><b />online</span>
</div>
<div class="mock__row">
<span class="g"><Icon name="swords" :size="13" /></span>
<span class="nm">
Exiled Lands · PvP-C
<small>asgard-02 · conan</small>
</span>
<span class="st"><b />online</span>
</div>
</main>
</div>
</div>
</div>
<div class="wrap" style="text-align:center">
<div class="hero__foot">
One host agent, many game instances · Rust · Dune: Awakening · Soulmask · Conan Exiles ·
Windows &amp; Linux hosts
</div>
</div>
<div style="height:80px" />
</section>
<!-- PROBLEM -->
<section class="sec" id="problem">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">The problem</span>
<h2 class="title">Game servers were never supposed<br>to be babysitting duty</h2>
</div>
<div class="pain reveal">
<div class="pain__item">
<span class="pain__x"><Icon name="x" :size="14" /></span>
Editing configs over SFTP
</div>
<div class="pain__item">
<span class="pain__x"><Icon name="x" :size="14" /></span>
Manually updating mods &amp; plugins
</div>
<div class="pain__item">
<span class="pain__x"><Icon name="x" :size="14" /></span>
Guessing when a server crashed
</div>
<div class="pain__item">
<span class="pain__x"><Icon name="x" :size="14" /></span>
Running wipe day by hand
</div>
<div class="pain__item">
<span class="pain__x"><Icon name="x" :size="14" /></span>
Juggling Discord bots &amp; cron tasks
</div>
<div class="pain__item">
<span class="pain__x"><Icon name="x" :size="14" /></span>
Linking multi-server clusters manually
</div>
<div class="pain__item">
<span class="pain__x"><Icon name="x" :size="14" /></span>
Managing admins without real permissions
</div>
<div class="pain__item">
<span class="pain__x"><Icon name="x" :size="14" /></span>
Explaining downtime to players
</div>
</div>
<p class="closing reveal">
Your community sees the server. You deal with the chaos.<br>
<span class="accent">Corrosion gives you the control plane.</span>
</p>
</div>
</section>
<!-- SHIFT -->
<section class="sec" id="shift">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">The shift</span>
<h2 class="title">Drop in the agent.<br>Take control from the panel.</h2>
<p class="lead">
One lightweight host agent runs on your machine and manages every game instance you assign
to it an outbound-only ops runtime, not an exposed panel.
</p>
</div>
<div class="steps reveal">
<div class="step">
<div class="step__n">1</div>
<b>Install the Corrosion Agent</b>
<p>One runtime on your Windows or Linux host. Outbound connection only.</p>
</div>
<div class="step">
<div class="step__n">2</div>
<b>Register your server or fleet</b>
<p>Connect one server, a cluster, or multiple game worlds on the same box.</p>
</div>
<div class="step">
<div class="step__n">3</div>
<b>Manage from the browser</b>
<p>Console, files, schedules, wipes, plugins, mods, players, backups, metrics.</p>
</div>
</div>
<div class="nots reveal">
<span><Icon name="check" :size="16" style="color:var(--status-online)" />No open inbound ports</span>
<span><Icon name="check" :size="16" style="color:var(--status-online)" />No constant SSH sessions</span>
<span><Icon name="check" :size="16" style="color:var(--status-online)" />No config spelunking</span>
<span><Icon name="check" :size="16" style="color:var(--status-online)" />No fragile scripts</span>
</div>
<p class="closing reveal" style="font-size:var(--text-lg)">
You provide the machine.
<span class="accent">Corrosion provides the control plane.</span>
</p>
</div>
</section>
<!-- BLUEPRINTS / SUPPORTED GAMES -->
<section class="sec" id="blueprints">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">Supported games</span>
<h2 class="title">Game-aware blueprints,<br>not generic templates</h2>
<p class="lead">
Every game has a different operational reality. Corrosion models each one as an operations
blueprint Rust wipes, Dune: Awakening battlegroups, Soulmask clusters, Conan persistent
worlds.
</p>
</div>
<div class="blueprints reveal">
<!-- Rust card sets its own game scope via inline style on the surrounding element.
The token system resolves var(--accent) from data-game on <html> (set globally by
useThemeGame). Cards carry a data-game attr for future per-card scoping if desired. -->
<div class="bp" data-game="rust">
<div class="bp__head">
<span class="bp__ic"><Icon name="box" :size="21" /></span>
<div>
<div class="bp__name">Rust</div>
<div class="bp__accent">Oxide Orange</div>
</div>
</div>
<div class="bp__role">Modded server operations</div>
<div class="bp__list">
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />uMod / Oxide plugin browsing</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Plugin config profiles</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Map / blueprint / full wipes</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Wipe schedules &amp; map library</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Player &amp; admin workflows</div>
</div>
</div>
<div class="bp" data-game="dune">
<div class="bp__head">
<span class="bp__ic"><Icon name="sun" :size="21" /></span>
<div>
<div class="bp__name">Dune: Awakening</div>
<div class="bp__accent">Spice Amber</div>
</div>
</div>
<div class="bp__role">Battlegroup orchestration</div>
<div class="bp__list">
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Deep Desert wipe scheduling</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Procedural map regeneration</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Service health checks</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Backups before maintenance</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Battlegroup lifecycle controls</div>
</div>
</div>
<div class="bp" data-game="soulmask">
<div class="bp__head">
<span class="bp__ic"><Icon name="drama" :size="21" /></span>
<div>
<div class="bp__name">Soulmask</div>
<div class="bp__accent">Ritual Jade</div>
</div>
</div>
<div class="bp__role">Linked-world cluster deployment</div>
<div class="bp__list">
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />One-click linked-server clusters</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Linked map validation</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Port &amp; config automation</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Cluster health</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Scheduled maintenance</div>
</div>
</div>
<div class="bp" data-game="conan">
<div class="bp__head">
<span class="bp__ic"><Icon name="swords" :size="21" /></span>
<div>
<div class="bp__name">Conan Exiles</div>
<div class="bp__accent">Hyborian Bronze</div>
</div>
</div>
<div class="bp__role">Persistent world management</div>
<div class="bp__list">
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Mod &amp; server management</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Clan &amp; player visibility</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Purge, decay &amp; event tracking</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />Backup &amp; restart scheduling</div>
<div><Icon name="check" :size="15" style="color:var(--accent-text);flex:none" />World maintenance workflows</div>
</div>
</div>
</div>
</div>
</section>
<!-- CAPABILITIES -->
<section class="sec" id="caps">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">Core capabilities</span>
<h2 class="title">Everything an operator needs</h2>
</div>
<div class="caps reveal">
<div class="caps__col">
<span class="eyebrow">Operations</span>
<div class="feat">
<span class="feat__ic"><Icon name="power" :size="16" /></span>
<b>Server lifecycle control</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="terminal" :size="16" /></span>
<b>Real-time console</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="users" :size="16" /></span>
<b>Player visibility</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="folder-open" :size="16" /></span>
<b>File manager</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="calendar-clock" :size="16" /></span>
<b>Scheduled tasks &amp; restart windows</b>
</div>
</div>
<div class="caps__col">
<span class="eyebrow">Automation</span>
<div class="feat">
<span class="feat__ic"><Icon name="refresh-cw" :size="16" /></span>
<b>Wipe orchestration</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="database-backup" :size="16" /></span>
<b>Backup-before-change</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="download" :size="16" /></span>
<b>SteamCMD / game updates</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="bell" :size="16" /></span>
<b>Discord / status announcements</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="undo-2" :size="16" /></span>
<b>Health checks &amp; rollback</b>
</div>
</div>
<div class="caps__col">
<span class="eyebrow">Game systems</span>
<div class="feat">
<span class="feat__ic"><Icon name="puzzle" :size="16" /></span>
<b>Rust plugins &amp; configs</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="sun" :size="16" /></span>
<b>Dune: Awakening battlegroups</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="drama" :size="16" /></span>
<b>Soulmask clusters</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="swords" :size="16" /></span>
<b>Conan mods &amp; events</b>
</div>
<div class="feat">
<span class="feat__ic"><Icon name="store" :size="16" /></span>
<b>Public pages &amp; storefront</b>
</div>
</div>
</div>
</div>
</section>
<!-- WIPE & MAINTENANCE ORCHESTRATION -->
<section class="sec" id="wipe">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">Wipe &amp; maintenance orchestration</span>
<h2 class="title">Wipes should be workflows,<br>not rituals</h2>
<p class="lead">
Rust map / BP / full wipes. Dune: Awakening Deep Desert wipes. Soulmask &amp; Conan
maintenance and event resets all as verified, logged sequences.
</p>
</div>
<div class="pipe reveal">
<span class="pchip">Pre-warning</span>
<Icon name="chevron-right" :size="15" style="color:var(--text-muted)" />
<span class="pchip">Backup</span>
<Icon name="chevron-right" :size="15" style="color:var(--text-muted)" />
<span class="pchip">Stop services</span>
<Icon name="chevron-right" :size="15" style="color:var(--text-muted)" />
<span class="pchip">Update</span>
<Icon name="chevron-right" :size="15" style="color:var(--text-muted)" />
<span class="pchip">Rotate map / config</span>
<Icon name="chevron-right" :size="15" style="color:var(--text-muted)" />
<span class="pchip">Restart</span>
<Icon name="chevron-right" :size="15" style="color:var(--text-muted)" />
<span class="pchip">Health check</span>
<Icon name="chevron-right" :size="15" style="color:var(--text-muted)" />
<span class="pchip pchip--last">Announce complete</span>
</div>
<div class="stack-lines reveal">
<span>Every operation is logged.</span>
<span>Every step is verified.</span>
<span class="hi">Rollback is one click away when supported.</span>
</div>
</div>
</section>
<!-- BUILT LIKE INFRASTRUCTURE -->
<section class="sec" id="platform">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">Built like infrastructure</span>
<h2 class="title">Not a skin over SSH</h2>
<p class="lead">
A hosted control plane plus a host agent with tenant isolation, command namespacing,
health reporting, and outbound-only connectivity.
</p>
</div>
<div class="infra reveal">
<div class="icard">
<div class="icard__ic"><Icon name="cpu" :size="16" /></div>
<b>Agent-based control</b>
<p>Your host connects to Corrosion. No exposed management panel required.</p>
</div>
<div class="icard">
<div class="icard__ic"><Icon name="shield" :size="16" /></div>
<b>Tenant isolated</b>
<p>Every license, server, and command is scoped.</p>
</div>
<div class="icard">
<div class="icard__ic"><Icon name="route" :size="16" /></div>
<b>Command namespaced</b>
<p>Server actions are routed intentionally, not sprayed blindly.</p>
</div>
<div class="icard">
<div class="icard__ic"><Icon name="zap" :size="16" /></div>
<b>Event-driven</b>
<p>NATS-powered messaging keeps agents and panel in sync.</p>
</div>
<div class="icard">
<div class="icard__ic"><Icon name="activity" :size="16" /></div>
<b>Observable</b>
<p>Health, metrics, task history, and agent status all visible.</p>
</div>
</div>
<div class="techrow reveal">
<span>NestJS</span>
<span>NATS JetStream</span>
<span>PostgreSQL</span>
<span>Go host agent</span>
<span>Outbound-only</span>
</div>
</div>
</section>
<!-- PUBLIC SITES & STOREFRONT -->
<section class="sec" id="store">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">Public server sites &amp; storefront</span>
<h2 class="title">Give your players a home base</h2>
<p class="lead">
Publish a server page with live status, wipe countdowns, player counts, plugin / mod lists,
announcements, and optional storefront support.
</p>
</div>
<div class="chips reveal">
<div class="chip-card"><Icon name="globe" :size="16" style="color:var(--accent-text)" />Public server page</div>
<div class="chip-card"><Icon name="users" :size="16" style="color:var(--accent-text)" />Live player count</div>
<div class="chip-card"><Icon name="timer" :size="16" style="color:var(--accent-text)" />Wipe countdown</div>
<div class="chip-card"><Icon name="puzzle" :size="16" style="color:var(--accent-text)" />Mod / plugin list</div>
<div class="chip-card"><Icon name="megaphone" :size="16" style="color:var(--accent-text)" />Announcements</div>
<div class="chip-card chip-card--accent"><Icon name="shopping-cart" :size="16" />Integrated webstore</div>
</div>
<p
class="closing reveal"
style="font-size:var(--text-md);color:var(--text-tertiary);font-weight:500"
>
Operate the server. Inform the players. Monetize without duct tape.
</p>
</div>
</section>
<!-- PRICING -->
<section class="sec" id="pricing">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">Pricing</span>
<h2 class="title">Scale from one server to a fleet</h2>
</div>
<div class="pricing reveal">
<div class="plan">
<div class="plan__tag" />
<div class="plan__name">Hobby</div>
<div class="plan__price">$9.99<small>/mo</small></div>
<div class="plan__scope">15 non-commercial servers.</div>
<RouterLink class="btn btn--ghost" :to="{ name: 'early-access' }">Start</RouterLink>
</div>
<div class="plan">
<div class="plan__tag" />
<div class="plan__name">Community</div>
<div class="plan__price">$19.99<small>/mo</small></div>
<div class="plan__scope">610 non-commercial servers.</div>
<RouterLink class="btn btn--ghost" :to="{ name: 'early-access' }">Start</RouterLink>
</div>
<div class="plan plan--feature">
<div class="plan__tag">Most popular</div>
<div class="plan__name">Operator</div>
<div class="plan__price">$99.99<small>/mo</small></div>
<div class="plan__scope">Commercial use, or up to 50 servers.</div>
<RouterLink class="btn btn--primary" :to="{ name: 'early-access' }">Get Operator</RouterLink>
</div>
<div class="plan">
<div class="plan__tag" />
<div class="plan__name">Network</div>
<div class="plan__price">$99.99<small>/mo</small></div>
<div class="plan__scope">50+ servers for fleets and hosting partners. Fleet Blocks add capacity.</div>
<RouterLink class="btn btn--ghost" :to="{ name: 'early-access' }">Start</RouterLink>
</div>
</div>
<div class="fleetblock reveal">
<b>Fleet Block</b>
<span class="p">+$49.99/mo</span>
<span>each additional 50 servers stack as many as your network needs.</span>
</div>
<p class="commercial reveal">
<b>Commercial use</b> includes monetized communities, paid access, VIP slots, donations,
sponsorship-supported servers, hosting providers, or managing servers for others.
</p>
<p class="support-note reveal">
Community support is included with every plan (documentation, community forum, diagnostics,
structured bug reports).
<b>Direct 1:1 support</b> is available separately at $125/hour in prepaid 1-hour blocks.
Corrosion is a tool, not a managed service.
</p>
</div>
</section>
<!-- FOR SERIOUS ADMINS -->
<section class="sec" id="admins">
<div class="wrap">
<div class="sec__head reveal">
<span class="eyebrow">For serious admins</span>
<h2 class="title">Built for admins<br>who are done babysitting</h2>
</div>
<div class="admins reveal">
<span><Icon name="chevron-right" :size="18" style="color:var(--accent-text)" />You run more than a toy server.</span>
<span><Icon name="chevron-right" :size="18" style="color:var(--accent-text)" />Your players expect uptime.</span>
<span><Icon name="chevron-right" :size="18" style="color:var(--accent-text)" />Wipe day needs a plan.</span>
<span><Icon name="chevron-right" :size="18" style="color:var(--accent-text)" />Mods and plugins need control.</span>
<span><Icon name="chevron-right" :size="18" style="color:var(--accent-text)" />Admin access needs boundaries.</span>
<span><Icon name="chevron-right" :size="18" style="color:var(--accent-text)" />Your community deserves better than guesswork.</span>
</div>
<p class="closing reveal accent">Stop babysitting your server. Start orchestrating it.</p>
</div>
</section>
<!-- FINAL CTA -->
<section class="finalcta">
<div class="finalcta__atmo" />
<div class="wrap finalcta__in reveal">
<h2>Ready to run your servers<br>like an operation?</h2>
<div class="cta-row">
<RouterLink class="btn btn--primary btn--lg" :to="{ name: 'early-access' }">
Join early access
</RouterLink>
<a class="btn btn--ghost btn--lg" :href="panelUrl + '/login'">
<Icon name="play" :size="17" />View live demo
</a>
</div>
</div>
</section>
</template>