Spaces:
Running on Zero
Running on Zero
| """Terminal effects: the small kit every screen's animation draws from. | |
| Honest about the medium β everything here is characters and timing. | |
| SCRYPT_REDUCED_MOTION=1 turns all of it off (accessibility, and the test | |
| suite runs with it set so timing never leaks into assertions). | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import random | |
| from dataclasses import dataclass, field | |
| NOISE_CHARS = "βββββ " | |
| def reduced_motion() -> bool: | |
| return os.environ.get("SCRYPT_REDUCED_MOTION", "") not in ("", "0") | |
| def noise_line(width: int, rng: random.Random | None = None) -> str: | |
| rng = rng or random | |
| return "".join(rng.choice(NOISE_CHARS) for _ in range(width)) | |
| def corrupt(text: str, fraction: float = 0.12, rng: random.Random | None = None) -> str: | |
| """A glitched copy: a few characters replaced with static.""" | |
| rng = rng or random | |
| chars = list(text) | |
| for i, ch in enumerate(chars): | |
| if ch.strip() and rng.random() < fraction: | |
| chars[i] = rng.choice("βββββ") | |
| return "".join(chars) | |
| class BoardFX: | |
| """One frame of combat theater, consumed by render.board(). | |
| flash (side, lane) cells whose border burns bright this frame | |
| (side: "foe" | "player" | "queue") | |
| dissolve (side, lane) -> noise intensity 0..2 for dying cards | |
| floats lane -> (text, style) shown in the float row between rows | |
| scale overrides the displayed scale (progressive tipping) | |
| """ | |
| flash: set[tuple[str, int]] = field(default_factory=set) | |
| dissolve: dict[tuple[str, int], int] = field(default_factory=dict) | |
| floats: dict[int, tuple[str, str]] = field(default_factory=dict) | |
| scale: int | None = None | |
| def empty(self) -> bool: | |
| return not (self.flash or self.dissolve or self.floats) and self.scale is None | |
| # Typewriter pacing: ticks to hold after revealing certain characters. | |
| PUNCT_PAUSE = {".": 6, "β": 5, "β¦": 6, "?": 5, "!": 5, ",": 3, ";": 3, ":": 3} | |
| # The eye. It watches. Pupil position follows what the player has selected. | |
| EYE_FRAMES = ["(β )", "( β )", "( β )", "( β )", "( β)"] | |
| EYE_BLINK = "( ββββ )" | |
| EYE_WIDE = "((=β=))" | |
| def eye(selected: int, total: int, *, blink: bool = False, wide: bool = False) -> str: | |
| if wide: | |
| return EYE_WIDE | |
| if blink: | |
| return EYE_BLINK | |
| if total <= 1: | |
| return EYE_FRAMES[2] | |
| pos = round(selected / max(1, total - 1) * (len(EYE_FRAMES) - 1)) | |
| return EYE_FRAMES[pos] | |