The hunt screen kept whatever roll was last in memory, so a player coming back from a fight saw every encounter as fresh. Encounters now carry their own status, which the combat module advances as fights start and end. - hunt_encounters.status replaces consumed_at, which only recorded that a fight had begun and could not distinguish a win from a loss - a lost fight hands the encounter back as AVAILABLE, so it can be retried; the unique index tying one combat to one encounter goes with it - GET /hunts/active serves the resumable hunt, which the hunt page adopts on entry rather than trusting its in-memory roll - defeated encounters are crossed out and lose their hover and attack action - a fresh page load rejoins a combat the server still holds open Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
184 lines
4.4 KiB
SCSS
184 lines
4.4 KiB
SCSS
:host {
|
|
display: block;
|
|
}
|
|
|
|
/*
|
|
* The card is one painted frame, so every slot is placed as a share of that
|
|
* artwork rather than flowed: percentages keep the crest, the artwork panel,
|
|
* the two text bands and the blue plaque on their painted counterparts at any
|
|
* card width. Coordinates come from the 560x957 frame derivative, which is
|
|
* cut to the frame's silhouette so no black surround shows on the panel.
|
|
*/
|
|
.encounter-card {
|
|
position: relative;
|
|
container-type: inline-size;
|
|
max-inline-size: 20rem;
|
|
margin-inline: auto;
|
|
aspect-ratio: 560 / 957;
|
|
background-image: url('/images/hud/runtime/enemy-card-560.webp');
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
background-size: 100% 100%;
|
|
}
|
|
|
|
/* A settled encounter is out of play, so the card recedes: the artwork loses
|
|
its colour and the whole frame dims. */
|
|
.encounter-card--settled .encounter-card__artwork {
|
|
filter: grayscale(0.85) brightness(0.6);
|
|
}
|
|
|
|
.encounter-card--settled {
|
|
opacity: 0.78;
|
|
}
|
|
|
|
/* ---------- crest ---------- */
|
|
|
|
.encounter-card__crest {
|
|
position: absolute;
|
|
inset-block-start: 9%;
|
|
inset-inline-start: 50%;
|
|
inline-size: 16.8%;
|
|
aspect-ratio: 1;
|
|
overflow: hidden;
|
|
border-radius: 50%;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
/* Scaled past the frame so the icon's own painted ring is cropped away and
|
|
only the portrait sits inside the crest ring. */
|
|
.encounter-card__crest-icon {
|
|
inline-size: 100%;
|
|
block-size: 100%;
|
|
object-fit: cover;
|
|
scale: 1.22;
|
|
}
|
|
|
|
/* ---------- artwork ---------- */
|
|
|
|
.encounter-card__artwork-frame {
|
|
position: absolute;
|
|
inset-block: 12.6% 40.4%;
|
|
inset-inline: 15% 14.6%;
|
|
display: grid;
|
|
padding: 4% 6% 2%;
|
|
place-items: end center;
|
|
}
|
|
|
|
/* Sits over the artwork panel rather than the whole card, so the name and
|
|
the danger badge stay readable under it. */
|
|
.encounter-card__defeated-mark {
|
|
position: absolute;
|
|
inset: 6%;
|
|
inline-size: 88%;
|
|
block-size: 88%;
|
|
object-fit: contain;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.encounter-card__artwork {
|
|
inline-size: 100%;
|
|
block-size: 100%;
|
|
object-fit: contain;
|
|
object-position: bottom;
|
|
filter: drop-shadow(0 0.35rem 0.7rem rgb(0 0 0 / 0.75));
|
|
}
|
|
|
|
/* ---------- text bands ---------- */
|
|
|
|
.encounter-card__name {
|
|
position: absolute;
|
|
inset-block-start: 62.8%;
|
|
inset-inline: 15% 14.6%;
|
|
display: grid;
|
|
block-size: 5.4%;
|
|
margin: 0;
|
|
overflow: hidden;
|
|
place-items: center;
|
|
color: var(--ar-text);
|
|
font-family: Georgia, 'Times New Roman', serif;
|
|
font-size: clamp(0.85rem, 5.4cqw, 1.35rem);
|
|
font-weight: 400;
|
|
line-height: 1;
|
|
text-shadow: 0 0.1rem 0.4rem rgb(0 0 0 / 0.9);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.encounter-card__meta {
|
|
position: absolute;
|
|
inset-block-start: 69.2%;
|
|
inset-inline: 12% 12%;
|
|
display: grid;
|
|
block-size: 8.8%;
|
|
gap: 1.2cqw;
|
|
place-content: center;
|
|
justify-items: center;
|
|
}
|
|
|
|
.encounter-card__level {
|
|
color: var(--ar-gold);
|
|
font-size: clamp(0.6rem, 3.5cqw, 0.85rem);
|
|
letter-spacing: 0.08em;
|
|
line-height: 1;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
/* The badge sizes itself off the shared token, so scaling it with the card is
|
|
a matter of redefining that token inside the card. */
|
|
.encounter-card__danger {
|
|
--ar-font-sm: clamp(0.6rem, 3.4cqw, 0.8125rem);
|
|
}
|
|
|
|
/* ---------- attack plaque ---------- */
|
|
|
|
.encounter-card__attack {
|
|
position: absolute;
|
|
inset-block-start: 81.1%;
|
|
inset-inline-start: 11.8%;
|
|
inline-size: 77.1%;
|
|
block-size: 10.4%;
|
|
padding: 0;
|
|
border: 0;
|
|
border-radius: 0.2rem;
|
|
color: var(--ar-text);
|
|
background: transparent;
|
|
font-family: Georgia, 'Times New Roman', serif;
|
|
font-size: clamp(0.8rem, 4.6cqw, 1.15rem);
|
|
letter-spacing: 0.08em;
|
|
text-shadow: 0 0.1rem 0.35rem rgb(0 0 0 / 0.85);
|
|
}
|
|
|
|
.encounter-card__attack:enabled:hover {
|
|
color: #f4ecda;
|
|
box-shadow: inset 0 0 1.1rem rgb(150 200 245 / 0.5);
|
|
}
|
|
|
|
.encounter-card__attack:enabled:active {
|
|
box-shadow: inset 0 0.15rem 0.7rem rgb(0 0 0 / 0.6);
|
|
}
|
|
|
|
.encounter-card__attack:disabled {
|
|
color: rgb(214 206 190 / 0.45);
|
|
cursor: default;
|
|
}
|
|
|
|
.encounter-card__attack:focus-visible {
|
|
outline: 2px solid var(--ar-blue);
|
|
outline-offset: -3px;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: no-preference) {
|
|
.encounter-card {
|
|
transition: filter var(--ar-motion-base);
|
|
}
|
|
|
|
.encounter-card:not(.encounter-card--settled):hover {
|
|
filter: drop-shadow(0 0 0.9rem rgb(214 178 107 / 0.3));
|
|
}
|
|
|
|
.encounter-card__attack {
|
|
transition:
|
|
color var(--ar-motion-fast),
|
|
box-shadow var(--ar-motion-fast);
|
|
}
|
|
}
|