Spaces:
Running on Zero
Running on Zero
Commit Β·
d207901
1
Parent(s): a50d7dd
feat: dissolve entity mode + CSS for both ending visuals
Browse filesrage mode (asset-dependent) lands when hollow_rage.png arrives.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- render.py +12 -1
- styles.css +31 -0
- tests/test_render.py +8 -0
render.py
CHANGED
|
@@ -48,7 +48,8 @@ def render_entity(affinity: int, mode: str = "idle", seq: int = 0) -> str:
|
|
| 48 |
"""The entity panel: Hollow materializes as affinity rises.
|
| 49 |
|
| 50 |
mode: "idle" | "flash" (memory captured) | "flash_strong" (recall turn)
|
| 51 |
-
| "end" (finale
|
|
|
|
| 52 |
seq: turn counter β alternates one-shot animation names (flash-0/flash-1)
|
| 53 |
so the flash restarts even when Gradio morphs the DOM in place.
|
| 54 |
"""
|
|
@@ -81,6 +82,16 @@ def render_entity(affinity: int, mode: str = "idle", seq: int = 0) -> str:
|
|
| 81 |
f"style=\"background-image:url('{_src('terror')}')\"></div>"
|
| 82 |
)
|
| 83 |
fog_op, grain_op, vig_op = 0.15, 0.05, 0.4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
else:
|
| 85 |
sway = "" if almost_on else " entity-sway"
|
| 86 |
almost_op = opacity if almost_on else 0
|
|
|
|
| 48 |
"""The entity panel: Hollow materializes as affinity rises.
|
| 49 |
|
| 50 |
mode: "idle" | "flash" (memory captured) | "flash_strong" (recall turn)
|
| 51 |
+
| "end" (good finale) | "rage" (bad finale, needs hollow_rage asset)
|
| 52 |
+
| "dissolve" (neutral finale β materialization in reverse)
|
| 53 |
seq: turn counter β alternates one-shot animation names (flash-0/flash-1)
|
| 54 |
so the flash restarts even when Gradio morphs the DOM in place.
|
| 55 |
"""
|
|
|
|
| 82 |
f"style=\"background-image:url('{_src('terror')}')\"></div>"
|
| 83 |
)
|
| 84 |
fog_op, grain_op, vig_op = 0.15, 0.05, 0.4
|
| 85 |
+
elif mode == "dissolve":
|
| 86 |
+
figure = (
|
| 87 |
+
'<div class="entity-figure">'
|
| 88 |
+
f'<img class="entity-img entity-dissolve" src="{_src("base")}" '
|
| 89 |
+
f'style="{look}">'
|
| 90 |
+
"</div>"
|
| 91 |
+
)
|
| 92 |
+
flash = ""
|
| 93 |
+
# the curtain falls back: fog and vignette swallow the child again
|
| 94 |
+
fog_op, grain_op, vig_op = 0.9, 0.13, 0.9
|
| 95 |
else:
|
| 96 |
sway = "" if almost_on else " entity-sway"
|
| 97 |
almost_op = opacity if almost_on else 0
|
styles.css
CHANGED
|
@@ -651,3 +651,34 @@ footer { display: none !important; }
|
|
| 651 |
|
| 652 |
/* Title β no underline rule */
|
| 653 |
.gradio-container h2 { border-bottom: none !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 651 |
|
| 652 |
/* Title β no underline rule */
|
| 653 |
.gradio-container h2 { border-bottom: none !important; }
|
| 654 |
+
|
| 655 |
+
/* ββ 18. Three endings β rage portrait & fog dissolve ββ */
|
| 656 |
+
|
| 657 |
+
/* bad ending: a low red breath pulsing over the rage portrait β the only
|
| 658 |
+
color in the entire game, reserved for this moment */
|
| 659 |
+
.entity-rage-tint {
|
| 660 |
+
position: absolute;
|
| 661 |
+
inset: 0;
|
| 662 |
+
pointer-events: none;
|
| 663 |
+
background: radial-gradient(ellipse at 50% 35%, rgba(120, 20, 20, 0.16), transparent 70%);
|
| 664 |
+
mix-blend-mode: screen;
|
| 665 |
+
animation: rage-pulse 2.6s ease-in-out infinite;
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
@keyframes rage-pulse {
|
| 669 |
+
0%, 100% { opacity: 0.55; }
|
| 670 |
+
50% { opacity: 1; }
|
| 671 |
+
}
|
| 672 |
+
|
| 673 |
+
/* neutral ending: materialization in reverse β the child sinks back into
|
| 674 |
+
the fog. One-shot on mount; the animation overrides the inline idle style
|
| 675 |
+
and `forwards` holds the dissolved frame. Opacity/blur are the beat itself β
|
| 676 |
+
do NOT add these to the prefers-reduced-motion kill lists. */
|
| 677 |
+
.entity-dissolve {
|
| 678 |
+
animation: entity-dissolve 9s ease-in forwards;
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
@keyframes entity-dissolve {
|
| 682 |
+
0% { filter: blur(0px); opacity: 1; transform: scale(1); }
|
| 683 |
+
100% { filter: blur(10px); opacity: 0.18; transform: scale(0.62); }
|
| 684 |
+
}
|
tests/test_render.py
CHANGED
|
@@ -189,3 +189,11 @@ class TestTreasureWounds:
|
|
| 189 |
def test_wounds_alone_suppress_empty_placeholder(self):
|
| 190 |
html_out = render_treasure([], wounds=["w1"])
|
| 191 |
assert "nothing yet" not in html_out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
def test_wounds_alone_suppress_empty_placeholder(self):
|
| 190 |
html_out = render_treasure([], wounds=["w1"])
|
| 191 |
assert "nothing yet" not in html_out
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
class TestEntityDissolveMode:
|
| 195 |
+
def test_dissolve_mode_has_dissolve_class(self):
|
| 196 |
+
assert "entity-dissolve" in render_entity(89, "dissolve")
|
| 197 |
+
|
| 198 |
+
def test_idle_has_no_dissolve(self):
|
| 199 |
+
assert "entity-dissolve" not in render_entity(50, "idle")
|