Game — Wishlist / Beta Signup Gate
A neon-soaked closed-beta signup gate for a fictional dark-fantasy extraction RPG: a clipped-corner HUD card floats over a CSS-only key-art backdrop with animated glow orbs, scanlines, and a jagged skyline silhouette. Hunters get a perks list, email and platform fields with inline validation, a consent checkbox, and a glowing Request Beta Access CTA. Submitting reveals a success state with a fake invite code, copy-to-clipboard button, queue position, and a live signup counter that keeps ticking up.
MCP
Code
:root {
--bg: #0a0b10;
--bg-2: #12131c;
--panel: #171926;
--panel-2: #1f2233;
--text: #e7e9f3;
--muted: #9aa0bf;
--line: rgba(231, 233, 243, 0.1);
--line-2: rgba(231, 233, 243, 0.18);
--accent: #00e5ff;
--accent-2: #7c4dff;
--accent-3: #ff3d71;
--success: #36e27a;
--warn: #ffc857;
--danger: #ff4d4d;
--glow: 0 0 18px rgba(0, 229, 255, 0.45);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
color: var(--text);
font-family: "Inter", system-ui, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
/* ---------------------------------- backdrop / key art */
.backdrop {
position: fixed;
inset: 0;
z-index: 0;
overflow: hidden;
background:
radial-gradient(120% 90% at 80% 0%, #131628 0%, var(--bg) 60%),
var(--bg);
}
.backdrop-glow {
position: absolute;
border-radius: 50%;
filter: blur(90px);
opacity: 0.5;
animation: drift 14s ease-in-out infinite alternate;
}
.backdrop-glow-a {
width: 560px;
height: 560px;
top: -180px;
right: -120px;
background: radial-gradient(circle, rgba(124, 77, 255, 0.5), transparent 70%);
}
.backdrop-glow-b {
width: 480px;
height: 480px;
bottom: -200px;
left: -140px;
background: radial-gradient(circle, rgba(0, 229, 255, 0.35), transparent 70%);
animation-delay: -7s;
}
@keyframes drift {
from { transform: translate3d(0, 0, 0) scale(1); }
to { transform: translate3d(40px, 30px, 0) scale(1.12); }
}
.backdrop-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(0, 229, 255, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 229, 255, 0.05) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(80% 70% at 50% 30%, black 30%, transparent 100%);
-webkit-mask-image: radial-gradient(80% 70% at 50% 30%, black 30%, transparent 100%);
}
.backdrop-scan {
position: absolute;
inset: 0;
background: repeating-linear-gradient(
to bottom,
transparent 0 3px,
rgba(0, 0, 0, 0.16) 3px 4px
);
opacity: 0.35;
pointer-events: none;
}
.backdrop-silhouette {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 38vh;
background:
linear-gradient(to top, rgba(10, 11, 16, 0.95), transparent),
linear-gradient(75deg, transparent 18%, rgba(124, 77, 255, 0.12) 18.5%, transparent 26%),
linear-gradient(105deg, transparent 58%, rgba(0, 229, 255, 0.1) 58.5%, transparent 70%);
clip-path: polygon(
0 100%, 0 62%, 8% 55%, 14% 70%, 22% 38%, 30% 64%, 38% 30%,
47% 58%, 55% 22%, 64% 52%, 72% 34%, 81% 60%, 90% 40%, 100% 58%, 100% 100%
);
background-color: #0c0d15;
}
/* ---------------------------------- layout */
.gate {
position: relative;
z-index: 1;
max-width: 1120px;
margin: 0 auto;
padding: clamp(28px, 6vw, 72px) 24px 64px;
display: grid;
grid-template-columns: 1.05fr 0.95fr;
gap: clamp(28px, 5vw, 64px);
align-items: center;
min-height: 100vh;
}
/* ---------------------------------- hero (left) */
.studio-tag {
display: inline-flex;
align-items: center;
gap: 10px;
font-family: "Orbitron", sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.32em;
color: var(--muted);
}
.studio-mark {
width: 14px;
height: 14px;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
box-shadow: var(--glow);
}
.hero-title {
margin: 14px 0 12px;
font-family: "Orbitron", sans-serif;
font-weight: 900;
font-size: clamp(40px, 6.5vw, 76px);
line-height: 0.98;
letter-spacing: 0.02em;
text-transform: uppercase;
text-shadow: 0 0 32px rgba(124, 77, 255, 0.35);
}
.hero-title-accent {
display: block;
color: transparent;
background: linear-gradient(95deg, var(--accent) 10%, var(--accent-2) 90%);
-webkit-background-clip: text;
background-clip: text;
filter: drop-shadow(0 0 14px rgba(0, 229, 255, 0.35));
}
.hero-sub {
max-width: 46ch;
margin: 0 0 28px;
color: var(--muted);
font-size: 15.5px;
}
.hero-stats {
list-style: none;
margin: 0 0 26px;
padding: 0;
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.hero-stat {
flex: 1 1 130px;
padding: 14px 16px;
background: linear-gradient(180deg, var(--panel), rgba(23, 25, 38, 0.55));
border: 1px solid var(--line);
border-radius: var(--r-md);
clip-path: polygon(12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%, 0 12px);
}
.hero-stat-num {
display: block;
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: 22px;
color: var(--accent);
text-shadow: 0 0 14px rgba(0, 229, 255, 0.4);
font-variant-numeric: tabular-nums;
}
.hero-stat-label {
font-size: 11.5px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--muted);
}
/* wave meter */
.wave-meter {
padding: 16px 18px;
background: rgba(23, 25, 38, 0.7);
border: 1px solid var(--line);
border-radius: var(--r-md);
backdrop-filter: blur(6px);
}
.wave-meter-head {
display: flex;
justify-content: space-between;
font-family: "Orbitron", sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.22em;
color: var(--muted);
margin-bottom: 10px;
}
.wave-meter-pct {
color: var(--warn);
text-shadow: 0 0 10px rgba(255, 200, 87, 0.45);
}
.wave-meter-track {
height: 8px;
border-radius: 999px;
background: var(--bg-2);
border: 1px solid var(--line);
overflow: hidden;
}
.wave-meter-fill {
height: 100%;
width: var(--fill, 0%);
border-radius: inherit;
background: linear-gradient(90deg, var(--accent-2), var(--accent));
box-shadow: 0 0 12px rgba(0, 229, 255, 0.55);
animation: meter-in 1.4s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
@keyframes meter-in {
from { width: 0; }
}
.wave-meter-note {
margin: 10px 0 0;
font-size: 12px;
color: var(--muted);
}
/* ---------------------------------- card (right) */
.card {
position: relative;
padding: clamp(22px, 3vw, 32px);
background:
linear-gradient(180deg, rgba(31, 34, 51, 0.92), rgba(23, 25, 38, 0.96));
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
box-shadow:
0 24px 60px rgba(0, 0, 0, 0.55),
inset 0 1px 0 rgba(231, 233, 243, 0.07);
backdrop-filter: blur(10px);
clip-path: polygon(
24px 0, 100% 0, 100% calc(100% - 24px),
calc(100% - 24px) 100%, 0 100%, 0 24px
);
}
.card-corner {
position: absolute;
width: 26px;
height: 26px;
pointer-events: none;
}
.card-corner-tl {
top: 0;
left: 0;
border-top: 2px solid var(--accent);
border-left: 2px solid var(--accent);
clip-path: polygon(0 100%, 0 24px, 24px 0, 100% 0, 100% 2px, 24.8px 2px, 2px 24.8px, 2px 100%);
filter: drop-shadow(0 0 6px rgba(0, 229, 255, 0.6));
}
.card-corner-br {
bottom: 0;
right: 0;
border-bottom: 2px solid var(--accent-2);
border-right: 2px solid var(--accent-2);
clip-path: polygon(100% 0, 100% calc(100% - 24px), calc(100% - 24px) 100%, 0 100%, 0 calc(100% - 2px), calc(100% - 24.8px) calc(100% - 2px), calc(100% - 2px) calc(100% - 24.8px), calc(100% - 2px) 0);
filter: drop-shadow(0 0 6px rgba(124, 77, 255, 0.6));
}
.card-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
font-family: "Orbitron", sans-serif;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.24em;
color: var(--accent);
background: rgba(0, 229, 255, 0.08);
border: 1px solid rgba(0, 229, 255, 0.35);
border-radius: 999px;
}
.card-badge-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--accent);
box-shadow: var(--glow);
animation: pulse 1.8s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.45; transform: scale(0.78); }
}
.card-title {
margin: 14px 0 6px;
font-family: "Orbitron", sans-serif;
font-weight: 700;
font-size: clamp(20px, 2.4vw, 25px);
letter-spacing: 0.04em;
text-transform: uppercase;
}
.card-sub {
margin: 0 0 20px;
color: var(--muted);
font-size: 14px;
}
/* perks */
.perks {
list-style: none;
margin: 0 0 22px;
padding: 0;
display: grid;
gap: 8px;
}
.perk {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
background: rgba(10, 11, 16, 0.45);
border: 1px solid var(--line);
border-left: 2px solid var(--accent-2);
border-radius: var(--r-sm);
transition: border-color 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}
.perk:hover {
border-left-color: var(--accent);
transform: translateX(3px);
box-shadow: -4px 0 14px -6px rgba(0, 229, 255, 0.5);
}
.perk-icon {
display: grid;
place-items: center;
width: 34px;
height: 34px;
flex: none;
font-size: 16px;
color: var(--accent);
background: var(--panel-2);
border: 1px solid var(--line-2);
clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
}
.perk strong {
display: block;
font-size: 13.5px;
font-weight: 700;
letter-spacing: 0.01em;
}
.perk span:not(.perk-icon) {
font-size: 12px;
color: var(--muted);
}
/* fields */
.field {
margin-bottom: 14px;
}
.field-label {
display: block;
margin-bottom: 6px;
font-family: "Orbitron", sans-serif;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.26em;
color: var(--muted);
}
.field-input {
width: 100%;
padding: 12px 14px;
font: inherit;
font-size: 14px;
color: var(--text);
background: var(--bg-2);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
transition: border-color 140ms ease, box-shadow 140ms ease;
appearance: none;
}
.field-input::placeholder {
color: rgba(154, 160, 191, 0.55);
}
.field-input:hover {
border-color: rgba(0, 229, 255, 0.35);
}
.field-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(0, 229, 255, 0.18), var(--glow);
}
.field-input[aria-invalid="true"] {
border-color: var(--danger);
box-shadow: 0 0 0 3px rgba(255, 77, 77, 0.16);
}
select.field-input {
cursor: pointer;
padding-right: 38px;
}
select.field-input:invalid {
color: rgba(154, 160, 191, 0.7);
}
.select-wrap {
position: relative;
}
.select-arrow {
position: absolute;
top: 50%;
right: 14px;
width: 9px;
height: 9px;
border-right: 2px solid var(--accent);
border-bottom: 2px solid var(--accent);
transform: translateY(-70%) rotate(45deg);
pointer-events: none;
}
.field-error {
margin: 6px 0 0;
font-size: 12.5px;
font-weight: 600;
color: var(--danger);
}
.field-error::before {
content: "▲ ";
font-size: 9px;
vertical-align: 1px;
}
/* consent */
.consent {
display: flex;
align-items: flex-start;
gap: 11px;
margin: 4px 0 0;
cursor: pointer;
user-select: none;
}
.consent input {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
}
.consent-box {
flex: none;
width: 19px;
height: 19px;
margin-top: 1px;
background: var(--bg-2);
border: 1px solid var(--line-2);
border-radius: 4px;
position: relative;
transition: border-color 140ms ease, background 140ms ease, box-shadow 140ms ease;
}
.consent-box::after {
content: "";
position: absolute;
left: 6px;
top: 2px;
width: 5px;
height: 9px;
border-right: 2px solid #04121a;
border-bottom: 2px solid #04121a;
transform: rotate(45deg) scale(0);
transition: transform 140ms cubic-bezier(0.3, 1.4, 0.6, 1);
}
.consent input:checked + .consent-box {
background: var(--accent);
border-color: var(--accent);
box-shadow: var(--glow);
}
.consent input:checked + .consent-box::after {
transform: rotate(45deg) scale(1);
}
.consent input:focus-visible + .consent-box {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.consent-text {
font-size: 12.5px;
color: var(--muted);
}
/* CTA */
.cta {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
margin-top: 18px;
padding: 15px 18px;
font-family: "Orbitron", sans-serif;
font-size: 14px;
font-weight: 700;
letter-spacing: 0.18em;
color: #04121a;
background: linear-gradient(95deg, var(--accent), #4ce8ff 55%, var(--accent));
border: none;
border-radius: var(--r-sm);
cursor: pointer;
overflow: hidden;
clip-path: polygon(14px 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%, 0 14px);
box-shadow: var(--glow);
transition: transform 140ms ease, box-shadow 140ms ease, filter 140ms ease;
}
.cta:hover {
transform: translateY(-2px);
box-shadow: 0 0 28px rgba(0, 229, 255, 0.65);
filter: brightness(1.06);
}
.cta:active {
transform: translateY(0);
}
.cta:focus-visible {
outline: 2px solid var(--text);
outline-offset: 3px;
}
.cta:disabled {
cursor: progress;
filter: saturate(0.4) brightness(0.8);
box-shadow: none;
}
.cta-shine {
position: absolute;
inset: 0;
background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.55) 48%, transparent 60%);
transform: translateX(-110%);
animation: shine 3.4s ease-in-out infinite;
pointer-events: none;
}
@keyframes shine {
0%, 60% { transform: translateX(-110%); }
100% { transform: translateX(110%); }
}
.cta-ghost {
color: var(--accent);
background: transparent;
border: 1px solid rgba(0, 229, 255, 0.45);
box-shadow: none;
}
.cta-ghost:hover {
background: rgba(0, 229, 255, 0.08);
box-shadow: var(--glow);
}
/* share / wishlist */
.share {
margin-top: 22px;
padding-top: 18px;
border-top: 1px solid var(--line);
}
.share-label {
display: block;
margin-bottom: 10px;
font-family: "Orbitron", sans-serif;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.3em;
color: var(--muted);
}
.share-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.share-btn {
flex: 1 1 0;
min-width: 70px;
padding: 9px 10px;
font-family: "Orbitron", sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.14em;
color: var(--text);
background: var(--panel-2);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
cursor: pointer;
transition: border-color 140ms ease, color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
}
.share-btn:hover {
color: var(--accent);
border-color: var(--accent);
box-shadow: var(--glow);
transform: translateY(-1px);
}
.share-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* ---------------------------------- success state */
.card-state-success {
text-align: center;
animation: state-in 360ms cubic-bezier(0.2, 0.9, 0.3, 1.2) both;
}
@keyframes state-in {
from { opacity: 0; transform: translateY(14px) scale(0.97); }
}
.success-sigil {
display: grid;
place-items: center;
width: 72px;
height: 72px;
margin: 8px auto 18px;
color: var(--success);
background: rgba(54, 226, 122, 0.1);
border: 1px solid rgba(54, 226, 122, 0.5);
clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
box-shadow: 0 0 24px rgba(54, 226, 122, 0.4);
animation: pulse 2.2s ease-in-out infinite;
}
.invite {
margin: 22px 0;
padding: 16px;
text-align: left;
background: rgba(10, 11, 16, 0.6);
border: 1px dashed rgba(0, 229, 255, 0.45);
border-radius: var(--r-md);
}
.invite-label {
display: block;
margin-bottom: 8px;
font-family: "Orbitron", sans-serif;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.3em;
color: var(--muted);
}
.invite-row {
display: flex;
align-items: center;
gap: 10px;
}
.invite-code {
flex: 1;
font-family: "Orbitron", sans-serif;
font-size: clamp(15px, 2.2vw, 19px);
font-weight: 700;
letter-spacing: 0.12em;
color: var(--accent);
text-shadow: 0 0 14px rgba(0, 229, 255, 0.5);
overflow-wrap: anywhere;
}
.copy-btn {
display: inline-flex;
align-items: center;
gap: 7px;
flex: none;
padding: 9px 14px;
font-family: "Orbitron", sans-serif;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.16em;
color: var(--accent);
background: rgba(0, 229, 255, 0.08);
border: 1px solid rgba(0, 229, 255, 0.45);
border-radius: var(--r-sm);
cursor: pointer;
transition: background 140ms ease, box-shadow 140ms ease, color 140ms ease, border-color 140ms ease;
}
.copy-btn:hover {
background: rgba(0, 229, 255, 0.16);
box-shadow: var(--glow);
}
.copy-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.copy-btn.copied {
color: var(--success);
border-color: var(--success);
background: rgba(54, 226, 122, 0.1);
box-shadow: 0 0 14px rgba(54, 226, 122, 0.4);
}
.success-meta {
list-style: none;
margin: 0 0 20px;
padding: 0;
text-align: left;
}
.success-meta li {
display: flex;
justify-content: space-between;
gap: 14px;
padding: 10px 2px;
font-size: 13px;
border-bottom: 1px solid var(--line);
}
.success-meta li:last-child {
border-bottom: none;
}
.success-meta span {
color: var(--muted);
}
.success-meta strong {
font-weight: 700;
font-variant-numeric: tabular-nums;
text-align: right;
}
.meta-locked {
font-family: "Orbitron", sans-serif;
font-size: 10.5px;
letter-spacing: 0.12em;
color: var(--success);
text-shadow: 0 0 10px rgba(54, 226, 122, 0.45);
}
/* ---------------------------------- toast */
.toast {
position: fixed;
left: 50%;
bottom: 28px;
z-index: 10;
padding: 12px 20px;
font-family: "Orbitron", sans-serif;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.12em;
color: var(--text);
background: var(--panel-2);
border: 1px solid var(--accent);
border-radius: var(--r-sm);
box-shadow: var(--glow), 0 14px 34px rgba(0, 0, 0, 0.5);
clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
transform: translate(-50%, 16px);
opacity: 0;
pointer-events: none;
transition: transform 220ms cubic-bezier(0.2, 0.9, 0.3, 1.2), opacity 220ms ease;
}
.toast.show {
transform: translate(-50%, 0);
opacity: 1;
}
/* counter tick */
.hero-stat-num.tick {
animation: tick 420ms cubic-bezier(0.2, 0.9, 0.3, 1.4);
}
@keyframes tick {
35% {
transform: translateY(-4px) scale(1.08);
color: var(--success);
text-shadow: 0 0 16px rgba(54, 226, 122, 0.6);
}
}
/* ---------------------------------- responsive */
@media (max-width: 880px) {
.gate {
grid-template-columns: 1fr;
gap: 36px;
padding-top: 44px;
}
.hero-sub {
max-width: none;
}
}
@media (max-width: 520px) {
.gate {
padding-left: 16px;
padding-right: 16px;
}
.hero-title {
font-size: 38px;
}
.hero-stats {
gap: 8px;
}
.hero-stat {
flex: 1 1 100%;
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
padding: 11px 14px;
}
.hero-stat-num {
font-size: 18px;
}
.card {
padding: 20px 16px;
}
.invite-row {
flex-direction: column;
align-items: stretch;
}
.copy-btn {
justify-content: center;
}
.share-row {
flex-direction: column;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}/* Hollow Reign — Closed Beta Signup Gate
Vanilla JS: validation, fake submit flow, invite code reveal,
copy-to-clipboard, live counter, toasts. Illustrative only. */
(() => {
"use strict";
// ---------- elements
const form = document.getElementById("betaForm");
const emailInput = document.getElementById("email");
const platformSelect = document.getElementById("platform");
const consentInput = document.getElementById("consent");
const emailError = document.getElementById("emailError");
const platformError = document.getElementById("platformError");
const consentError = document.getElementById("consentError");
const submitBtn = document.getElementById("submitBtn");
const submitLabel = submitBtn.querySelector(".cta-label");
const formState = document.getElementById("formState");
const successState = document.getElementById("successState");
const successEmail = document.getElementById("successEmail");
const successPlatform = document.getElementById("successPlatform");
const queuePos = document.getElementById("queuePos");
const inviteCode = document.getElementById("inviteCode");
const copyBtn = document.getElementById("copyBtn");
const resetBtn = document.getElementById("resetBtn");
const counterEl = document.getElementById("signupCounter");
const toastEl = document.getElementById("toast");
const PLATFORM_LABELS = {
pc: "PC — Steam",
ps5: "PlayStation 5",
xbox: "Xbox Series X|S",
cloud: "Cloud Streaming",
};
// ---------- toast helper
let toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("show"), 2400);
}
// ---------- live signup counter
let count = 48217;
function renderCount() {
counterEl.textContent = count.toLocaleString("en-US");
counterEl.classList.remove("tick");
// restart the tick animation
void counterEl.offsetWidth;
counterEl.classList.add("tick");
}
function bumpCount(by) {
count += by;
renderCount();
}
// ambient ticking: other "hunters" enlisting every few seconds
function scheduleAmbientTick() {
const delay = 2600 + Math.random() * 4800;
setTimeout(() => {
bumpCount(1 + Math.floor(Math.random() * 3));
scheduleAmbientTick();
}, delay);
}
scheduleAmbientTick();
// ---------- validation
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
function setError(input, errorEl, message) {
if (message) {
errorEl.textContent = message;
errorEl.hidden = false;
if (input) input.setAttribute("aria-invalid", "true");
} else {
errorEl.textContent = "";
errorEl.hidden = true;
if (input) input.removeAttribute("aria-invalid");
}
}
function validateEmail() {
const value = emailInput.value.trim();
if (!value) {
setError(emailInput, emailError, "Enter your email to receive a beta key.");
return false;
}
if (!EMAIL_RE.test(value)) {
setError(emailInput, emailError, "That doesn't look like a valid email.");
return false;
}
setError(emailInput, emailError, null);
return true;
}
function validatePlatform() {
if (!platformSelect.value) {
setError(platformSelect, platformError, "Pick the platform you'll play on.");
return false;
}
setError(platformSelect, platformError, null);
return true;
}
function validateConsent() {
if (!consentInput.checked) {
setError(null, consentError, "You need to opt in so we can send your key.");
return false;
}
setError(null, consentError, null);
return true;
}
// live re-validation once a field has been flagged
emailInput.addEventListener("input", () => {
if (!emailError.hidden) validateEmail();
});
platformSelect.addEventListener("change", () => {
if (!platformError.hidden) validatePlatform();
});
consentInput.addEventListener("change", () => {
if (!consentError.hidden) validateConsent();
});
emailInput.addEventListener("blur", () => {
if (emailInput.value.trim()) validateEmail();
});
// ---------- invite code generator (clearly fake)
function generateInviteCode() {
const alphabet = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"; // no 0/O/1/I/L
const block = () =>
Array.from({ length: 4 }, () =>
alphabet[Math.floor(Math.random() * alphabet.length)]
).join("");
return `HRGN-${block()}-${block()}`;
}
// ---------- submit flow
let submitting = false;
form.addEventListener("submit", (event) => {
event.preventDefault();
if (submitting) return;
// validate all; focus first failing field
const emailOk = validateEmail();
const platformOk = validatePlatform();
const consentOk = validateConsent();
if (!emailOk) { emailInput.focus(); return; }
if (!platformOk) { platformSelect.focus(); return; }
if (!consentOk) { consentInput.focus(); return; }
submitting = true;
submitBtn.disabled = true;
submitLabel.textContent = "TRANSMITTING…";
// fake network round-trip
setTimeout(() => {
submitting = false;
submitBtn.disabled = false;
submitLabel.textContent = "REQUEST BETA ACCESS";
bumpCount(1);
successEmail.textContent = emailInput.value.trim();
successPlatform.textContent =
PLATFORM_LABELS[platformSelect.value] || platformSelect.value;
queuePos.textContent = `#${count.toLocaleString("en-US")}`;
inviteCode.textContent = generateInviteCode();
formState.hidden = true;
successState.hidden = false;
copyBtn.classList.remove("copied");
successState.querySelector(".card-title").setAttribute("tabindex", "-1");
successState.querySelector(".card-title").focus();
toast("ENLISTED — INVITE CODE ISSUED");
}, 900);
});
// ---------- copy invite code
copyBtn.addEventListener("click", async () => {
const code = inviteCode.textContent.trim();
try {
await navigator.clipboard.writeText(code);
} catch {
// clipboard API unavailable (e.g. sandboxed iframe) — select fallback
const range = document.createRange();
range.selectNodeContents(inviteCode);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
copyBtn.classList.add("copied");
setTimeout(() => copyBtn.classList.remove("copied"), 1600);
toast(`COPIED ${code}`);
});
// ---------- reset to form state
resetBtn.addEventListener("click", () => {
form.reset();
setError(emailInput, emailError, null);
setError(platformSelect, platformError, null);
setError(null, consentError, null);
successState.hidden = true;
formState.hidden = false;
emailInput.focus();
});
// ---------- wishlist / share buttons
document.querySelectorAll(".share-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const platform = btn.dataset.platform || "your platform";
toast(`WISHLISTED ON ${platform.toUpperCase()} ✦`);
});
});
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hollow Reign — Closed Beta Signup</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="backdrop" aria-hidden="true">
<div class="backdrop-glow backdrop-glow-a"></div>
<div class="backdrop-glow backdrop-glow-b"></div>
<div class="backdrop-grid"></div>
<div class="backdrop-scan"></div>
<div class="backdrop-silhouette"></div>
</div>
<main class="gate">
<!-- Left: hero copy -->
<section class="hero" aria-labelledby="hero-title">
<div class="studio-tag">
<span class="studio-mark" aria-hidden="true"></span>
NULLFORGE STUDIOS PRESENTS
</div>
<h1 id="hero-title" class="hero-title">
HOLLOW<span class="hero-title-accent">REIGN</span>
</h1>
<p class="hero-sub">
A dark-fantasy extraction RPG. Descend into the Sunken Capital, claim relics
from a dying kingdom, and escape before the Hollow takes you too.
</p>
<ul class="hero-stats" aria-label="Beta program stats">
<li class="hero-stat">
<span class="hero-stat-num" id="signupCounter" aria-live="polite">48,217</span>
<span class="hero-stat-label">Hunters enlisted</span>
</li>
<li class="hero-stat">
<span class="hero-stat-num">12</span>
<span class="hero-stat-label">Days until wave 3</span>
</li>
<li class="hero-stat">
<span class="hero-stat-num">4.9</span>
<span class="hero-stat-label">Alpha tester rating</span>
</li>
</ul>
<div class="wave-meter" role="img" aria-label="Beta wave 3 capacity 72 percent full">
<div class="wave-meter-head">
<span>WAVE 3 CAPACITY</span>
<span class="wave-meter-pct">72%</span>
</div>
<div class="wave-meter-track">
<div class="wave-meter-fill" style="--fill: 72%"></div>
</div>
<p class="wave-meter-note">Invites are granted in waves. Earlier signups get priority.</p>
</div>
</section>
<!-- Right: signup card -->
<section class="card" aria-labelledby="card-title">
<div class="card-corner card-corner-tl" aria-hidden="true"></div>
<div class="card-corner card-corner-br" aria-hidden="true"></div>
<!-- FORM STATE -->
<div class="card-state" id="formState">
<header class="card-head">
<span class="card-badge">
<span class="card-badge-dot" aria-hidden="true"></span>
CLOSED BETA · WAVE 3
</span>
<h2 id="card-title" class="card-title">Request Beta Access</h2>
<p class="card-sub">Enlist for the Sunken Capital playtest and lock in your founder rewards.</p>
</header>
<ul class="perks" aria-label="Signup perks">
<li class="perk">
<span class="perk-icon" aria-hidden="true">⚡</span>
<div>
<strong>Early access</strong>
<span>Play 3 weeks before open beta</span>
</div>
</li>
<li class="perk">
<span class="perk-icon" aria-hidden="true">🜲</span>
<div>
<strong>Exclusive “Gravewrought” skin</strong>
<span>Founder-only armor set, never re-issued</span>
</div>
</li>
<li class="perk">
<span class="perk-icon" aria-hidden="true">✉</span>
<div>
<strong>Dev updates</strong>
<span>Monthly build notes straight from Nullforge</span>
</div>
</li>
</ul>
<form id="betaForm" novalidate>
<div class="field">
<label class="field-label" for="email">EMAIL</label>
<input
class="field-input"
type="email"
id="email"
name="email"
placeholder="hunter@sunkencapital.gg"
autocomplete="email"
required
aria-describedby="emailError"
/>
<p class="field-error" id="emailError" role="alert" hidden></p>
</div>
<div class="field">
<label class="field-label" for="platform">PLATFORM</label>
<div class="select-wrap">
<select class="field-input" id="platform" name="platform" required>
<option value="" selected disabled>Select your platform</option>
<option value="pc">PC — Steam</option>
<option value="ps5">PlayStation 5</option>
<option value="xbox">Xbox Series X|S</option>
<option value="cloud">Cloud Streaming</option>
</select>
<span class="select-arrow" aria-hidden="true"></span>
</div>
<p class="field-error" id="platformError" role="alert" hidden></p>
</div>
<label class="consent">
<input type="checkbox" id="consent" required />
<span class="consent-box" aria-hidden="true"></span>
<span class="consent-text">
I agree to receive beta keys and dev updates from Nullforge Studios.
Unsubscribe anytime.
</span>
</label>
<p class="field-error" id="consentError" role="alert" hidden></p>
<button type="submit" class="cta" id="submitBtn">
<span class="cta-label">REQUEST BETA ACCESS</span>
<span class="cta-shine" aria-hidden="true"></span>
</button>
</form>
<div class="share">
<span class="share-label">WISHLIST ON</span>
<div class="share-row" role="group" aria-label="Wishlist platforms">
<button type="button" class="share-btn" data-platform="Steam">STEAM</button>
<button type="button" class="share-btn" data-platform="PlayStation">PS5</button>
<button type="button" class="share-btn" data-platform="Xbox">XBOX</button>
<button type="button" class="share-btn" data-platform="Epic">EPIC</button>
</div>
</div>
</div>
<!-- SUCCESS STATE -->
<div class="card-state card-state-success" id="successState" hidden>
<div class="success-sigil" aria-hidden="true">
<svg viewBox="0 0 24 24" width="34" height="34" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 6L9 17l-5-5" />
</svg>
</div>
<h2 class="card-title">You're Enlisted, Hunter</h2>
<p class="card-sub">
A confirmation was sent to <strong id="successEmail">your inbox</strong>.
Guard this invite code — you'll need it when wave 3 opens.
</p>
<div class="invite" aria-label="Your beta invite code">
<span class="invite-label">INVITE CODE</span>
<div class="invite-row">
<code class="invite-code" id="inviteCode">HRGN-XXXX-XXXX</code>
<button type="button" class="copy-btn" id="copyBtn" aria-label="Copy invite code">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
COPY
</button>
</div>
</div>
<ul class="success-meta">
<li><span>Queue position</span><strong id="queuePos">#48,218</strong></li>
<li><span>Platform</span><strong id="successPlatform">PC — Steam</strong></li>
<li><span>Founder skin</span><strong class="meta-locked">GRAVEWROUGHT · LOCKED IN</strong></li>
</ul>
<button type="button" class="cta cta-ghost" id="resetBtn">
<span class="cta-label">ENLIST ANOTHER HUNTER</span>
</button>
</div>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Wishlist / Beta Signup Gate
A conversion-focused beta gate for the fictional dark-fantasy extraction RPG Hollow Reign by Nullforge Studios. The left column sells the game: an Orbitron display title with a gradient accent, live program stats (signups, days to wave 3, alpha rating), and a “Wave 3 capacity” progress meter that animates its fill on load. The right column is a sci-fi HUD card with clip-path corners, neon corner brackets, a pulsing “Closed Beta” badge, and a perks list — early access, an exclusive founder skin, and dev updates.
The form validates inline: the email field checks format on blur and re-validates as you type once flagged, the platform select and consent checkbox each surface their own error rows, and the first failing field receives focus. The glowing CTA enters a “Transmitting…” disabled state before flipping the card to a success panel that shows the confirmed email, platform, queue position, and a freshly generated HRGN-XXXX-XXXX invite code with a copy button — copying fires a toast and a green “copied” pulse.
Everything is vanilla HTML/CSS/JS with zero assets: the key art is layered gradients, a masked grid, scanlines, and a polygon-clipped skyline. The signup counter ticks up ambiently at random intervals (and once more on your own signup), the wishlist row toasts per storefront, and a prefers-reduced-motion block tones the whole thing down. The layout collapses to a single column under 880px and stays usable at 360px.
Illustrative UI only — fictional games, studios, characters, and data. Not engine integrations.