A new post attachment type beside polls: the composer gains a Start
Opinion War builder (same disabled-inputs opt-in as the poll builder)
that names exactly two factions; the battle runs for exactly 7 days
from post creation. Members join a side, may defect at any time
(damage already dealt stays with the faction it was dealt to), and
fight once per 24 hours per battle. A fight spends 25 Code Farm coins
and deals deterministic level-weighted damage: 100 + 10 * min(level,
20) HP, so a newcomer deals 110 and a veteran caps at 300 - no
randomness anywhere.
The battle renders on the post card as a CSS pixel-art battlefield
(box-shadow sprites: castles, faction flags, marching soldiers, a
flickering campfire; steps() animation, disabled under reduced motion)
with live HP bars, a countdown, the viewer's faction strip, top
contributors and an event ticker. Live frames ride pub/sub on
public.battle.{uid} via a relay on the service-lock owner, with the
durable opinion_war_events trail (per-war atomic seq) as the source of
truth and a 15s incremental poller as fallback. /battles lists battles
with active/ended/mine filters, search and pagination.
Every mutation is a conditional UPDATE via conditional_update_row: the
fight sequence claims the cooldown first, then spends coins, then lands
the damage, compensating earlier steps on any later refusal so a crash
costs a turn, never coins. Resolution is lazy on read (no cron):
an exactly-once CAS computes the winner in the statement, awards XP
(participation, winner bonus, top damage dealer bonus; draws pay
participation only), emits the result event and notifies fighters. The
OpinionWarService backstop resolves unviewed wars and sends
fight-ready notifications, exactly-once via a marker CAS.
Fan-out: battle notification type, four badges, audit keys
(battle.create/join/switch/fight/resolve), Devii actions (join/fight
confirm-gated), API docs group, docs prose page, sitemap and topnav
entries, REPORTABLE_TARGETS registration, post-delete cascades,
README and nested CLAUDE.md documentation.
Verified with the four-layer procedure: property checks over the full
damage domain, 1200-step stateful fuzz (hp-sum invariant, coins never
negative, resolved totals frozen), and real 8-process races proving
exactly-once semantics for concurrent fights, double-spends across two
wars, resolution XP and double-joins. Persisted tests in
tests/unit/services/opinionwar, tests/api/battles, tests/e2e/battles
and tests/api/posts/create.py.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
715 lines
17 KiB
CSS
715 lines
17 KiB
CSS
:root {
|
|
--war-a-rgb: 66, 133, 244;
|
|
--war-a: rgb(var(--war-a-rgb));
|
|
--war-b-rgb: 234, 67, 53;
|
|
--war-b: rgb(var(--war-b-rgb));
|
|
--war-stone: #9b93ad;
|
|
--war-stone-dark: #5b5372;
|
|
--war-window: #ffd66b;
|
|
--war-door: #241b31;
|
|
--war-skin: #e8b98c;
|
|
--war-steel: #cfd6e4;
|
|
--war-flame: #ffb02e;
|
|
--war-flame-hot: #ff5a1f;
|
|
--war-wood: #7a4a2b;
|
|
--war-ground-top: #35502f;
|
|
--war-ground-deep: #22371f;
|
|
--war-sky-top: #120c26;
|
|
--war-sky-deep: #322357;
|
|
--war-leg: #2c2440;
|
|
--war-gold: #f4c542;
|
|
}
|
|
|
|
dp-opinion-war {
|
|
display: block;
|
|
}
|
|
|
|
.war-card {
|
|
position: relative;
|
|
z-index: 1;
|
|
margin: var(--space-md) 0;
|
|
padding: var(--space-md);
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius-lg);
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
.war-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
.war-head-title {
|
|
font-family: var(--font-mono);
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.18em;
|
|
color: var(--accent-light);
|
|
}
|
|
|
|
.war-countdown {
|
|
font-family: var(--font-mono);
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.war-bars {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
align-items: center;
|
|
gap: var(--space-md);
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
.war-side {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
min-width: 0;
|
|
}
|
|
|
|
.war-side-b {
|
|
text-align: right;
|
|
}
|
|
|
|
.war-side-name {
|
|
font-weight: 700;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.war-side-a .war-side-name {
|
|
color: var(--war-a);
|
|
}
|
|
|
|
.war-side-b .war-side-name {
|
|
color: var(--war-b);
|
|
}
|
|
|
|
.war-vs {
|
|
font-family: var(--font-mono);
|
|
font-weight: 700;
|
|
font-size: 1.1rem;
|
|
color: var(--war-gold);
|
|
}
|
|
|
|
.war-bar {
|
|
position: relative;
|
|
height: 20px;
|
|
border: 1px solid var(--border-light);
|
|
border-radius: 4px;
|
|
background: var(--bg-input);
|
|
overflow: hidden;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.war-bar-fill {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: var(--war-pct, 0%);
|
|
transition: width 0.4s ease;
|
|
}
|
|
|
|
.war-bar-a .war-bar-fill {
|
|
background: rgba(var(--war-a-rgb), 0.55);
|
|
}
|
|
|
|
.war-bar-b .war-bar-fill {
|
|
left: auto;
|
|
right: 0;
|
|
background: rgba(var(--war-b-rgb), 0.55);
|
|
}
|
|
|
|
.war-bar-pct {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--text-primary);
|
|
z-index: 1;
|
|
}
|
|
|
|
.war-bar-a .war-bar-pct {
|
|
left: var(--space-sm);
|
|
}
|
|
|
|
.war-bar-b .war-bar-pct {
|
|
right: var(--space-sm);
|
|
}
|
|
|
|
.war-side-hp {
|
|
font-family: var(--font-mono);
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.war-field {
|
|
--px: 3px;
|
|
position: relative;
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
height: 96px;
|
|
padding: 0 var(--space-md) 12px;
|
|
border-radius: var(--radius);
|
|
background: linear-gradient(var(--war-sky-top), var(--war-sky-deep));
|
|
overflow: hidden;
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
.war-ground {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
height: 10px;
|
|
background: linear-gradient(var(--war-ground-top), var(--war-ground-deep));
|
|
}
|
|
|
|
.war-px {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: var(--px);
|
|
height: var(--px);
|
|
}
|
|
|
|
.war-castle {
|
|
position: relative;
|
|
z-index: 1;
|
|
width: calc(var(--px) * 5);
|
|
height: calc(var(--px) * 8);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.war-castle-b {
|
|
transform: scaleX(-1);
|
|
}
|
|
|
|
.war-castle-px {
|
|
background: var(--war-stone-dark);
|
|
box-shadow:
|
|
calc(var(--px) * 2) 0 0 0 var(--war-stone-dark),
|
|
calc(var(--px) * 4) 0 0 0 var(--war-stone-dark),
|
|
0 var(--px) 0 0 var(--war-stone-dark),
|
|
var(--px) var(--px) 0 0 var(--war-stone-dark),
|
|
calc(var(--px) * 2) var(--px) 0 0 var(--war-stone-dark),
|
|
calc(var(--px) * 3) var(--px) 0 0 var(--war-stone-dark),
|
|
calc(var(--px) * 4) var(--px) 0 0 var(--war-stone-dark),
|
|
0 calc(var(--px) * 2) 0 0 var(--war-stone-dark),
|
|
var(--px) calc(var(--px) * 2) 0 0 var(--war-stone),
|
|
calc(var(--px) * 2) calc(var(--px) * 2) 0 0 var(--war-stone),
|
|
calc(var(--px) * 3) calc(var(--px) * 2) 0 0 var(--war-stone),
|
|
calc(var(--px) * 4) calc(var(--px) * 2) 0 0 var(--war-stone-dark),
|
|
0 calc(var(--px) * 3) 0 0 var(--war-stone-dark),
|
|
var(--px) calc(var(--px) * 3) 0 0 var(--war-stone),
|
|
calc(var(--px) * 2) calc(var(--px) * 3) 0 0 var(--war-window),
|
|
calc(var(--px) * 3) calc(var(--px) * 3) 0 0 var(--war-stone),
|
|
calc(var(--px) * 4) calc(var(--px) * 3) 0 0 var(--war-stone-dark),
|
|
0 calc(var(--px) * 4) 0 0 var(--war-stone-dark),
|
|
var(--px) calc(var(--px) * 4) 0 0 var(--war-stone),
|
|
calc(var(--px) * 2) calc(var(--px) * 4) 0 0 var(--war-stone),
|
|
calc(var(--px) * 3) calc(var(--px) * 4) 0 0 var(--war-stone),
|
|
calc(var(--px) * 4) calc(var(--px) * 4) 0 0 var(--war-stone-dark),
|
|
0 calc(var(--px) * 5) 0 0 var(--war-stone-dark),
|
|
var(--px) calc(var(--px) * 5) 0 0 var(--war-stone),
|
|
calc(var(--px) * 2) calc(var(--px) * 5) 0 0 var(--war-stone),
|
|
calc(var(--px) * 3) calc(var(--px) * 5) 0 0 var(--war-stone),
|
|
calc(var(--px) * 4) calc(var(--px) * 5) 0 0 var(--war-stone-dark),
|
|
0 calc(var(--px) * 6) 0 0 var(--war-stone-dark),
|
|
var(--px) calc(var(--px) * 6) 0 0 var(--war-stone),
|
|
calc(var(--px) * 2) calc(var(--px) * 6) 0 0 var(--war-door),
|
|
calc(var(--px) * 3) calc(var(--px) * 6) 0 0 var(--war-stone),
|
|
calc(var(--px) * 4) calc(var(--px) * 6) 0 0 var(--war-stone-dark),
|
|
0 calc(var(--px) * 7) 0 0 var(--war-stone-dark),
|
|
var(--px) calc(var(--px) * 7) 0 0 var(--war-stone),
|
|
calc(var(--px) * 2) calc(var(--px) * 7) 0 0 var(--war-door),
|
|
calc(var(--px) * 3) calc(var(--px) * 7) 0 0 var(--war-stone),
|
|
calc(var(--px) * 4) calc(var(--px) * 7) 0 0 var(--war-stone-dark);
|
|
}
|
|
|
|
.war-flag {
|
|
top: calc(var(--px) * -4);
|
|
left: calc(var(--px) * 2);
|
|
background: var(--war-stone-dark);
|
|
box-shadow:
|
|
0 var(--px) 0 0 var(--war-stone-dark),
|
|
0 calc(var(--px) * 2) 0 0 var(--war-stone-dark),
|
|
0 calc(var(--px) * 3) 0 0 var(--war-stone-dark),
|
|
var(--px) 0 0 0 var(--war-flag-color),
|
|
calc(var(--px) * 2) 0 0 0 var(--war-flag-color),
|
|
var(--px) var(--px) 0 0 var(--war-flag-color);
|
|
}
|
|
|
|
.war-flag-a {
|
|
--war-flag-color: var(--war-a);
|
|
}
|
|
|
|
.war-flag-b {
|
|
--war-flag-color: var(--war-b);
|
|
}
|
|
|
|
.war-troops {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: flex;
|
|
gap: var(--space-sm);
|
|
flex-shrink: 1;
|
|
min-width: 0;
|
|
transition: transform 0.6s ease;
|
|
}
|
|
|
|
.war-field-lead-a .war-troops-a {
|
|
transform: translateX(calc(var(--px) * 2));
|
|
}
|
|
|
|
.war-field-lead-b .war-troops-b {
|
|
transform: translateX(calc(var(--px) * -2));
|
|
}
|
|
|
|
.war-soldier {
|
|
position: relative;
|
|
display: block;
|
|
width: calc(var(--px) * 6);
|
|
height: calc(var(--px) * 7);
|
|
animation: war-march 1.2s steps(1, end) infinite;
|
|
}
|
|
|
|
.war-soldier:nth-child(2) {
|
|
animation-delay: 0.4s;
|
|
}
|
|
|
|
.war-soldier:nth-child(3) {
|
|
animation-delay: 0.8s;
|
|
}
|
|
|
|
.war-soldier:nth-child(4) {
|
|
animation-delay: 0.2s;
|
|
}
|
|
|
|
.war-soldier-a {
|
|
--war-soldier-main: var(--war-a);
|
|
}
|
|
|
|
.war-soldier-b {
|
|
--war-soldier-main: var(--war-b);
|
|
transform: scaleX(-1);
|
|
}
|
|
|
|
.war-soldier-px {
|
|
background: transparent;
|
|
box-shadow:
|
|
var(--px) 0 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 2) 0 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 3) 0 0 0 var(--war-soldier-main),
|
|
var(--px) var(--px) 0 0 var(--war-skin),
|
|
calc(var(--px) * 2) var(--px) 0 0 var(--war-skin),
|
|
calc(var(--px) * 3) var(--px) 0 0 var(--war-skin),
|
|
calc(var(--px) * 5) var(--px) 0 0 var(--war-steel),
|
|
0 calc(var(--px) * 2) 0 0 var(--war-soldier-main),
|
|
var(--px) calc(var(--px) * 2) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 2) calc(var(--px) * 2) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 3) calc(var(--px) * 2) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 4) calc(var(--px) * 2) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 5) calc(var(--px) * 2) 0 0 var(--war-steel),
|
|
var(--px) calc(var(--px) * 3) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 2) calc(var(--px) * 3) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 3) calc(var(--px) * 3) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 4) calc(var(--px) * 3) 0 0 var(--war-skin),
|
|
calc(var(--px) * 5) calc(var(--px) * 3) 0 0 var(--war-steel),
|
|
var(--px) calc(var(--px) * 4) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 2) calc(var(--px) * 4) 0 0 var(--war-soldier-main),
|
|
calc(var(--px) * 3) calc(var(--px) * 4) 0 0 var(--war-soldier-main),
|
|
var(--px) calc(var(--px) * 5) 0 0 var(--war-leg),
|
|
calc(var(--px) * 3) calc(var(--px) * 5) 0 0 var(--war-leg),
|
|
var(--px) calc(var(--px) * 6) 0 0 var(--war-leg),
|
|
calc(var(--px) * 3) calc(var(--px) * 6) 0 0 var(--war-leg);
|
|
}
|
|
|
|
.war-campfire {
|
|
position: relative;
|
|
z-index: 1;
|
|
width: calc(var(--px) * 5);
|
|
height: calc(var(--px) * 5);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.war-fire-logs {
|
|
background: transparent;
|
|
box-shadow:
|
|
var(--px) calc(var(--px) * 3) 0 0 var(--war-wood),
|
|
calc(var(--px) * 2) calc(var(--px) * 3) 0 0 var(--war-wood),
|
|
calc(var(--px) * 3) calc(var(--px) * 3) 0 0 var(--war-wood),
|
|
0 calc(var(--px) * 4) 0 0 var(--war-wood),
|
|
var(--px) calc(var(--px) * 4) 0 0 var(--war-wood),
|
|
calc(var(--px) * 2) calc(var(--px) * 4) 0 0 var(--war-wood),
|
|
calc(var(--px) * 3) calc(var(--px) * 4) 0 0 var(--war-wood),
|
|
calc(var(--px) * 4) calc(var(--px) * 4) 0 0 var(--war-wood);
|
|
}
|
|
|
|
.war-fire-flame {
|
|
background: transparent;
|
|
animation: war-flicker 0.8s steps(1, end) infinite;
|
|
box-shadow:
|
|
calc(var(--px) * 2) 0 0 0 var(--war-flame-hot),
|
|
var(--px) var(--px) 0 0 var(--war-flame),
|
|
calc(var(--px) * 3) var(--px) 0 0 var(--war-flame),
|
|
var(--px) calc(var(--px) * 2) 0 0 var(--war-flame),
|
|
calc(var(--px) * 2) calc(var(--px) * 2) 0 0 var(--war-flame-hot),
|
|
calc(var(--px) * 3) calc(var(--px) * 2) 0 0 var(--war-flame);
|
|
}
|
|
|
|
.war-victory {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: var(--space-md);
|
|
margin-bottom: var(--space-md);
|
|
border: 1px solid var(--war-gold);
|
|
border-radius: var(--radius);
|
|
background: rgba(var(--accent-rgb), 0.12);
|
|
}
|
|
|
|
.war-victory-title {
|
|
font-family: var(--font-mono);
|
|
font-weight: 700;
|
|
color: var(--war-gold);
|
|
}
|
|
|
|
.war-mine {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
padding: var(--space-sm) var(--space-md);
|
|
margin-bottom: var(--space-md);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
background: var(--bg-card);
|
|
}
|
|
|
|
.war-mine-faction {
|
|
font-weight: 700;
|
|
}
|
|
|
|
.war-tag-a {
|
|
color: var(--war-a);
|
|
}
|
|
|
|
.war-tag-b {
|
|
color: var(--war-b);
|
|
}
|
|
|
|
.war-mine-stats {
|
|
font-family: var(--font-mono);
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.war-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
.war-join-btn.war-join-a {
|
|
border-color: rgba(var(--war-a-rgb), 0.6);
|
|
}
|
|
|
|
.war-join-btn.war-join-b {
|
|
border-color: rgba(var(--war-b-rgb), 0.6);
|
|
}
|
|
|
|
.war-fight-btn[disabled] {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.war-block-label {
|
|
display: block;
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.12em;
|
|
color: var(--text-muted);
|
|
margin-bottom: var(--space-xs);
|
|
}
|
|
|
|
.war-contributors {
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
.war-contributor-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
}
|
|
|
|
.war-contributor {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
padding: var(--space-xs) var(--space-sm);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
background: var(--bg-card);
|
|
}
|
|
|
|
.war-contributor-hp {
|
|
margin-left: auto;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.war-text-a {
|
|
color: var(--war-a);
|
|
}
|
|
|
|
.war-text-b {
|
|
color: var(--war-b);
|
|
}
|
|
|
|
.war-events {
|
|
margin-bottom: var(--space-md);
|
|
}
|
|
|
|
.war-ticker {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-xs);
|
|
}
|
|
|
|
.war-event {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
padding: var(--space-xs) var(--space-sm);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
background: var(--bg-card);
|
|
font-size: 0.85rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
.war-event-empty {
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.war-event-dot {
|
|
flex-shrink: 0;
|
|
width: 8px;
|
|
height: 8px;
|
|
background: var(--text-muted);
|
|
}
|
|
|
|
.war-dot-a {
|
|
background: var(--war-a);
|
|
}
|
|
|
|
.war-dot-b {
|
|
background: var(--war-b);
|
|
}
|
|
|
|
.war-event-message {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.war-event-time {
|
|
margin-left: auto;
|
|
flex-shrink: 0;
|
|
font-size: 0.72rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.war-foot {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.war-all-link {
|
|
font-weight: 600;
|
|
color: var(--accent-light);
|
|
}
|
|
|
|
.war-foot-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--space-xs);
|
|
padding: var(--space-xs) var(--space-sm);
|
|
border: none;
|
|
border-radius: var(--radius);
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.war-foot-btn:hover {
|
|
color: var(--text-primary);
|
|
background: var(--bg-card-hover);
|
|
}
|
|
|
|
@keyframes war-march {
|
|
0%,
|
|
100% {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
50% {
|
|
transform: translateY(calc(var(--px) * -1));
|
|
}
|
|
}
|
|
|
|
@keyframes war-flicker {
|
|
0%,
|
|
100% {
|
|
box-shadow:
|
|
calc(var(--px) * 2) 0 0 0 var(--war-flame-hot),
|
|
var(--px) var(--px) 0 0 var(--war-flame),
|
|
calc(var(--px) * 3) var(--px) 0 0 var(--war-flame),
|
|
var(--px) calc(var(--px) * 2) 0 0 var(--war-flame),
|
|
calc(var(--px) * 2) calc(var(--px) * 2) 0 0 var(--war-flame-hot),
|
|
calc(var(--px) * 3) calc(var(--px) * 2) 0 0 var(--war-flame);
|
|
}
|
|
|
|
50% {
|
|
box-shadow:
|
|
var(--px) 0 0 0 var(--war-flame),
|
|
calc(var(--px) * 2) var(--px) 0 0 var(--war-flame-hot),
|
|
calc(var(--px) * 3) 0 0 0 var(--war-flame),
|
|
var(--px) calc(var(--px) * 2) 0 0 var(--war-flame-hot),
|
|
calc(var(--px) * 2) calc(var(--px) * 2) 0 0 var(--war-flame),
|
|
calc(var(--px) * 3) calc(var(--px) * 2) 0 0 var(--war-flame);
|
|
}
|
|
}
|
|
|
|
.war-soldier-b.war-soldier {
|
|
animation-name: war-march-b;
|
|
}
|
|
|
|
@keyframes war-march-b {
|
|
0%,
|
|
100% {
|
|
transform: scaleX(-1) translateY(0);
|
|
}
|
|
|
|
50% {
|
|
transform: scaleX(-1) translateY(calc(var(--px) * -1));
|
|
}
|
|
}
|
|
|
|
.war-filter-count {
|
|
margin-left: auto;
|
|
font-family: var(--font-mono);
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.war-listing-head {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: var(--space-sm);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.war-listing-meta {
|
|
font-size: 0.8rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.war-builder {
|
|
margin: var(--space-sm) 0;
|
|
padding: var(--space-md);
|
|
border: 1px dashed var(--border-light);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.war-builder[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.war-builder-hint {
|
|
margin: 0 0 var(--space-sm);
|
|
font-size: 0.82rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.war-builder-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.war-builder-row input {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.war-builder-vs {
|
|
font-family: var(--font-mono);
|
|
font-weight: 700;
|
|
color: var(--war-gold);
|
|
}
|
|
|
|
.war-builder-error {
|
|
margin: var(--space-sm) 0 0;
|
|
color: var(--danger);
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.war-field {
|
|
--px: 2px;
|
|
height: 76px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.war-bars {
|
|
gap: var(--space-sm);
|
|
}
|
|
|
|
.war-vs {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.war-bar-pct {
|
|
display: none;
|
|
}
|
|
|
|
.war-builder-row {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.war-soldier,
|
|
.war-fire-flame {
|
|
animation: none;
|
|
}
|
|
|
|
.war-bar-fill,
|
|
.war-troops {
|
|
transition: none;
|
|
}
|
|
}
|