feat(combat): rebuild the combat screen around the reference layout

Move both health bars to a status row at the top of the scene with a
circular portrait beside each, and place full-body sprites for the
player and the monster standing on the location background instead of
square portraits. The Angriff action now uses the ornate HUD frame art,
with the keybind in the frame's own tab.

Sprite and icon derivatives are keyed by monster key so both seeded
monsters resolve; the Aschenratte body art still carries its original
backdrop until a cut-out version replaces it at the same path.
This commit is contained in:
Bastian Wagner
2026-08-19 22:11:07 +02:00
parent 6affb9eddc
commit 92b9f1cd35
12 changed files with 475 additions and 203 deletions

View File

@@ -1,24 +1,27 @@
:host {
display: block;
min-block-size: 100%;
block-size: 100%;
}
.combat-page {
.combat {
display: grid;
grid-template-columns: minmax(0, 1fr) clamp(13rem, 17vw, 17rem);
gap: var(--ar-space-4);
block-size: 100%;
}
.combat-page__scene {
/* ---------- stage ---------- */
.combat__stage {
position: relative;
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: end;
grid-template-rows: auto minmax(0, 1fr) auto;
gap: var(--ar-space-4);
min-block-size: clamp(18rem, 40vh, 26rem);
padding: var(--ar-space-5);
min-block-size: 26rem;
padding: var(--ar-space-4);
overflow: hidden;
border: 1px solid var(--ar-border);
background-color: #151718;
background-color: var(--ar-bg);
background-image: url('/images/backgrounds/Aschestrasse.png');
background-position: center;
background-repeat: no-repeat;
@@ -31,201 +34,370 @@
url('/images/backgrounds/runtime/Aschestrasse-960.jpg') type('image/jpeg') 1x
)
) {
.combat-page__scene {
.combat__stage {
background-image: image-set(
url('/images/backgrounds/runtime/Aschestrasse-960.jpg') type('image/jpeg') 1x
);
}
}
.combat-page__scene::before {
.combat__stage::before {
position: absolute;
z-index: 0;
inset: 0;
content: '';
background: linear-gradient(180deg, rgb(4 6 8 / 0.15), rgb(4 6 8 / 0.6));
background:
linear-gradient(180deg, rgb(4 6 8 / 0.72) 0%, rgb(4 6 8 / 0.1) 26%, rgb(4 6 8 / 0.66) 100%);
pointer-events: none;
}
.combat-page__combatant {
/* ---------- combatant status ---------- */
.combat__status {
position: relative;
z-index: 1;
z-index: 2;
display: grid;
gap: var(--ar-space-2);
justify-items: center;
text-align: center;
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
align-items: start;
gap: var(--ar-space-4);
}
.combat-page__combatant--player {
justify-self: start;
.fighter {
display: flex;
align-items: center;
gap: var(--ar-space-3);
min-inline-size: 0;
}
.combat-page__combatant--monster {
justify-self: end;
.fighter--monster {
justify-content: flex-end;
}
.combat-page__portrait {
inline-size: clamp(6rem, 14vw, 10rem);
block-size: clamp(6rem, 14vw, 10rem);
.fighter__icon {
flex: 0 0 auto;
inline-size: clamp(2.75rem, 5vw, 3.75rem);
block-size: clamp(2.75rem, 5vw, 3.75rem);
object-fit: cover;
border: 2px solid var(--ar-border-highlight);
border-radius: var(--ar-radius-md);
box-shadow: var(--ar-shadow-raised);
border-radius: 50%;
box-shadow:
0 0 0 1px var(--ar-border-highlight),
0 0.3rem 0.9rem rgb(0 0 0 / 0.8);
}
.combat-page__info {
.fighter__meter {
display: grid;
gap: var(--ar-space-1);
min-inline-size: 10rem;
color: var(--ar-text);
text-shadow: 0 0.1rem 0.4rem rgb(0 0 0 / 0.85);
gap: 0.3rem;
min-inline-size: 0;
flex: 1 1 auto;
max-inline-size: 22rem;
}
.combat-page__name {
font-family: Georgia, 'Times New Roman', serif;
font-size: 1.15rem;
.fighter--monster .fighter__meter {
justify-items: end;
text-align: end;
}
.combat-page__level {
color: var(--ar-gold);
font-size: var(--ar-font-sm);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.combat-page__hp {
display: grid;
gap: var(--ar-space-1);
font-size: var(--ar-font-sm);
}
.combat-page__hp-track {
display: block;
inline-size: 100%;
block-size: 0.5rem;
.fighter__name {
margin: 0;
overflow: hidden;
border: 1px solid var(--ar-border);
border-radius: var(--ar-radius-sm);
background: rgb(0 0 0 / 0.5);
}
.combat-page__hp-value {
display: block;
block-size: 100%;
background: var(--ar-success);
}
.combat-page__hp-value--monster {
background: var(--ar-danger);
}
.combat-page__round {
position: relative;
z-index: 1;
align-self: start;
justify-self: center;
padding: var(--ar-space-1) var(--ar-space-3);
border: 1px solid var(--ar-border-highlight);
border-radius: var(--ar-radius-sm);
color: var(--ar-gold);
background: rgb(9 11 13 / 0.75);
color: var(--ar-text);
font-family: Georgia, 'Times New Roman', serif;
font-size: clamp(0.95rem, 1.5vw, 1.15rem);
letter-spacing: 0.02em;
text-overflow: ellipsis;
text-shadow: 0 0.1rem 0.5rem rgb(0 0 0 / 0.95);
white-space: nowrap;
}
.fighter__level {
color: var(--ar-gold);
font-family: system-ui, sans-serif;
font-size: var(--ar-font-sm);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.combat-page__actions {
/* ---------- hp bars ---------- */
.bar {
position: relative;
display: block;
inline-size: 100%;
block-size: 1.25rem;
overflow: hidden;
border: 1px solid var(--ar-border);
background: linear-gradient(180deg, rgb(0 0 0 / 0.85), rgb(0 0 0 / 0.6));
box-shadow: inset 0 0 0.6rem rgb(0 0 0 / 0.9);
}
.bar__fill {
position: absolute;
z-index: 0;
inset-block: 0;
inset-inline-start: 0;
display: block;
background: linear-gradient(180deg, #d0564a, #8e2b23);
}
.bar--monster .bar__fill {
inset-inline-start: auto;
inset-inline-end: 0;
}
.bar__text {
position: relative;
z-index: 1;
display: grid;
justify-items: center;
}
.combat-page__attack {
padding: var(--ar-space-3) var(--ar-space-6);
border: 1px solid var(--ar-border-highlight);
border-radius: var(--ar-radius-sm);
block-size: 100%;
place-items: center;
color: var(--ar-text);
background: linear-gradient(180deg, #263b4b, #17232d);
cursor: pointer;
font-size: var(--ar-font-sm);
font-variant-numeric: tabular-nums;
letter-spacing: 0.04em;
text-shadow: 0 0.05rem 0.25rem rgb(0 0 0 / 1);
}
/* ---------- round marker ---------- */
.combat__round {
margin: 0;
align-self: center;
padding: var(--ar-space-1) var(--ar-space-5);
border-block: 1px solid var(--ar-border-highlight);
color: var(--ar-gold);
background: linear-gradient(90deg, transparent, rgb(9 11 13 / 0.9) 18%, rgb(9 11 13 / 0.9) 82%, transparent);
font-family: Georgia, 'Times New Roman', serif;
font-size: 1.1rem;
font-size: clamp(0.95rem, 1.4vw, 1.1rem);
letter-spacing: 0.14em;
text-transform: uppercase;
white-space: nowrap;
}
/* ---------- battlefield ---------- */
.combat__field {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1fr 1fr;
align-items: end;
min-block-size: 0;
}
.sprite {
display: block;
max-block-size: 100%;
inline-size: auto;
object-fit: contain;
filter: drop-shadow(0 1rem 1.5rem rgb(0 0 0 / 0.75));
}
.sprite--player {
justify-self: start;
block-size: clamp(11rem, 30vh, 19rem);
transform: scaleX(-1);
}
.sprite--monster {
justify-self: end;
block-size: clamp(9rem, 24vh, 15rem);
}
/* ---------- action bar ---------- */
.combat__actions {
position: relative;
z-index: 2;
display: flex;
gap: var(--ar-space-3);
min-block-size: clamp(6.5rem, 11vw, 8.5rem);
}
.action {
position: relative;
inline-size: clamp(6.5rem, 11vw, 8.5rem);
aspect-ratio: 1;
padding: 0;
border: 0;
background: transparent url('/images/hud/runtime/fight-action-frame-360.png') center / 100% 100%
no-repeat;
cursor: pointer;
}
.action__icon {
position: absolute;
inset-block-start: 32%;
inset-inline-start: 50%;
inline-size: 34%;
translate: -50% -50%;
border-radius: 50%;
opacity: 0.92;
}
.action__label {
position: absolute;
inset-block-start: 62%;
inset-inline-start: 50%;
translate: -50% -50%;
color: var(--ar-text);
font-family: Georgia, 'Times New Roman', serif;
font-size: clamp(0.85rem, 1.2vw, 1rem);
letter-spacing: 0.03em;
white-space: nowrap;
}
.combat-page__attack:hover:not(:disabled) {
border-color: #d6b26b;
background: linear-gradient(180deg, #315067, #1a2c3a);
.action__key {
position: absolute;
inset-block-start: 90%;
inset-inline-start: 50%;
translate: -50% -50%;
color: var(--ar-text-muted);
font-size: var(--ar-font-sm);
font-variant-numeric: tabular-nums;
}
.combat-page__attack:disabled {
cursor: not-allowed;
opacity: 0.6;
.action:hover:not(:disabled) .action__label,
.action:focus-visible .action__label {
color: var(--ar-gold);
}
.combat-page__result {
.action:focus-visible {
outline: 2px solid var(--ar-gold);
outline-offset: 3px;
}
.action:disabled {
cursor: progress;
opacity: 0.55;
}
/* ---------- outcome overlay ---------- */
.outcome {
position: absolute;
z-index: 3;
inset-block-start: 50%;
inset-inline-start: 50%;
display: grid;
gap: var(--ar-space-2);
max-inline-size: 30rem;
justify-items: center;
inline-size: min(26rem, 80%);
padding: var(--ar-space-5);
translate: -50% -50%;
border: 1px solid var(--ar-border-highlight);
background: var(--ar-panel);
background: rgb(9 11 13 / 0.94);
box-shadow: var(--ar-shadow-raised);
text-align: center;
}
.combat-page__result h2 {
.outcome p {
margin: 0;
color: var(--ar-text-muted);
}
.outcome__title {
margin: 0;
font-family: Georgia, 'Times New Roman', serif;
font-size: 1.6rem;
font-size: clamp(1.5rem, 3vw, 2rem);
font-weight: 400;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.combat-page__result--won h2 {
color: var(--ar-success);
.outcome--won .outcome__title {
color: var(--ar-gold);
}
.combat-page__result--lost h2 {
.outcome--lost .outcome__title {
color: var(--ar-danger);
}
.combat-page__result-hint {
color: var(--ar-text-muted);
.outcome__hint {
font-size: var(--ar-font-sm);
font-style: italic;
}
.combat-page__log {
.outcome__button {
margin-block-start: var(--ar-space-2);
padding: var(--ar-space-2) var(--ar-space-5);
border: 1px solid var(--ar-border-highlight);
border-radius: var(--ar-radius-sm);
color: var(--ar-text);
background: linear-gradient(180deg, #23282c, #14181b);
cursor: pointer;
font-family: Georgia, 'Times New Roman', serif;
letter-spacing: 0.04em;
}
.outcome__button:hover {
border-color: var(--ar-gold);
color: var(--ar-gold);
}
/* ---------- combat log ---------- */
.combat__log {
display: grid;
gap: var(--ar-space-1);
max-block-size: 16rem;
padding: var(--ar-space-4);
overflow-y: auto;
grid-template-rows: auto minmax(0, 1fr);
min-block-size: 0;
border: 1px solid var(--ar-border);
background: var(--ar-panel-muted);
box-shadow: var(--ar-shadow-raised);
}
.combat-page__log-round {
margin: var(--ar-space-2) 0 0;
.combat__log-title {
margin: 0;
padding: var(--ar-space-3) var(--ar-space-4);
border-block-end: 1px solid var(--ar-border);
color: var(--ar-gold);
font-size: var(--ar-font-sm);
letter-spacing: 0.06em;
font-family: Georgia, 'Times New Roman', serif;
font-size: 1.05rem;
font-weight: 400;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.combat-page__log-entry {
margin: 0;
color: var(--ar-text-muted);
.combat__log-body {
padding: var(--ar-space-3) var(--ar-space-4) var(--ar-space-4);
overflow-y: auto;
}
.combat-page__loading,
.combat-page__error {
.combat__log-round {
margin: var(--ar-space-4) 0 var(--ar-space-2);
color: var(--ar-gold);
font-size: var(--ar-font-sm);
letter-spacing: 0.1em;
text-transform: uppercase;
}
.combat__log-round:first-child {
margin-block-start: 0;
}
.combat__log-entry {
margin: 0 0 var(--ar-space-1);
padding-inline-start: var(--ar-space-3);
border-inline-start: 2px solid var(--ar-danger);
color: var(--ar-text-muted);
font-size: var(--ar-font-sm);
line-height: 1.45;
}
.combat__log-entry--player {
border-inline-start-color: var(--ar-border-highlight);
}
/* ---------- notices ---------- */
.combat__notice {
grid-column: 1 / -1;
padding: var(--ar-space-4);
border: 1px solid var(--ar-border);
background: var(--ar-panel);
box-shadow: var(--ar-shadow-raised);
}
.combat-page__error {
.combat__notice--error {
display: flex;
align-items: center;
justify-content: space-between;
@@ -233,7 +405,7 @@
border-color: var(--ar-danger);
}
.combat-page__error button {
.combat__notice--error button {
flex: 0 0 auto;
padding: var(--ar-space-2) var(--ar-space-3);
border: 1px solid var(--ar-border-highlight);
@@ -244,20 +416,64 @@
}
@media (prefers-reduced-motion: no-preference) {
.combat-page__hp-value {
.bar__fill {
transition: inline-size var(--ar-motion-base);
}
.action__label {
transition: color var(--ar-motion-fast);
}
}
@media (width < 720px) {
.combat-page__scene {
grid-template-columns: 1fr;
justify-items: center;
text-align: center;
@media (width < 60rem) {
:host,
.combat {
block-size: auto;
}
.combat-page__combatant--player,
.combat-page__combatant--monster {
.combat {
grid-template-columns: minmax(0, 1fr);
}
.combat__stage {
min-block-size: clamp(24rem, 55vh, 34rem);
}
.combat__log {
max-block-size: 18rem;
}
}
@media (width < 40rem) {
.combat__status {
grid-template-columns: minmax(0, 1fr);
gap: var(--ar-space-2);
}
.combat__round {
order: -1;
justify-self: center;
}
.fighter--monster {
justify-content: flex-start;
}
.fighter--monster .fighter__meter {
justify-items: start;
text-align: start;
}
.bar--monster .bar__fill {
inset-inline-start: 0;
inset-inline-end: auto;
}
.sprite--player {
block-size: clamp(8rem, 22vh, 12rem);
}
.sprite--monster {
block-size: clamp(7rem, 18vh, 10rem);
}
}