added blackout timer

This commit is contained in:
2026-10-24 21:16:18 +02:00
parent 471dc33f0e
commit 0fb223cdee
135 changed files with 68 additions and 1418 deletions
View File
Executable → Regular
View File
Executable → Regular
View File
View File
View File
Executable → Regular
View File
Generated Executable → Regular
View File
View File
View File

Before

Width:  |  Height:  |  Size: 377 B

After

Width:  |  Height:  |  Size: 377 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 930 B

After

Width:  |  Height:  |  Size: 930 B

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File
View File
View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File
View File
Executable → Regular
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
@@ -0,0 +1,46 @@
.timer {
position: absolute;
top: 4px;
right: 4px;
display: flex;
align-items: center;
gap: 6px;
padding: 4px 6px;
border: 3px solid;
background: #000;
font-size: 12px;
line-height: 1;
pointer-events: none;
z-index: 2;
}
.icon {
width: 14px;
height: 14px;
image-rendering: pixelated;
}
.small {
top: 2px;
right: 2px;
gap: 3px;
padding: 2px 3px;
border-width: 2px;
font-size: 8px;
}
.small .icon {
width: 8px;
height: 8px;
}
/* Blink during the last second so the return of the board isn't a surprise. */
.ending {
animation: blink 0.25s steps(2) infinite;
}
@keyframes blink {
50% {
opacity: 0.3;
}
}
@@ -0,0 +1,18 @@
import { itemVisual } from '../theme/assets';
import styles from './BlackoutTimer.module.css';
/** Seconds left on a player's blackout, in the corner of their board. */
export function BlackoutTimer({ remaining, small = false }: { remaining: number; small?: boolean }) {
if (remaining <= 0) return null;
const visual = itemVisual('blind');
return (
<div
className={[styles.timer, small ? styles.small : '', remaining < 1 ? styles.ending : ''].join(' ')}
style={{ color: visual.color, borderColor: visual.color }}
title={visual.name}
>
<img className={styles.icon} src={visual.icon} alt="" />
{remaining.toFixed(1)}s
</div>
);
}
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
+2
View File
@@ -1,6 +1,7 @@
import { memo } from 'react';
import type { PlayerState } from '../net/types';
import type { ItemFlash } from '../net/useGameConnection';
import { BlackoutTimer } from './BlackoutTimer';
import { BoardCanvas } from './BoardCanvas';
import { ItemBar } from './ItemBar';
import { ItemFlashOverlay } from './ItemFlashOverlay';
@@ -29,6 +30,7 @@ export const MiniBoard = memo(function MiniBoard({ state, width, height, slot, s
</div>
<BoardCanvas state={state} width={width} height={height} cell={cell} highlighted={selected}>
<ItemFlashOverlay flashes={flashes} small />
<BlackoutTimer remaining={state.alive ? state.invisibleRemaining : 0} small />
</BoardCanvas>
<div className={styles.items} style={{ width: width * cell + 6 }}>
<ItemBar items={state.inventory} size={Math.max(10, cell)} />
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
+2
View File
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCurrentTrack, useSound } from '../audio/SoundContext';
import { useGameSounds } from '../audio/useGameSounds';
import { BlackoutTimer } from '../components/BlackoutTimer';
import { BoardCanvas } from '../components/BoardCanvas';
import { ItemBar } from '../components/ItemBar';
import { ItemFlashOverlay } from '../components/ItemFlashOverlay';
@@ -129,6 +130,7 @@ export function GamePage({ match, lobby, playerId, flashes, actions }: Props) {
highlighted={targetId === playerId}
>
<ItemFlashOverlay flashes={flashesFor(me.id)} />
<BlackoutTimer remaining={me.invisibleRemaining} />
{match.phase === 'countdown' && (
<div className={styles.overlay}>
<span className={styles.countdown}>{Math.ceil(match.countdown)}</span>
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File