Game — Press Kit (presskit() style)
A presskit()-style press page for a fictional action roguelike: neon HUD header with studio logo and review-copy CTA, a structured factsheet table, description and features panels, a filterable downloadable-assets grid with copy-link buttons and simulated single and bulk downloads, an awards and press-quotes band, plus press contact emails with one-click copy and a validated request-review-copy form — all dark sci-fi panels, clipped corners, and cyan glow built with vanilla HTML, CSS, and JS.
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;
--font-display: "Orbitron", system-ui, sans-serif;
--font-body: "Inter", system-ui, sans-serif;
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
font-family: var(--font-body);
line-height: 1.5;
color: var(--text);
background:
radial-gradient(900px 480px at 85% -10%, rgba(124, 77, 255, 0.16), transparent 60%),
radial-gradient(800px 420px at 0% 0%, rgba(0, 229, 255, 0.1), transparent 55%),
var(--bg);
}
.page {
max-width: 1100px;
margin: 0 auto;
padding: 28px 20px 60px;
}
/* ===== Buttons ===== */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 22px;
font-family: var(--font-display);
font-weight: 700;
font-size: 0.82rem;
letter-spacing: 0.08em;
text-transform: uppercase;
text-decoration: none;
color: var(--text);
background: var(--panel-2);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
cursor: pointer;
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.btn:hover {
transform: translateY(-1px);
border-color: var(--accent);
box-shadow: var(--glow);
}
.btn:active {
transform: translateY(1px);
}
.btn:focus-visible,
.chip:focus-visible,
.copy-link:focus-visible,
.iconbtn:focus-visible,
.social:focus-visible,
.field__input:focus-visible,
.selectall input:focus-visible,
.asset-select:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.btn--primary {
color: #041318;
background: linear-gradient(135deg, var(--accent), #00b4d6);
border-color: transparent;
box-shadow: 0 0 14px rgba(0, 229, 255, 0.35);
}
.btn--primary:hover {
box-shadow: 0 0 26px rgba(0, 229, 255, 0.6);
border-color: transparent;
}
.btn--accent {
color: #f3edff;
background: linear-gradient(135deg, var(--accent-2), #5b32d6);
border-color: transparent;
}
.btn--accent:hover {
border-color: transparent;
box-shadow: 0 0 20px rgba(124, 77, 255, 0.55);
}
.btn--accent:disabled,
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn--ghost {
background: transparent;
}
.btn--sm {
padding: 8px 14px;
font-size: 0.7rem;
clip-path: polygon(7px 0, 100% 0, 100% calc(100% - 7px), calc(100% - 7px) 100%, 0 100%, 0 7px);
}
.btn--block {
width: 100%;
}
.btn.is-busy {
pointer-events: none;
opacity: 0.8;
animation: busyPulse 1s ease-in-out infinite;
}
@keyframes busyPulse {
50% {
box-shadow: 0 0 22px rgba(0, 229, 255, 0.5);
}
}
/* ===== Header ===== */
.pk-header {
position: relative;
overflow: hidden;
background: linear-gradient(160deg, var(--panel-2), var(--panel) 55%, var(--bg-2));
border: 1px solid var(--line-2);
border-radius: var(--r-lg);
clip-path: polygon(24px 0, 100% 0, 100% calc(100% - 24px), calc(100% - 24px) 100%, 0 100%, 0 24px);
padding: 36px 32px 30px;
margin-bottom: 22px;
}
.pk-header::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(115deg, rgba(0, 229, 255, 0.1), transparent 45%),
repeating-linear-gradient(0deg, transparent 0 3px, rgba(231, 233, 243, 0.012) 3px 4px);
pointer-events: none;
}
.pk-header__scan {
position: absolute;
inset: 0 0 auto 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
animation: scanline 5s linear infinite;
opacity: 0.7;
pointer-events: none;
}
@keyframes scanline {
0% {
transform: translateY(0);
}
100% {
transform: translateY(260px);
}
}
.pk-header__inner {
position: relative;
display: grid;
grid-template-columns: auto 1fr;
gap: 8px 26px;
align-items: start;
}
.pk-logo {
color: var(--accent);
filter: drop-shadow(0 0 14px rgba(0, 229, 255, 0.5));
animation: logoPulse 3.5s ease-in-out infinite;
}
@keyframes logoPulse {
50% {
filter: drop-shadow(0 0 24px rgba(0, 229, 255, 0.75));
}
}
.pk-kicker {
margin: 0 0 6px;
font-family: var(--font-display);
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.28em;
text-transform: uppercase;
color: var(--accent);
}
.pk-title {
margin: 0 0 10px;
font-family: var(--font-display);
font-weight: 900;
font-size: clamp(2rem, 5.5vw, 3.4rem);
line-height: 1.05;
letter-spacing: 0.04em;
text-transform: uppercase;
background: linear-gradient(90deg, var(--text) 30%, var(--accent) 75%, var(--accent-2));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.pk-sub {
margin: 0 0 18px;
max-width: 56ch;
color: var(--muted);
font-size: 1.02rem;
}
.pk-sub strong {
color: var(--text);
}
.pk-header__cta {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.pk-header__meta {
grid-column: 1 / -1;
display: flex;
align-items: center;
gap: 8px;
margin-top: 18px;
padding-top: 14px;
border-top: 1px solid var(--line);
font-size: 0.8rem;
color: var(--muted);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--success);
box-shadow: 0 0 10px rgba(54, 226, 122, 0.8);
animation: dotPulse 2s ease-in-out infinite;
}
@keyframes dotPulse {
50% {
box-shadow: 0 0 3px rgba(54, 226, 122, 0.4);
}
}
/* ===== Layout ===== */
.pk-main {
display: grid;
gap: 22px;
}
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 22px;
}
.panel {
position: relative;
background: linear-gradient(180deg, var(--panel), var(--bg-2));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 24px 26px;
box-shadow: inset 0 1px 0 rgba(231, 233, 243, 0.05);
transition: border-color 0.25s ease;
}
.panel:hover {
border-color: var(--line-2);
}
.panel--clip {
clip-path: polygon(18px 0, 100% 0, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%, 0 18px);
}
.panel--cta {
border-color: rgba(0, 229, 255, 0.28);
box-shadow: inset 0 1px 0 rgba(231, 233, 243, 0.05), 0 0 28px rgba(0, 229, 255, 0.07);
}
.panel__title {
margin: 0 0 16px;
font-family: var(--font-display);
font-size: 0.95rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.panel__title::after {
content: "";
display: block;
width: 44px;
height: 2px;
margin-top: 8px;
background: linear-gradient(90deg, var(--accent), transparent);
}
.prose {
margin: 0 0 12px;
color: var(--muted);
font-size: 0.95rem;
}
.prose:last-child {
margin-bottom: 0;
}
.prose strong,
.prose em {
color: var(--text);
}
/* ===== Factsheet ===== */
.factsheet {
width: 100%;
border-collapse: collapse;
font-size: 0.92rem;
}
.factsheet th,
.factsheet td {
padding: 10px 4px;
text-align: left;
border-bottom: 1px solid var(--line);
vertical-align: top;
}
.factsheet tr:last-child th,
.factsheet tr:last-child td {
border-bottom: none;
}
.factsheet th {
width: 36%;
font-family: var(--font-display);
font-size: 0.68rem;
font-weight: 500;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
padding-top: 13px;
}
/* ===== Copy links ===== */
.copy-link {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 2px 4px;
margin: -2px -4px;
font: inherit;
color: var(--accent);
background: none;
border: 0;
border-radius: var(--r-sm);
cursor: pointer;
transition: color 0.2s ease, text-shadow 0.2s ease;
}
.copy-link:hover {
color: #7df2ff;
text-shadow: 0 0 12px rgba(0, 229, 255, 0.6);
}
.copy-link__ico {
font-size: 0.85em;
opacity: 0.7;
}
.copy-link.is-copied {
color: var(--success);
text-shadow: 0 0 12px rgba(54, 226, 122, 0.5);
}
/* ===== Features ===== */
.features {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px 26px;
}
.features li {
display: flex;
gap: 10px;
padding: 10px 12px;
font-size: 0.92rem;
color: var(--muted);
background: rgba(231, 233, 243, 0.025);
border: 1px solid var(--line);
border-left: 2px solid var(--accent-2);
border-radius: var(--r-sm);
transition: border-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.features li:hover {
color: var(--text);
border-left-color: var(--accent);
transform: translateX(3px);
}
.features__tick {
color: var(--accent);
flex: none;
}
/* ===== Assets ===== */
.assets-head {
display: grid;
gap: 14px;
margin-bottom: 20px;
}
.assets-tools {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 14px;
}
.filterbar {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip {
padding: 8px 16px;
font-family: var(--font-display);
font-size: 0.68rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
background: var(--bg-2);
border: 1px solid var(--line-2);
border-radius: 999px;
cursor: pointer;
transition: color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
}
.chip:hover {
color: var(--text);
border-color: var(--accent);
}
.chip.is-active {
color: #041318;
background: var(--accent);
border-color: var(--accent);
box-shadow: var(--glow);
}
.bulkbar {
display: flex;
align-items: center;
gap: 14px;
}
.selectall {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 0.85rem;
color: var(--muted);
cursor: pointer;
user-select: none;
}
.selectall input,
.asset-select {
width: 17px;
height: 17px;
accent-color: var(--accent);
cursor: pointer;
}
.bulk-count {
opacity: 0.85;
font-family: var(--font-body);
font-weight: 700;
}
.asset-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: 18px;
}
.asset {
position: relative;
display: flex;
flex-direction: column;
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: var(--r-md);
overflow: hidden;
transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.25s ease;
}
.asset:hover {
transform: translateY(-3px);
border-color: rgba(0, 229, 255, 0.45);
box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45), 0 0 16px rgba(0, 229, 255, 0.12);
}
.asset.is-selected {
border-color: var(--accent-2);
box-shadow: 0 0 0 1px var(--accent-2), 0 0 18px rgba(124, 77, 255, 0.3);
}
.asset.is-hidden {
display: none;
}
.asset__thumb {
position: relative;
display: grid;
place-items: center;
height: 118px;
background: linear-gradient(135deg, var(--panel-2), var(--bg-2));
border-bottom: 1px solid var(--line);
}
.asset__thumb--logo {
background:
radial-gradient(220px 120px at 50% 120%, rgba(0, 229, 255, 0.22), transparent 70%),
linear-gradient(135deg, var(--panel-2), var(--bg-2));
}
.asset__thumb--logo.asset__thumb--alt {
background:
radial-gradient(220px 120px at 50% 120%, rgba(124, 77, 255, 0.3), transparent 70%),
linear-gradient(135deg, var(--panel-2), var(--bg-2));
}
.asset__thumb--s1 {
background: linear-gradient(160deg, #103244 0%, #0c1a2b 50%, #15102e 100%);
}
.asset__thumb--s2 {
background: linear-gradient(200deg, #2b1030 0%, #14102b 55%, #0c1622 100%);
}
.asset__thumb--s3 {
background: linear-gradient(135deg, #0e2b22 0%, #0c1a2b 60%, #1a1030 100%);
}
.asset__thumb--art {
background:
radial-gradient(160px 90px at 70% 10%, rgba(255, 61, 113, 0.3), transparent 70%),
linear-gradient(150deg, #241032 0%, #120e26 60%, #0a0b10 100%);
}
.asset__thumb--art.asset__thumb--alt {
background:
radial-gradient(160px 90px at 25% 15%, rgba(255, 200, 87, 0.22), transparent 70%),
linear-gradient(150deg, #14222e 0%, #120e26 70%);
}
.asset__thumb--video {
background:
radial-gradient(200px 110px at 50% 50%, rgba(0, 229, 255, 0.14), transparent 75%),
linear-gradient(150deg, #101a2e 0%, #0a0b10 80%);
}
.asset__thumb::after {
content: "";
position: absolute;
inset: 0;
background: repeating-linear-gradient(0deg, transparent 0 3px, rgba(231, 233, 243, 0.02) 3px 4px);
pointer-events: none;
}
.asset__glyph {
font-family: var(--font-display);
font-weight: 900;
font-size: 2rem;
letter-spacing: 0.08em;
color: var(--accent);
text-shadow: 0 0 18px rgba(0, 229, 255, 0.6);
}
.asset__thumb--alt .asset__glyph {
color: var(--accent-2);
text-shadow: 0 0 18px rgba(124, 77, 255, 0.7);
}
.asset__tag {
position: absolute;
top: 10px;
right: 10px;
padding: 3px 9px;
font-family: var(--font-display);
font-size: 0.6rem;
font-weight: 700;
letter-spacing: 0.12em;
color: var(--accent);
background: rgba(10, 11, 16, 0.78);
border: 1px solid rgba(0, 229, 255, 0.45);
border-radius: var(--r-sm);
}
.asset__play {
display: grid;
place-items: center;
width: 50px;
height: 50px;
font-size: 1.05rem;
color: var(--text);
background: rgba(10, 11, 16, 0.7);
border: 1px solid var(--line-2);
border-radius: 50%;
box-shadow: var(--glow);
transition: transform 0.2s ease;
}
.asset:hover .asset__play {
transform: scale(1.12);
}
.asset__body {
position: relative;
display: flex;
flex-direction: column;
flex: 1;
gap: 4px;
padding: 14px 16px 16px;
}
.asset__check {
position: absolute;
top: -104px;
left: 10px;
display: grid;
place-items: center;
width: 28px;
height: 28px;
background: rgba(10, 11, 16, 0.78);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
cursor: pointer;
}
.asset__name {
margin: 0;
font-size: 0.92rem;
font-weight: 700;
}
.asset__meta {
margin: 0 0 10px;
font-size: 0.78rem;
color: var(--muted);
}
.asset__actions {
display: flex;
align-items: center;
gap: 8px;
margin-top: auto;
}
.dl-btn {
flex: 1;
}
.dl-btn.is-done {
color: var(--success);
border-color: rgba(54, 226, 122, 0.55);
box-shadow: 0 0 14px rgba(54, 226, 122, 0.25);
}
.iconbtn {
display: grid;
place-items: center;
width: 34px;
height: 34px;
flex: none;
font-size: 0.95rem;
color: var(--muted);
background: transparent;
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
cursor: pointer;
transition: color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.iconbtn:hover {
color: var(--accent);
border-color: var(--accent);
box-shadow: var(--glow);
}
.iconbtn.is-copied {
color: var(--success);
border-color: var(--success);
box-shadow: 0 0 12px rgba(54, 226, 122, 0.4);
}
.asset-empty {
margin: 18px 0 0;
padding: 22px;
text-align: center;
color: var(--muted);
border: 1px dashed var(--line-2);
border-radius: var(--r-md);
}
/* ===== Awards & quotes band ===== */
.band {
position: relative;
padding: 26px 28px;
background:
linear-gradient(120deg, rgba(124, 77, 255, 0.12), transparent 50%),
linear-gradient(180deg, var(--panel), var(--bg-2));
border: 1px solid rgba(124, 77, 255, 0.3);
border-radius: var(--r-lg);
clip-path: polygon(18px 0, 100% 0, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%, 0 18px);
}
.band__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-bottom: 22px;
}
.award {
padding: 16px 14px;
text-align: center;
background: rgba(10, 11, 16, 0.45);
border: 1px solid var(--line);
border-radius: var(--r-md);
transition: border-color 0.2s ease, box-shadow 0.25s ease, transform 0.2s ease;
}
.award:hover {
transform: translateY(-2px);
border-color: rgba(255, 200, 87, 0.5);
box-shadow: 0 0 18px rgba(255, 200, 87, 0.14);
}
.award__laurel {
font-size: 1.4rem;
color: var(--warn);
text-shadow: 0 0 14px rgba(255, 200, 87, 0.55);
}
.award__name {
margin: 8px 0 2px;
font-family: var(--font-display);
font-size: 0.76rem;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.award__org {
margin: 0;
font-size: 0.78rem;
color: var(--muted);
}
.quotes {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.quote {
margin: 0;
padding: 16px 18px;
background: rgba(10, 11, 16, 0.45);
border-left: 3px solid var(--accent);
border-radius: 0 var(--r-md) var(--r-md) 0;
}
.quote p {
margin: 0 0 8px;
font-size: 0.97rem;
font-style: italic;
}
.quote cite {
font-size: 0.78rem;
font-style: normal;
color: var(--muted);
}
/* ===== Contact + review form ===== */
.contact-list {
list-style: none;
margin: 0 0 18px;
padding: 0;
}
.contact-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 11px 2px;
border-bottom: 1px solid var(--line);
font-size: 0.92rem;
}
.contact-row:last-child {
border-bottom: none;
}
.contact-row__label {
color: var(--muted);
font-size: 0.82rem;
}
.socials {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.social {
padding: 7px 13px;
font-size: 0.78rem;
font-weight: 600;
color: var(--muted);
text-decoration: none;
background: var(--bg-2);
border: 1px solid var(--line-2);
border-radius: 999px;
transition: color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
.social:hover {
color: var(--accent);
border-color: var(--accent);
box-shadow: var(--glow);
}
.review-form {
display: grid;
gap: 14px;
margin: 16px 0 12px;
}
.field {
display: grid;
gap: 6px;
}
.field__label {
font-family: var(--font-display);
font-size: 0.66rem;
font-weight: 500;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
}
.field__input {
padding: 11px 13px;
font: inherit;
font-size: 0.92rem;
color: var(--text);
background: var(--bg);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.field__input::placeholder {
color: rgba(154, 160, 191, 0.55);
}
.field__input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(0, 229, 255, 0.16);
}
.field__input.is-invalid {
border-color: var(--danger);
box-shadow: 0 0 0 3px rgba(255, 77, 77, 0.16);
animation: shake 0.3s ease;
}
@keyframes shake {
25% {
transform: translateX(-4px);
}
75% {
transform: translateX(4px);
}
}
.fineprint {
margin: 0;
font-size: 0.76rem;
color: var(--muted);
}
/* ===== Footer ===== */
.pk-footer {
margin-top: 34px;
padding-top: 20px;
border-top: 1px solid var(--line);
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 10px;
font-size: 0.8rem;
color: var(--muted);
}
.pk-footer p {
margin: 0;
}
.mono {
font-family: "SFMono-Regular", Consolas, monospace;
color: var(--accent);
}
/* ===== Toast ===== */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 16px);
max-width: min(420px, calc(100vw - 40px));
padding: 12px 20px;
font-size: 0.88rem;
font-weight: 600;
color: var(--text);
background: var(--panel-2);
border: 1px solid rgba(0, 229, 255, 0.5);
border-radius: var(--r-md);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), var(--glow);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease, transform 0.25s ease;
z-index: 50;
}
.toast.is-visible {
opacity: 1;
transform: translate(-50%, 0);
}
/* ===== Responsive ===== */
@media (max-width: 880px) {
.grid-2 {
grid-template-columns: 1fr;
}
.features {
grid-template-columns: 1fr;
}
.band__grid,
.quotes {
grid-template-columns: 1fr;
}
.pk-header__inner {
grid-template-columns: 1fr;
}
}
@media (max-width: 520px) {
.page {
padding: 16px 12px 44px;
}
.pk-header {
padding: 24px 18px 22px;
clip-path: polygon(16px 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%, 0 100%, 0 16px);
}
.panel {
padding: 18px 16px;
}
.band {
padding: 20px 16px;
}
.pk-header__cta .btn {
width: 100%;
}
.assets-tools {
flex-direction: column;
align-items: stretch;
}
.bulkbar {
justify-content: space-between;
}
.asset-grid {
grid-template-columns: 1fr;
}
.factsheet th {
width: 42%;
}
.contact-row {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}/* Hollow Reign — Press Kit interactions (illustrative demo, no real downloads) */
(() => {
"use strict";
/* ---------- Toast helper ---------- */
const toastEl = document.getElementById("toast");
let toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("is-visible"), 2400);
}
/* ---------- Copy-to-clipboard (contact + asset links) ---------- */
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
return navigator.clipboard.writeText(text);
}
// Fallback for non-secure contexts
const ta = document.createElement("textarea");
ta.value = text;
ta.setAttribute("readonly", "");
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
return Promise.resolve();
}
document.querySelectorAll(".copy-link").forEach((btn) => {
btn.addEventListener("click", () => {
const value = btn.dataset.copy;
copyText(value).then(() => {
btn.classList.add("is-copied");
setTimeout(() => btn.classList.remove("is-copied"), 1200);
toast(`Copied: ${value}`);
});
});
});
/* ---------- Asset filter chips ---------- */
const chips = document.querySelectorAll(".chip[data-filter]");
const assets = Array.from(document.querySelectorAll(".asset"));
const assetEmpty = document.getElementById("assetEmpty");
chips.forEach((chip) => {
chip.addEventListener("click", () => {
chips.forEach((c) => {
c.classList.toggle("is-active", c === chip);
c.setAttribute("aria-pressed", String(c === chip));
});
const filter = chip.dataset.filter;
let visible = 0;
assets.forEach((card) => {
const show = filter === "all" || card.dataset.type === filter;
card.classList.toggle("is-hidden", !show);
if (show) visible++;
});
assetEmpty.hidden = visible > 0;
syncSelectAllState();
});
});
/* ---------- Selection: per-card, select-all, bulk count ---------- */
const selectAll = document.getElementById("selectAll");
const bulkBtn = document.getElementById("downloadSelected");
const bulkCount = document.getElementById("bulkCount");
const checks = Array.from(document.querySelectorAll(".asset-select"));
function visibleChecks() {
return checks.filter((cb) => !cb.closest(".asset").classList.contains("is-hidden"));
}
function selectedChecks() {
return checks.filter((cb) => cb.checked);
}
function syncSelectAllState() {
const vis = visibleChecks();
const visChecked = vis.filter((cb) => cb.checked);
selectAll.checked = vis.length > 0 && visChecked.length === vis.length;
selectAll.indeterminate = visChecked.length > 0 && visChecked.length < vis.length;
updateBulkBar();
}
function updateBulkBar() {
const n = selectedChecks().length;
bulkCount.textContent = `(${n})`;
bulkBtn.disabled = n === 0;
}
checks.forEach((cb) => {
cb.addEventListener("change", () => {
cb.closest(".asset").classList.toggle("is-selected", cb.checked);
syncSelectAllState();
});
});
selectAll.addEventListener("change", () => {
visibleChecks().forEach((cb) => {
cb.checked = selectAll.checked;
cb.closest(".asset").classList.toggle("is-selected", cb.checked);
});
syncSelectAllState();
});
/* ---------- Single download simulation ---------- */
function simulateDownload(btn, name, size, done) {
const original = btn.textContent;
btn.classList.add("is-busy");
btn.setAttribute("aria-busy", "true");
let pct = 0;
const tick = setInterval(() => {
pct = Math.min(100, pct + 12 + Math.floor(Math.random() * 18));
btn.textContent = `${pct}%`;
if (pct >= 100) {
clearInterval(tick);
btn.classList.remove("is-busy");
btn.classList.add("is-done");
btn.removeAttribute("aria-busy");
btn.textContent = "Saved ✓";
toast(`Downloaded ${name} (${size}) — demo only`);
setTimeout(() => {
btn.classList.remove("is-done");
btn.textContent = original;
}, 1800);
if (done) done();
}
}, 140);
}
document.querySelectorAll(".dl-btn").forEach((btn) => {
btn.addEventListener("click", () => {
if (btn.classList.contains("is-busy")) return;
simulateDownload(btn, btn.dataset.name, btn.dataset.size);
});
});
/* ---------- Download selected (bulk simulation) ---------- */
bulkBtn.addEventListener("click", () => {
const queue = selectedChecks().map((cb) => cb.closest(".asset").querySelector(".dl-btn"));
if (!queue.length) return;
bulkBtn.disabled = true;
const originalLabel = bulkBtn.firstChild.textContent;
toast(`Bundling ${queue.length} asset${queue.length > 1 ? "s" : ""}…`);
let i = 0;
function next() {
if (i >= queue.length) {
bulkBtn.firstChild.textContent = originalLabel;
toast("All selected assets downloaded — demo only");
// Clear selection
checks.forEach((cb) => {
cb.checked = false;
cb.closest(".asset").classList.remove("is-selected");
});
syncSelectAllState();
return;
}
const btn = queue[i++];
bulkBtn.firstChild.textContent = `Downloading ${i}/${queue.length} `;
simulateDownload(btn, btn.dataset.name, btn.dataset.size, next);
}
next();
});
/* ---------- Review copy form ---------- */
const form = document.getElementById("reviewForm");
const outletInput = document.getElementById("outletInput");
const emailInput = document.getElementById("emailInput");
const submitBtn = document.getElementById("reviewSubmit");
function flagInvalid(input) {
input.classList.add("is-invalid");
input.addEventListener("input", () => input.classList.remove("is-invalid"), { once: true });
}
form.addEventListener("submit", (e) => {
e.preventDefault();
const outlet = outletInput.value.trim();
const email = emailInput.value.trim();
let ok = true;
if (!outlet) {
flagInvalid(outletInput);
ok = false;
}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
flagInvalid(emailInput);
ok = false;
}
if (!ok) {
toast("Add your outlet and a valid work email");
return;
}
submitBtn.classList.add("is-busy");
submitBtn.textContent = "Sending…";
setTimeout(() => {
submitBtn.classList.remove("is-busy");
submitBtn.textContent = "Request Review Copy";
form.reset();
toast(`Request received for ${outlet} — keys ship in the next wave`);
}, 900);
});
/* ---------- Header CTA scrolls to the form ---------- */
document.getElementById("reviewCopyTop").addEventListener("click", () => {
form.scrollIntoView({ behavior: "smooth", block: "center" });
setTimeout(() => outletInput.focus({ preventScroll: true }), 450);
});
/* Initial state */
syncSelectAllState();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Hollow Reign — Press Kit | Nullforge</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="page">
<!-- ===== Header ===== -->
<header class="pk-header">
<div class="pk-header__scan" aria-hidden="true"></div>
<div class="pk-header__inner">
<div class="pk-logo" aria-hidden="true">
<svg viewBox="0 0 64 64" width="72" height="72" role="img">
<polygon points="32,4 60,20 60,44 32,60 4,44 4,20" fill="none" stroke="currentColor" stroke-width="3"/>
<polygon points="32,16 48,25 48,39 32,48 16,39 16,25" fill="currentColor" opacity="0.18"/>
<path d="M22 40 L32 20 L42 40 M26 33 H38" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
</svg>
</div>
<div class="pk-header__text">
<p class="pk-kicker">Press Kit · presskit()</p>
<h1 class="pk-title">Hollow Reign</h1>
<p class="pk-sub">A neon-soaked action roguelike from <strong>Nullforge</strong> — descend the Spire, steal the crown, die gloriously.</p>
<div class="pk-header__cta">
<button class="btn btn--primary" id="reviewCopyTop" type="button">Request Review Copy</button>
<a class="btn btn--ghost" href="#assets">Browse Assets</a>
</div>
</div>
<div class="pk-header__meta" aria-label="Kit status">
<span class="status-dot" aria-hidden="true"></span>
<span>Kit updated <time datetime="2026-06-02">Jun 2, 2026</time></span>
</div>
</div>
</header>
<main class="pk-main">
<!-- ===== Factsheet + Description ===== -->
<div class="grid-2">
<section class="panel panel--clip" aria-labelledby="factsheetTitle">
<h2 class="panel__title" id="factsheetTitle">Factsheet</h2>
<table class="factsheet">
<tbody>
<tr><th scope="row">Developer</th><td>Nullforge — Rotterdam, NL</td></tr>
<tr><th scope="row">Publisher</th><td>Self-published</td></tr>
<tr><th scope="row">Release date</th><td>October 16, 2026</td></tr>
<tr><th scope="row">Platforms</th><td>PC (Steam), PlayStation 5, Xbox Series X|S, Switch 2</td></tr>
<tr><th scope="row">Price</th><td>$24.99 / €24.99 / £19.99</td></tr>
<tr>
<th scope="row">Website</th>
<td>
<button class="copy-link" type="button" data-copy="https://hollowreign.nullforge.dev">
hollowreign.nullforge.dev <span class="copy-link__ico" aria-hidden="true">⧉</span>
</button>
</td>
</tr>
<tr><th scope="row">Engine</th><td>Forgelight 3 (in-house)</td></tr>
<tr><th scope="row">Languages</th><td>EN, FR, DE, ES, PT-BR, JA, KO, ZH</td></tr>
</tbody>
</table>
</section>
<section class="panel" aria-labelledby="descTitle">
<h2 class="panel__title" id="descTitle">Description</h2>
<p class="prose">
<em>Hollow Reign</em> is a fast, brutal action roguelike set inside the Spire of Vael — a
collapsing mega-dungeon ruled by a crown that refuses to stay dead. Every run rewires the
Spire's floors, factions, and physics. Players chain dash-cancels, glaive ricochets, and
stolen royal decrees into combo systems that reward style as much as survival.
</p>
<p class="prose">
Between runs, the Undercroft hub grows with every rescued NPC: a blacksmith who forges
weapons out of your previous deaths, a cartographer who sells rumors, and a choir that
literally sings your build into existence. Permanent progression is narrative-first —
you unlock <strong>story</strong>, not just stats.
</p>
<p class="prose">
Built by a team of nine over four years, <em>Hollow Reign</em> mixes hand-painted 2.5D art
with a reactive synthwave score by composer Mira Voss that escalates with your combo meter.
</p>
</section>
</div>
<!-- ===== Features ===== -->
<section class="panel" aria-labelledby="featTitle">
<h2 class="panel__title" id="featTitle">Features</h2>
<ul class="features">
<li><span class="features__tick" aria-hidden="true">▸</span> 7 procedurally remixed biomes with 200+ hand-authored rooms</li>
<li><span class="features__tick" aria-hidden="true">▸</span> 12 weapon classes, each with a style-meter that mutates movesets mid-run</li>
<li><span class="features__tick" aria-hidden="true">▸</span> Royal Decree system — break the game's rules using laws stolen from bosses</li>
<li><span class="features__tick" aria-hidden="true">▸</span> Reactive synthwave score that layers with your combo multiplier</li>
<li><span class="features__tick" aria-hidden="true">▸</span> Narrative permadeath: every death is canon, remembered by NPCs</li>
<li><span class="features__tick" aria-hidden="true">▸</span> Daily Spire challenge with global leaderboards and ghost replays</li>
<li><span class="features__tick" aria-hidden="true">▸</span> Full controller, keyboard remapping, and extensive accessibility options</li>
<li><span class="features__tick" aria-hidden="true">▸</span> No microtransactions. One price, whole crown.</li>
</ul>
</section>
<!-- ===== Assets ===== -->
<section class="panel" id="assets" aria-labelledby="assetsTitle">
<div class="assets-head">
<h2 class="panel__title" id="assetsTitle">Downloadable Assets</h2>
<div class="assets-tools">
<div class="filterbar" role="tablist" aria-label="Filter assets">
<button class="chip is-active" type="button" data-filter="all" aria-pressed="true">All</button>
<button class="chip" type="button" data-filter="logo" aria-pressed="false">Logos</button>
<button class="chip" type="button" data-filter="screen" aria-pressed="false">Screenshots</button>
<button class="chip" type="button" data-filter="art" aria-pressed="false">Key Art & Video</button>
</div>
<div class="bulkbar">
<label class="selectall">
<input type="checkbox" id="selectAll" />
<span>Select all</span>
</label>
<button class="btn btn--accent btn--sm" id="downloadSelected" type="button" disabled>
Download selected <span class="bulk-count" id="bulkCount">(0)</span>
</button>
</div>
</div>
</div>
<div class="asset-grid" id="assetGrid">
<article class="asset" data-type="logo">
<div class="asset__thumb asset__thumb--logo" aria-hidden="true"><span class="asset__glyph">HR</span></div>
<div class="asset__body">
<label class="asset__check"><input type="checkbox" class="asset-select" aria-label="Select Logo pack" /></label>
<h3 class="asset__name">Logo Pack — Light & Dark</h3>
<p class="asset__meta">PNG + SVG · 4.2 MB</p>
<div class="asset__actions">
<button class="btn btn--ghost btn--sm dl-btn" type="button" data-name="HollowReign_LogoPack.zip" data-size="4.2 MB">Download</button>
<button class="iconbtn copy-link" type="button" data-copy="https://press.nullforge.dev/hr/logo-pack.zip" aria-label="Copy link to logo pack">⧉</button>
</div>
</div>
</article>
<article class="asset" data-type="logo">
<div class="asset__thumb asset__thumb--logo asset__thumb--alt" aria-hidden="true"><span class="asset__glyph">NF</span></div>
<div class="asset__body">
<label class="asset__check"><input type="checkbox" class="asset-select" aria-label="Select Nullforge studio logo" /></label>
<h3 class="asset__name">Nullforge Studio Logo</h3>
<p class="asset__meta">PNG + EPS · 1.8 MB</p>
<div class="asset__actions">
<button class="btn btn--ghost btn--sm dl-btn" type="button" data-name="Nullforge_Logo.zip" data-size="1.8 MB">Download</button>
<button class="iconbtn copy-link" type="button" data-copy="https://press.nullforge.dev/nullforge-logo.zip" aria-label="Copy link to studio logo">⧉</button>
</div>
</div>
</article>
<article class="asset" data-type="screen">
<div class="asset__thumb asset__thumb--s1" aria-hidden="true"><span class="asset__tag">4K</span></div>
<div class="asset__body">
<label class="asset__check"><input type="checkbox" class="asset-select" aria-label="Select screenshot set: Spire Ascent" /></label>
<h3 class="asset__name">Screens — Spire Ascent (x12)</h3>
<p class="asset__meta">PNG 3840×2160 · 96 MB</p>
<div class="asset__actions">
<button class="btn btn--ghost btn--sm dl-btn" type="button" data-name="HR_Screens_Ascent.zip" data-size="96 MB">Download</button>
<button class="iconbtn copy-link" type="button" data-copy="https://press.nullforge.dev/hr/screens-ascent.zip" aria-label="Copy link to Spire Ascent screenshots">⧉</button>
</div>
</div>
</article>
<article class="asset" data-type="screen">
<div class="asset__thumb asset__thumb--s2" aria-hidden="true"><span class="asset__tag">4K</span></div>
<div class="asset__body">
<label class="asset__check"><input type="checkbox" class="asset-select" aria-label="Select screenshot set: Boss Gallery" /></label>
<h3 class="asset__name">Screens — Boss Gallery (x9)</h3>
<p class="asset__meta">PNG 3840×2160 · 74 MB</p>
<div class="asset__actions">
<button class="btn btn--ghost btn--sm dl-btn" type="button" data-name="HR_Screens_Bosses.zip" data-size="74 MB">Download</button>
<button class="iconbtn copy-link" type="button" data-copy="https://press.nullforge.dev/hr/screens-bosses.zip" aria-label="Copy link to Boss Gallery screenshots">⧉</button>
</div>
</div>
</article>
<article class="asset" data-type="screen">
<div class="asset__thumb asset__thumb--s3" aria-hidden="true"><span class="asset__tag">GIF</span></div>
<div class="asset__body">
<label class="asset__check"><input type="checkbox" class="asset-select" aria-label="Select gameplay GIF pack" /></label>
<h3 class="asset__name">Gameplay GIF Pack (x6)</h3>
<p class="asset__meta">GIF 1280×720 · 38 MB</p>
<div class="asset__actions">
<button class="btn btn--ghost btn--sm dl-btn" type="button" data-name="HR_GIF_Pack.zip" data-size="38 MB">Download</button>
<button class="iconbtn copy-link" type="button" data-copy="https://press.nullforge.dev/hr/gif-pack.zip" aria-label="Copy link to GIF pack">⧉</button>
</div>
</div>
</article>
<article class="asset" data-type="art">
<div class="asset__thumb asset__thumb--art" aria-hidden="true"><span class="asset__tag">KEY ART</span></div>
<div class="asset__body">
<label class="asset__check"><input type="checkbox" class="asset-select" aria-label="Select key art" /></label>
<h3 class="asset__name">Key Art — Crown of Vael</h3>
<p class="asset__meta">PSD + PNG 7680×4320 · 412 MB</p>
<div class="asset__actions">
<button class="btn btn--ghost btn--sm dl-btn" type="button" data-name="HR_KeyArt.zip" data-size="412 MB">Download</button>
<button class="iconbtn copy-link" type="button" data-copy="https://press.nullforge.dev/hr/key-art.zip" aria-label="Copy link to key art">⧉</button>
</div>
</div>
</article>
<article class="asset" data-type="art">
<div class="asset__thumb asset__thumb--video" aria-hidden="true">
<span class="asset__play">▶</span>
<span class="asset__tag">1:48</span>
</div>
<div class="asset__body">
<label class="asset__check"><input type="checkbox" class="asset-select" aria-label="Select announce trailer" /></label>
<h3 class="asset__name">Announce Trailer (ProRes)</h3>
<p class="asset__meta">MOV 3840×2160 · 2.4 GB</p>
<div class="asset__actions">
<button class="btn btn--ghost btn--sm dl-btn" type="button" data-name="HR_Trailer_ProRes.mov" data-size="2.4 GB">Download</button>
<button class="iconbtn copy-link" type="button" data-copy="https://press.nullforge.dev/hr/trailer-prores.mov" aria-label="Copy link to announce trailer">⧉</button>
</div>
</div>
</article>
<article class="asset" data-type="art">
<div class="asset__thumb asset__thumb--art asset__thumb--alt" aria-hidden="true"><span class="asset__tag">CONCEPT</span></div>
<div class="asset__body">
<label class="asset__check"><input type="checkbox" class="asset-select" aria-label="Select concept art pack" /></label>
<h3 class="asset__name">Concept Art Pack (x18)</h3>
<p class="asset__meta">JPG · 64 MB</p>
<div class="asset__actions">
<button class="btn btn--ghost btn--sm dl-btn" type="button" data-name="HR_Concept_Art.zip" data-size="64 MB">Download</button>
<button class="iconbtn copy-link" type="button" data-copy="https://press.nullforge.dev/hr/concept-art.zip" aria-label="Copy link to concept art">⧉</button>
</div>
</div>
</article>
</div>
<p class="asset-empty" id="assetEmpty" hidden>No assets in this category.</p>
</section>
<!-- ===== Awards & Quotes ===== -->
<section class="band" aria-labelledby="awardsTitle">
<h2 class="panel__title" id="awardsTitle">Awards & Selected Press</h2>
<div class="band__grid">
<div class="award">
<span class="award__laurel" aria-hidden="true">❧</span>
<p class="award__name">Best in Play — Honorable Mention</p>
<p class="award__org">GDC 2026</p>
</div>
<div class="award">
<span class="award__laurel" aria-hidden="true">❧</span>
<p class="award__name">Official Selection</p>
<p class="award__org">Day of the Devs 2025</p>
</div>
<div class="award">
<span class="award__laurel" aria-hidden="true">❧</span>
<p class="award__name">Audience Award Finalist</p>
<p class="award__org">IndieCade 2025</p>
</div>
</div>
<div class="quotes">
<blockquote class="quote">
<p>“The most stylish death I've had all year — combat that reads like calligraphy.”</p>
<cite>— Polyhedron Weekly, hands-on preview</cite>
</blockquote>
<blockquote class="quote">
<p>“Nullforge treats permadeath as storytelling, and it's quietly revolutionary.”</p>
<cite>— The Crit Lounge</cite>
</blockquote>
</div>
</section>
<!-- ===== Contact + Review Copy ===== -->
<div class="grid-2">
<section class="panel" aria-labelledby="contactTitle">
<h2 class="panel__title" id="contactTitle">Press Contact</h2>
<ul class="contact-list">
<li class="contact-row">
<span class="contact-row__label">Press inquiries</span>
<button class="copy-link" type="button" data-copy="[email protected]">[email protected] <span class="copy-link__ico" aria-hidden="true">⧉</span></button>
</li>
<li class="contact-row">
<span class="contact-row__label">Studio contact</span>
<button class="copy-link" type="button" data-copy="[email protected]">[email protected] <span class="copy-link__ico" aria-hidden="true">⧉</span></button>
</li>
<li class="contact-row">
<span class="contact-row__label">Creator codes</span>
<button class="copy-link" type="button" data-copy="[email protected]">[email protected] <span class="copy-link__ico" aria-hidden="true">⧉</span></button>
</li>
</ul>
<div class="socials" aria-label="Social channels">
<a class="social" href="#" aria-label="Hollow Reign on Bluesky">@hollowreign.bsky</a>
<a class="social" href="#" aria-label="Nullforge on YouTube">yt/nullforge</a>
<a class="social" href="#" aria-label="Hollow Reign Discord">discord.gg/spire</a>
<a class="social" href="#" aria-label="Hollow Reign on Steam">steam/hollowreign</a>
</div>
</section>
<section class="panel panel--cta" aria-labelledby="reviewTitle">
<h2 class="panel__title" id="reviewTitle">Request a Review Copy</h2>
<p class="prose">Verified press and creators get Steam / console codes ahead of launch. Codes go out in monthly waves; embargo details ship with each key.</p>
<form class="review-form" id="reviewForm" novalidate>
<label class="field">
<span class="field__label">Outlet / channel</span>
<input class="field__input" type="text" id="outletInput" name="outlet" placeholder="e.g. Polyhedron Weekly" autocomplete="organization" />
</label>
<label class="field">
<span class="field__label">Work email</span>
<input class="field__input" type="email" id="emailInput" name="email" placeholder="[email protected]" autocomplete="email" />
</label>
<button class="btn btn--primary btn--block" type="submit" id="reviewSubmit">Request Review Copy</button>
</form>
<p class="fineprint">We reply within 3 business days. Keys are for coverage, not resale.</p>
</section>
</div>
</main>
<footer class="pk-footer">
<p>© 2026 Nullforge BV. <em>Hollow Reign</em>™. Press kit assets may be used freely for coverage purposes.</p>
<p class="pk-footer__kit">Built in the spirit of <span class="mono">presskit()</span></p>
</footer>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Press Kit (presskit() style)
A complete press kit page in the spirit of presskit(), themed for the fictional action roguelike Hollow Reign by studio Nullforge. The header pairs an animated SVG logo and scanline accent with a gradient Orbitron title and a “Request Review Copy” CTA. Below it sit a factsheet table (developer, release date, platforms, price, engine, languages), a long-form description, and a two-column features list with neon-edged rows.
The downloadable assets section is the interactive core: filter chips (All / Logos / Screenshots / Key Art & Video) show and hide asset cards, each card has a per-asset checkbox, a simulated download button that animates a percentage fill before flashing “Saved”, and a copy-link icon button. A select-all toggle keeps an indeterminate state in sync with visible cards, and “Download selected” runs the queue one asset at a time with progress in the button label.
Rounding it out: an awards and press-quotes band with laurel cards, a press contact list where every email copies to the clipboard with a toast confirmation, social pills, and a review-copy request form with inline validation (shake on invalid fields) and a simulated submit. Fully responsive down to 360px with keyboard focus rings throughout.
Illustrative UI only — fictional games, studios, characters, and data. Not engine integrations.