4ad96b0ef7
Tokenize every remaining component <style> — calculator, order tab, inspectors, tables, report sections, lobby, auth, mail, battle viewer, toasts, map overlays. A scripted pass handled the unambiguous core palette (text/bg/surface/border/accent/danger/muted), the rest were mapped to the semantic/grey tokens by role. Remaining colour literals are the documented exceptions only: the battle-scene SVG data-visualisation palette (fixed dark, like the WebGL map canvas), overlay scrims (modal / map-canvas), and directional or deliberate drop shadows. The default theme stays dark until light coherence is signed off across the views. Updates ui/docs/design-system.md (migration status + exceptions). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
156 lines
3.8 KiB
Svelte
156 lines
3.8 KiB
Svelte
<!--
|
|
PlaybackControls — rewind / step-back / play-pause / step-forward
|
|
plus a single cycling speed button (1x → 2x → 4x → 6x → 1x) and a
|
|
"log" toggle that the orchestrator uses to collapse the always-on
|
|
text protocol when the user wants more space for the scene. Owns no
|
|
state of its own; binds `playing`, `frameIndex`, `speed`, and
|
|
`logOpen` from the orchestrator. Disables step/rewind when there's
|
|
nowhere to go and step-forward when the timeline is at its end.
|
|
-->
|
|
<script lang="ts">
|
|
import { i18n } from "$lib/i18n/index.svelte";
|
|
|
|
export type PlaybackSpeed = 1 | 2 | 4 | 6;
|
|
|
|
const SPEED_CYCLE: PlaybackSpeed[] = [1, 2, 4, 6];
|
|
|
|
let {
|
|
playing = $bindable(),
|
|
frameIndex = $bindable(),
|
|
speed = $bindable(),
|
|
logOpen = $bindable(),
|
|
frameCount,
|
|
}: {
|
|
playing: boolean;
|
|
frameIndex: number;
|
|
speed: PlaybackSpeed;
|
|
logOpen: boolean;
|
|
frameCount: number;
|
|
} = $props();
|
|
|
|
function rewind() {
|
|
playing = false;
|
|
frameIndex = 0;
|
|
}
|
|
function stepBack() {
|
|
playing = false;
|
|
if (frameIndex > 0) frameIndex = frameIndex - 1;
|
|
}
|
|
function togglePlay() {
|
|
if (frameIndex >= frameCount - 1) {
|
|
frameIndex = 0;
|
|
}
|
|
playing = !playing;
|
|
}
|
|
function stepForward() {
|
|
playing = false;
|
|
if (frameIndex < frameCount - 1) frameIndex = frameIndex + 1;
|
|
}
|
|
function cycleSpeed() {
|
|
const idx = SPEED_CYCLE.indexOf(speed);
|
|
const next = SPEED_CYCLE[(idx + 1) % SPEED_CYCLE.length];
|
|
speed = next;
|
|
}
|
|
function toggleLog() {
|
|
logOpen = !logOpen;
|
|
}
|
|
|
|
const speedLabel = $derived(`${speed}x`);
|
|
</script>
|
|
|
|
<div class="controls" data-testid="battle-controls">
|
|
<button
|
|
type="button"
|
|
onclick={rewind}
|
|
disabled={frameIndex === 0}
|
|
aria-label={i18n.t("game.battle.controls.rewind")}
|
|
data-testid="battle-control-rewind"
|
|
>⏮</button>
|
|
<button
|
|
type="button"
|
|
onclick={stepBack}
|
|
disabled={frameIndex === 0}
|
|
aria-label={i18n.t("game.battle.controls.step_backward")}
|
|
data-testid="battle-control-step-back"
|
|
>◀︎◀︎</button>
|
|
<button
|
|
type="button"
|
|
onclick={togglePlay}
|
|
aria-label={playing
|
|
? i18n.t("game.battle.controls.pause")
|
|
: i18n.t("game.battle.controls.play")}
|
|
data-testid="battle-control-play"
|
|
data-playing={playing ? "true" : "false"}
|
|
>{playing ? "⏸" : "▶︎"}</button>
|
|
<button
|
|
type="button"
|
|
onclick={stepForward}
|
|
disabled={frameIndex >= frameCount - 1}
|
|
aria-label={i18n.t("game.battle.controls.step_forward")}
|
|
data-testid="battle-control-step-forward"
|
|
>▶︎▶︎</button>
|
|
|
|
<div class="spacer" aria-hidden="true"></div>
|
|
|
|
<button
|
|
type="button"
|
|
class="speed-btn"
|
|
onclick={cycleSpeed}
|
|
title={i18n.t("game.battle.controls.speed_label")}
|
|
aria-label={i18n.t("game.battle.controls.speed_label")}
|
|
data-testid="battle-control-speed"
|
|
data-speed={speed}
|
|
>{speedLabel}</button>
|
|
|
|
<button
|
|
type="button"
|
|
class="log-toggle"
|
|
class:active={logOpen}
|
|
onclick={toggleLog}
|
|
aria-pressed={logOpen}
|
|
aria-label={i18n.t("game.battle.controls.log_toggle")}
|
|
data-testid="battle-control-log-toggle"
|
|
>{i18n.t("game.battle.controls.log_toggle")} {logOpen ? "▲" : "▼"}</button>
|
|
</div>
|
|
|
|
<style>
|
|
.controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
padding: 0.5rem 0.75rem;
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border-subtle);
|
|
border-radius: 4px;
|
|
}
|
|
.spacer {
|
|
flex: 1 1 auto;
|
|
}
|
|
button {
|
|
appearance: none;
|
|
background: var(--color-surface-raised);
|
|
color: var(--color-text);
|
|
border: 1px solid var(--color-border);
|
|
padding: 0.35rem 0.7rem;
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
font-size: 0.9rem;
|
|
font-family: inherit;
|
|
min-width: 2.5rem;
|
|
}
|
|
button:hover:not(:disabled) {
|
|
background: var(--color-surface-hover);
|
|
}
|
|
button:disabled {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
.speed-btn {
|
|
min-width: 3rem;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.log-toggle.active {
|
|
background: var(--color-surface-hover);
|
|
}
|
|
</style>
|