The pieces finally meet: procgen dungeons + the baked EoB atlas pipeline + the Vigor combat engine become one game. Three fixed pre-baked floors (seeds 7 → 69 → 59, 208 frames), monsters/loot/encounters rerolled per run and composited over the empty scenes; the Ember Sovereign waits at the bottom. - game-wasm: run state machine in Rust (manifest nav graph as dungeon truth, seeded spawning by theme/depth, embedded combat-core Encounter, vigor/fatigue persistence between fights, rest/potions/relics/flee, permadeath + victory). 8 native tests incl. a whole-run bot and a ScriptedPlayer boss-winnability probe used for tuning. - game-web: no-framework front-end — title/death/victory screens, baked frames with screen-blend monster portraits, combat overlay (dual vigor bars, cast telegraphs, cooldown sweeps, floaters), minimap with fog, all SFX synthesized in WebAudio. - balance: floor-scaled levels with a hero's edge over trash, no packs on floor 1, boss tuned by sim sweep (~35%/55% win at skill 50/80). - campaign atlases now versioned (tools/.gitignore exception); boss portrait added to the Z-Image roster. Verified end-to-end in-browser: an autopilot beat the full game through the real UI (10 fights, 2 flees, Sovereign down), and died plenty before that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
384 lines
16 KiB
CSS
384 lines
16 KiB
CSS
/* REIKHELM: DESCENT — torchlit retro UI over the Primordyn-dithered frames.
|
|
Palette pulls from the baked atlas: deep near-black stone, warm torch gold,
|
|
ember orange, bone parchment, and a cold cistern teal for accents. */
|
|
|
|
:root {
|
|
--void: #0a0908;
|
|
--stone: #15131a;
|
|
--stone-2: #221e26;
|
|
--line: #3a3340;
|
|
--bone: #e8dcc8;
|
|
--dim: #8d8273;
|
|
--gold: #d4a54a;
|
|
--ember: #e06a28;
|
|
--blood: #b03a2e;
|
|
--teal: #5a9aa8;
|
|
--vigor: #d8b95e;
|
|
--vigor-deep: #7a6228;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
html, body {
|
|
height: 100%;
|
|
background: var(--void);
|
|
color: var(--bone);
|
|
font-family: "Menlo", "Consolas", "Courier New", monospace;
|
|
font-size: 14px;
|
|
overflow: hidden;
|
|
user-select: none;
|
|
}
|
|
|
|
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
|
|
.dim { color: var(--dim); }
|
|
.kb {
|
|
font-size: 10px; color: var(--dim); border: 1px solid var(--line);
|
|
border-radius: 3px; padding: 0 4px; margin-left: 6px; letter-spacing: 1px;
|
|
}
|
|
|
|
/* ---------------- screens ---------------- */
|
|
.screen {
|
|
position: absolute; inset: 0; display: none;
|
|
align-items: center; justify-content: center;
|
|
}
|
|
.screen.show { display: flex; }
|
|
|
|
/* ---------------- title ---------------- */
|
|
#title { background: radial-gradient(ellipse at 50% 35%, #1b1410 0%, var(--void) 70%); }
|
|
.title-wrap { text-align: center; animation: rise 0.9s ease-out; }
|
|
@keyframes rise { from { opacity: 0; transform: translateY(18px); } }
|
|
|
|
.wordmark {
|
|
font-size: 72px; letter-spacing: 26px; margin-left: 26px; font-weight: 700;
|
|
color: var(--gold);
|
|
text-shadow: 0 0 18px rgba(224, 106, 40, 0.45), 0 2px 0 #6b4a17, 0 5px 14px #000;
|
|
}
|
|
.subtitle { letter-spacing: 10px; color: var(--ember); margin-top: 6px; font-size: 15px; }
|
|
.tagline { margin: 26px 0 30px; color: var(--dim); line-height: 1.7; }
|
|
|
|
.archetypes { display: flex; gap: 26px; justify-content: center; }
|
|
.arch-card {
|
|
width: 240px; padding: 16px 16px 18px;
|
|
background: linear-gradient(180deg, var(--stone-2), var(--stone));
|
|
border: 1px solid var(--line); border-radius: 6px;
|
|
display: flex; flex-direction: column; align-items: center; gap: 10px;
|
|
transition: transform 0.15s, border-color 0.15s, box-shadow 0.15s;
|
|
}
|
|
.arch-card:hover {
|
|
transform: translateY(-4px); border-color: var(--gold);
|
|
box-shadow: 0 8px 30px rgba(212, 165, 74, 0.18);
|
|
}
|
|
.arch-card img {
|
|
width: 168px; height: 168px; object-fit: cover; border-radius: 4px;
|
|
border: 1px solid var(--line); image-rendering: auto;
|
|
}
|
|
.arch-name { color: var(--gold); letter-spacing: 3px; font-size: 16px; }
|
|
.arch-blurb { color: var(--dim); font-size: 12px; line-height: 1.6; }
|
|
|
|
.seed-row { margin-top: 30px; display: flex; gap: 10px; justify-content: center; align-items: center; }
|
|
.seed { color: var(--teal); letter-spacing: 2px; }
|
|
#reroll { color: var(--dim); font-size: 18px; }
|
|
#reroll:hover { color: var(--gold); }
|
|
.hint { margin-top: 14px; font-size: 12px; letter-spacing: 2px; }
|
|
|
|
/* ---------------- stage ---------------- */
|
|
#game { background: var(--void); }
|
|
#stage {
|
|
position: relative;
|
|
width: min(100vw, calc(100vh * 16 / 9));
|
|
aspect-ratio: 16 / 9;
|
|
background: #000;
|
|
overflow: hidden;
|
|
box-shadow: 0 0 80px rgba(0,0,0,0.9);
|
|
}
|
|
#frame {
|
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
image-rendering: pixelated;
|
|
object-fit: cover;
|
|
}
|
|
#frame-fade {
|
|
position: absolute; inset: 0; background: #000; opacity: 0; pointer-events: none;
|
|
transition: opacity 0.1s linear;
|
|
}
|
|
#frame-fade.dip { opacity: 1; transition: none; }
|
|
#vignette {
|
|
position: absolute; inset: 0; pointer-events: none;
|
|
background: radial-gradient(ellipse at 50% 46%, transparent 52%, rgba(0,0,0,0.55) 100%);
|
|
}
|
|
#danger-vignette {
|
|
position: absolute; inset: 0; pointer-events: none; opacity: 0;
|
|
background: radial-gradient(ellipse at 50% 50%, transparent 58%, rgba(140, 30, 20, 0.35) 100%);
|
|
transition: opacity 1.2s;
|
|
}
|
|
#danger-vignette.on { opacity: 1; animation: dangerpulse 2.6s ease-in-out infinite; }
|
|
@keyframes dangerpulse { 50% { opacity: 0.45; } }
|
|
|
|
/* ---------------- monsters ---------------- */
|
|
#sprites { position: absolute; inset: 0; pointer-events: none; }
|
|
.sprite {
|
|
position: absolute; bottom: 16%; left: 50%; pointer-events: auto; cursor: pointer;
|
|
transform: translateX(-50%);
|
|
display: flex; flex-direction: column; align-items: center;
|
|
}
|
|
.sprite img {
|
|
width: 30vmin; height: 30vmin; object-fit: cover;
|
|
mix-blend-mode: screen; /* portraits sit on near-black — melts into the scene */
|
|
filter: drop-shadow(0 18px 26px rgba(0,0,0,0.8)) contrast(1.04);
|
|
animation: sway 3.2s ease-in-out infinite;
|
|
image-rendering: auto;
|
|
/* feather the square portrait into the scene */
|
|
-webkit-mask-image: radial-gradient(ellipse 50% 50% at 50% 46%, #000 58%, transparent 82%);
|
|
mask-image: radial-gradient(ellipse 50% 50% at 50% 46%, #000 58%, transparent 82%);
|
|
}
|
|
.sprite.slot-1 img { animation-delay: -1.6s; }
|
|
.sprite.enter img { animation: lunge 0.45s ease-out, sway 3.2s ease-in-out 0.45s infinite; }
|
|
@keyframes lunge {
|
|
from { opacity: 0; transform: scale(0.6) translateY(8%); }
|
|
60% { opacity: 1; transform: scale(1.06); }
|
|
to { transform: scale(1); }
|
|
}
|
|
@keyframes sway { 50% { transform: translateY(-1.6%) scale(1.012); } }
|
|
.sprite.hurt img { animation: hurtflash 0.28s ease-out, sway 3.2s ease-in-out 0.28s infinite; }
|
|
@keyframes hurtflash {
|
|
0% { filter: brightness(2.4) sepia(1) hue-rotate(-28deg) saturate(3); transform: translateX(-2%); }
|
|
50% { transform: translateX(2%); }
|
|
}
|
|
.sprite.dazed img { filter: drop-shadow(0 18px 26px rgba(0,0,0,0.8)) saturate(0.6) brightness(0.85); animation: dazedwobble 1.4s ease-in-out infinite; }
|
|
@keyframes dazedwobble { 25% { transform: rotate(-2.4deg); } 75% { transform: rotate(2.4deg); } }
|
|
.sprite.dead img {
|
|
animation: collapse 0.9s ease-in forwards; mix-blend-mode: screen;
|
|
}
|
|
@keyframes collapse {
|
|
to { opacity: 0; transform: translateY(24%) scale(0.82); filter: saturate(0) brightness(0.4); }
|
|
}
|
|
.sprite .tgt {
|
|
margin-top: 4px; color: var(--gold); font-size: 16px; opacity: 0;
|
|
text-shadow: 0 0 8px var(--ember);
|
|
}
|
|
.sprite.targeted .tgt { opacity: 1; animation: tgtbob 1s ease-in-out infinite; }
|
|
@keyframes tgtbob { 50% { transform: translateY(-3px); } }
|
|
|
|
/* ---------------- floaters ---------------- */
|
|
#floaters { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
|
|
.floater {
|
|
position: absolute; font-weight: 700; font-size: 22px;
|
|
text-shadow: 0 2px 0 #000, 0 0 12px rgba(0,0,0,0.8);
|
|
animation: floatup 1.1s ease-out forwards;
|
|
}
|
|
.floater.dmg { color: var(--bone); }
|
|
.floater.big { color: var(--ember); font-size: 30px; }
|
|
.floater.ouch { color: var(--blood); }
|
|
.floater.heal { color: #7fc06a; }
|
|
.floater.curse { color: #b07ad0; }
|
|
.floater.info { color: var(--teal); font-size: 16px; font-weight: 400; }
|
|
@keyframes floatup {
|
|
from { opacity: 0; transform: translateY(10px) scale(0.8); }
|
|
20% { opacity: 1; transform: translateY(0) scale(1.06); }
|
|
to { opacity: 0; transform: translateY(-58px) scale(1); }
|
|
}
|
|
|
|
/* ---------------- banner / floor card ---------------- */
|
|
#banner {
|
|
position: absolute; top: 24%; left: 0; right: 0; text-align: center;
|
|
font-size: 30px; letter-spacing: 8px; color: var(--blood); font-weight: 700;
|
|
text-shadow: 0 0 18px rgba(176, 58, 46, 0.7), 0 2px 0 #000;
|
|
opacity: 0; pointer-events: none;
|
|
}
|
|
#banner.show { animation: bannerin 1.6s ease-out; }
|
|
@keyframes bannerin { 8% { opacity: 1; } 70% { opacity: 1; } 100% { opacity: 0; } }
|
|
|
|
#floorcard {
|
|
position: absolute; inset: 0; background: #000;
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
|
|
opacity: 0; pointer-events: none;
|
|
}
|
|
#floorcard.show { animation: floorcard 2.4s ease-in-out; }
|
|
@keyframes floorcard { 12% { opacity: 1; } 78% { opacity: 1; } 100% { opacity: 0; } }
|
|
#floorcard-depth { color: var(--dim); letter-spacing: 6px; font-size: 14px; }
|
|
#floorcard-name { color: var(--gold); letter-spacing: 8px; font-size: 30px; }
|
|
|
|
/* ---------------- enemy panels ---------------- */
|
|
#enemies {
|
|
position: absolute; top: 12px; left: 50%; transform: translateX(-50%);
|
|
display: flex; gap: 14px;
|
|
}
|
|
.epanel {
|
|
width: 280px; padding: 8px 10px;
|
|
background: rgba(10, 9, 8, 0.82);
|
|
border: 1px solid var(--line); border-radius: 5px;
|
|
cursor: pointer;
|
|
transition: border-color 0.15s;
|
|
}
|
|
.epanel.targeted { border-color: var(--gold); }
|
|
.epanel .ehead { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 5px; }
|
|
.epanel .ename { color: var(--bone); letter-spacing: 1px; font-size: 13px; }
|
|
.epanel .elvl { color: var(--dim); font-size: 11px; }
|
|
.epanel.dead { opacity: 0.45; filter: saturate(0); }
|
|
.estatus { margin-top: 4px; font-size: 10px; height: 13px; color: var(--dim); letter-spacing: 1px; }
|
|
.estatus .chip { color: #b07ad0; margin-right: 7px; }
|
|
.estatus .chip.daze { color: var(--gold); }
|
|
|
|
/* ---------------- vigor bars ---------------- */
|
|
.vbar {
|
|
position: relative; height: 16px;
|
|
background: #060505;
|
|
border: 1px solid var(--line); border-radius: 3px; overflow: hidden;
|
|
}
|
|
.vbar-cap { position: absolute; inset: 0; width: 100%; background: #241f14; }
|
|
.vbar-fill {
|
|
position: absolute; top: 0; bottom: 0; left: 0; width: 100%;
|
|
background: linear-gradient(180deg, var(--vigor), var(--vigor-deep));
|
|
transition: width 0.18s ease-out;
|
|
}
|
|
.vbar-ceiling {
|
|
position: absolute; top: 0; bottom: 0; width: 2px; background: var(--gold);
|
|
box-shadow: 0 0 6px var(--gold); transition: left 0.3s;
|
|
}
|
|
.vbar-num {
|
|
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
font-size: 10px; color: rgba(10, 8, 4, 0.9); font-weight: 700; letter-spacing: 1px;
|
|
text-shadow: 0 0 4px rgba(232, 220, 200, 0.35);
|
|
}
|
|
.vbar.hurtflash { animation: barflash 0.25s; }
|
|
@keyframes barflash { from { box-shadow: 0 0 16px var(--blood); } }
|
|
|
|
.castbar {
|
|
height: 5px; margin-top: 3px; border-radius: 2px; background: #100d12; overflow: hidden;
|
|
}
|
|
.castbar div {
|
|
height: 100%; width: 0%;
|
|
background: linear-gradient(90deg, #6c4f8f, #b07ad0);
|
|
}
|
|
.castbar.player div { background: linear-gradient(90deg, #2e6f7e, var(--teal)); }
|
|
|
|
/* ---------------- HUD ---------------- */
|
|
#hud-top {
|
|
position: absolute; top: 10px; left: 14px; right: 14px;
|
|
display: flex; justify-content: space-between; pointer-events: none;
|
|
text-shadow: 0 1px 0 #000, 0 0 10px #000;
|
|
}
|
|
#floor-label { color: var(--dim); letter-spacing: 3px; font-size: 12px; }
|
|
#floor-label b { color: var(--bone); }
|
|
#compass { color: var(--gold); letter-spacing: 2px; font-size: 13px; }
|
|
|
|
#hud {
|
|
position: absolute; left: 0; right: 0; bottom: 0;
|
|
display: flex; align-items: flex-end; justify-content: space-between;
|
|
padding: 10px 12px; gap: 12px;
|
|
background: linear-gradient(180deg, transparent, rgba(5, 4, 4, 0.88) 55%);
|
|
}
|
|
|
|
#log { width: 300px; font-size: 11.5px; line-height: 1.65; min-height: 56px; pointer-events: none; }
|
|
#log .line { color: var(--dim); text-shadow: 0 1px 0 #000; }
|
|
#log .line:last-child { color: var(--bone); }
|
|
#log .line.good { color: var(--gold); }
|
|
#log .line.bad { color: var(--blood); }
|
|
|
|
#player-panel { flex: 1; max-width: 560px; }
|
|
#player-vbar { height: 20px; }
|
|
#player-cast.castbar { display: none; }
|
|
#player-cast.castbar.on { display: block; }
|
|
|
|
#belt { display: flex; gap: 16px; margin-top: 7px; justify-content: center; font-size: 13px; }
|
|
.belt-item { color: var(--dim); }
|
|
.belt-item b { color: var(--bone); }
|
|
#belt-gold { color: var(--gold); }
|
|
#belt-gold b { color: var(--gold); }
|
|
.belt-item.flash { animation: beltflash 0.5s; }
|
|
@keyframes beltflash { from { text-shadow: 0 0 14px var(--gold); transform: scale(1.25); } }
|
|
|
|
/* ---------------- action bar ---------------- */
|
|
#actionbar { display: none; gap: 8px; margin-top: 8px; justify-content: center; }
|
|
#actionbar.on { display: flex; }
|
|
.slot {
|
|
position: relative; width: 92px; padding: 7px 4px 5px;
|
|
background: linear-gradient(180deg, var(--stone-2), var(--stone));
|
|
border: 1px solid var(--line); border-radius: 4px;
|
|
text-align: center; overflow: hidden;
|
|
transition: border-color 0.1s, transform 0.06s;
|
|
}
|
|
.slot:hover { border-color: var(--dim); }
|
|
.slot:active { transform: translateY(1px); }
|
|
.slot .nm { font-size: 10px; letter-spacing: 0.5px; display: block; color: var(--bone); white-space: nowrap; }
|
|
.slot .cost { font-size: 9px; color: var(--dim); display: block; margin-top: 2px; }
|
|
.slot .num {
|
|
position: absolute; top: 2px; left: 5px; font-size: 9px; color: var(--dim);
|
|
}
|
|
.slot .cd {
|
|
position: absolute; inset: 0; background: rgba(5, 4, 6, 0.78);
|
|
transform-origin: bottom; transform: scaleY(0); pointer-events: none;
|
|
}
|
|
.slot[data-kind="auto"] .nm { color: var(--dim); }
|
|
.slot[data-kind="skill"] .nm { color: var(--vigor); }
|
|
.slot[data-kind="spell"] .nm { color: var(--ember); }
|
|
.slot[data-kind="curse"] .nm { color: #b07ad0; }
|
|
.slot[data-kind="recovery"] .nm { color: #7fc06a; }
|
|
.slot[data-kind="defense"] .nm { color: var(--teal); }
|
|
.slot.locked { opacity: 0.4; }
|
|
|
|
#combat-controls { display: none; gap: 10px; margin-top: 7px; justify-content: center; }
|
|
#combat-controls.on { display: flex; }
|
|
.ctl {
|
|
font-size: 11px; letter-spacing: 2px; color: var(--dim);
|
|
border: 1px solid var(--line); border-radius: 4px; padding: 4px 12px;
|
|
}
|
|
.ctl:hover { color: var(--bone); border-color: var(--dim); }
|
|
.ctl.lit { color: var(--gold); border-color: var(--gold); }
|
|
|
|
/* ---------------- minimap ---------------- */
|
|
#minimap {
|
|
width: 196px; height: 124px;
|
|
border: 1px solid var(--line); border-radius: 4px;
|
|
background: rgba(8, 7, 8, 0.85);
|
|
image-rendering: pixelated;
|
|
}
|
|
|
|
/* ---------------- prompts ---------------- */
|
|
.prompt {
|
|
position: absolute; bottom: 26%; left: 0; right: 0; text-align: center;
|
|
color: var(--gold); letter-spacing: 2px; font-size: 16px;
|
|
text-shadow: 0 0 14px rgba(212, 165, 74, 0.6), 0 2px 0 #000;
|
|
opacity: 0; pointer-events: none; transition: opacity 0.3s;
|
|
}
|
|
.prompt.show { opacity: 1; animation: promptpulse 2s ease-in-out infinite; }
|
|
@keyframes promptpulse { 50% { transform: translateY(-3px); } }
|
|
|
|
#rest-overlay {
|
|
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
background: rgba(0, 0, 0, 0.45); color: var(--teal); letter-spacing: 6px; font-size: 18px;
|
|
opacity: 0; pointer-events: none; transition: opacity 0.25s;
|
|
}
|
|
#rest-overlay.show { opacity: 1; }
|
|
|
|
#keys-hint {
|
|
position: absolute; bottom: 2px; left: 0; right: 0; text-align: center;
|
|
font-size: 10px; letter-spacing: 1.5px; opacity: 0.4; pointer-events: none;
|
|
}
|
|
|
|
/* ---------------- end screens ---------------- */
|
|
#death { background: radial-gradient(ellipse at 50% 40%, #1a0c0a 0%, var(--void) 75%); }
|
|
#victory { background: radial-gradient(ellipse at 50% 38%, #241307 0%, var(--void) 75%); }
|
|
.end-wrap { text-align: center; animation: rise 1.1s ease-out; max-width: 640px; }
|
|
.end-title { font-size: 38px; letter-spacing: 10px; margin-bottom: 12px; }
|
|
.end-title.doom { color: var(--blood); text-shadow: 0 0 24px rgba(176, 58, 46, 0.5), 0 3px 0 #000; }
|
|
.end-title.gold { color: var(--gold); text-shadow: 0 0 24px rgba(224, 106, 40, 0.6), 0 3px 0 #000; }
|
|
.end-sub { color: var(--dim); margin-bottom: 28px; letter-spacing: 1px; }
|
|
#victory-boss {
|
|
width: 200px; height: 200px; object-fit: cover; border-radius: 6px;
|
|
border: 1px solid var(--gold); margin-bottom: 22px;
|
|
box-shadow: 0 0 50px rgba(224, 106, 40, 0.35);
|
|
filter: saturate(0.8) brightness(0.9);
|
|
}
|
|
.stats {
|
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px 30px;
|
|
margin: 0 auto 30px; width: fit-content;
|
|
}
|
|
.stats .stat { text-align: center; }
|
|
.stats .v { font-size: 24px; color: var(--bone); }
|
|
.stats .k { font-size: 10px; color: var(--dim); letter-spacing: 2px; margin-top: 3px; }
|
|
.big-btn {
|
|
border: 1px solid var(--gold); color: var(--gold); border-radius: 4px;
|
|
padding: 12px 34px; letter-spacing: 4px; font-size: 14px;
|
|
transition: background 0.15s, box-shadow 0.15s;
|
|
}
|
|
.big-btn:hover { background: rgba(212, 165, 74, 0.12); box-shadow: 0 0 24px rgba(212, 165, 74, 0.25); }
|