Compare commits

...

16 Commits

Author SHA1 Message Date
neru ab3bf047d4 style: reduce noise opacity 2026-06-02 04:43:52 -03:00
neru f1ab2b692d style: change text 2026-06-02 04:43:09 -03:00
neru 673aabce50 style: move patch to its own file 2026-06-02 04:42:16 -03:00
neru b3a5712c85 feat: make movement jitter, add phase effects 2026-06-02 04:42:07 -03:00
neru 4691a9fbf4 feat: add flicker anim 2026-06-02 04:30:03 -03:00
neru 0fca4db440 style: change size and alignment 2026-06-02 04:13:54 -03:00
neru 930139d1df feat: add unicode glitches 2026-06-02 04:13:29 -03:00
neru 4120e5ec72 feat: move href logic to state mgr 2026-06-02 03:57:42 -03:00
neru fd314cf2ec feat: make finale text infinite 2026-06-02 03:57:31 -03:00
neru e3ab974988 style: reduce fov 2026-06-02 03:45:04 -03:00
neru e4a0c57e79 feat: add TEST_MODE 2026-06-02 03:44:58 -03:00
neru ebda4b281e feat: add steel texture for doors 2026-06-02 03:29:17 -03:00
neru 8dcc888d5c feat: add psx style vertex snapping and affine distortion 2026-06-02 03:29:07 -03:00
neru b7e61b4240 feat: lerp flashlight pos 2026-06-02 02:50:11 -03:00
neru 23c39a71a6 feat: add footsteps 2026-06-02 02:50:04 -03:00
neru a0ee50703c feat: add better ambience sound 2026-06-02 02:44:44 -03:00
16 changed files with 303 additions and 68 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+9 -9
View File
@@ -8,7 +8,7 @@ import { Suspense, useEffect, useState } from "react";
import { AmbientSound } from './scene-components/ambient-sound';
import { fearState } from './state';
import { FEAR_SETTINGS, fearState } from './state';
import TheCreature from './scene-components/creature';
import Player from './scene-components/player';
@@ -28,9 +28,9 @@ function PostProcessing() {
}, []);
return (<EffectComposer>
<Pixelation granularity={wasCaught ? 18 : 12} />
<Pixelation granularity={wasCaught ? 18 : 10} />
<Vignette />
<Noise opacity={wasCaught ? 0.01 : 0.005} />
<Noise opacity={wasCaught ? 0.01 : 0.003} />
<BrightnessContrast
brightness={-0.01}
contrast={0.05}
@@ -78,7 +78,7 @@ export default function Fear() {
shadows
gl={{ antialias: true }}
className='canvas'
camera={{ position: [0, 3, -5], fov: 65, far: 100 }}
camera={{ position: [0, 3, -5], fov: 55, far: 100 }}
>
<FearStateUpdater />
@@ -86,20 +86,20 @@ export default function Fear() {
<color attach="background" args={['#050505']} />
<ambientLight intensity={0.0225} />
<fogExp2 attach='fog' args={[0x050505, 0.035]} />
<PostProcessing />
{FEAR_SETTINGS.TEST_MODE ? <ambientLight intensity={2} /> : <ambientLight intensity={0.0225} />}
{FEAR_SETTINGS.TEST_MODE ? null : <fogExp2 attach='fog' args={[0x050505, 0.035]} />}
{FEAR_SETTINGS.TEST_MODE ? null : < PostProcessing />}
<Suspense fallback={null}>
<Hallway />
<TheCreature />
<Player />
</Suspense>
<AmbientSound
key="ambient-1"
url='fear/snd/ambience.mp3'
volume={isRustActive ? 0 : 1}
volume={isRustActive ? 0 : 0.5}
/>
<AmbientSound
+89 -6
View File
@@ -1,14 +1,22 @@
import { useTexture, PositionalAudio } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import * as THREE from "three";
import { FEAR_SETTINGS, fearState } from "../state";
import { ShaderPatch } from "../shader-patch";
useTexture.preload('fear/img/creature.png');
export default function TheCreature() {
const texture = useTexture('fear/img/creature.png');
const baseTexture = useTexture('fear/img/creature.png');
const texture = useMemo(() => {
const t = baseTexture.clone();
t.needsUpdate = true;
return t;
}, [baseTexture]);
const meshRef = useRef<THREE.Mesh>(null);
const audioRef = useRef<THREE.PositionalAudio>(null);
const { camera } = useThree();
@@ -21,6 +29,13 @@ export default function TheCreature() {
const audioPlaying = useRef<boolean>(false);
const movePhase = useRef<'frozen' | 'lurching'>('frozen');
const phaseTimer = useRef<number>(1.5);
const glitchCooldown = useRef<number>(0);
const isGlitchSpiking = useRef<boolean>(false);
const flickerCooldown = useRef<number>(0);
useEffect(() => {
const unsubscribe = fearState.subscribe(() => {
setFinaleTriggered(fearState.finaleTriggered);
@@ -30,6 +45,8 @@ export default function TheCreature() {
setHasTriggered(false);
globalDistance.current = 32;
audioPlaying.current = false;
movePhase.current = 'frozen';
phaseTimer.current = 1.5;
if (audioRef.current && audioRef.current.isPlaying)
audioRef.current.stop();
@@ -47,16 +64,33 @@ export default function TheCreature() {
if (!isSpawned) {
setIsSpawned(true);
globalDistance.current = 32;
movePhase.current = 'frozen';
phaseTimer.current = 1.0 + Math.random() * 1.5;
}
if (!hasTriggered) {
if (globalDistance.current < 40) {
if (globalDistance.current < 40)
setHasTriggered(true);
}
}
if (hasTriggered) {
globalDistance.current -= FEAR_SETTINGS.CREATURE_SPEED * delta;
phaseTimer.current -= delta;
if (phaseTimer.current <= 0) {
if (movePhase.current === 'frozen') {
movePhase.current = 'lurching';
phaseTimer.current = 0.05 + Math.random() * 0.2;
} else {
movePhase.current = 'frozen';
const proximityFactor = Math.max(0.05, globalDistance.current / 32);
phaseTimer.current = (0.2 + Math.random() * 1.0) * proximityFactor;
}
}
if (movePhase.current === 'lurching') {
globalDistance.current -= FEAR_SETTINGS.CREATURE_SPEED * 3 * delta;
}
if (audioRef.current && !audioPlaying.current) {
audioPlaying.current = true;
@@ -84,6 +118,51 @@ export default function TheCreature() {
creature.position.set(0, 1.6, calculatedZ);
creature.lookAt(camera.position.x, creature.position.y, camera.position.z);
if (!hasTriggered) return;
const proximity = 1 - Math.max(0, Math.min(1, globalDistance.current / 32));
const jitterX = 1.0 + (Math.random() - 0.5) * 0.04 * proximity;
let jitterY = 1.0 + (Math.random() - 0.5) * 0.06 * proximity;
glitchCooldown.current -= delta;
if (glitchCooldown.current <= 0) {
if (Math.random() < 0.25 + proximity * 0.35) {
isGlitchSpiking.current = true;
glitchCooldown.current = 0.03 + Math.random() * 0.08;
} else {
isGlitchSpiking.current = false;
glitchCooldown.current = 0.08 + Math.random() * 0.4 * (1 - proximity * 0.7);
}
}
if (isGlitchSpiking.current) {
const spike = 0.15 + Math.random() * 0.35;
jitterY += Math.random() > 0.5 ? spike : -spike * 0.6;
}
creature.scale.set(jitterX, jitterY, 1.0);
flickerCooldown.current -= delta;
if (flickerCooldown.current <= 0) {
if (creature.visible && Math.random() < 0.12 + proximity * 0.08) {
creature.visible = false;
flickerCooldown.current = 0.02 + Math.random() * 0.05;
} else {
creature.visible = true;
flickerCooldown.current = 0.05 + Math.random() * 0.3 * (1 - proximity * 0.5);
}
}
texture.offset.set(
(Math.random() - 0.5) * 0.025 * proximity,
(Math.random() - 0.5) * 0.025 * proximity
);
if (proximity > 0.2) {
creature.position.x += (Math.random() - 0.5) * 0.12 * proximity;
creature.position.y += (Math.random() - 0.5) * 0.06 * proximity;
}
});
return (
@@ -92,11 +171,15 @@ export default function TheCreature() {
visible={finaleTriggered}
>
<planeGeometry args={[3.0, 4.8]} />
<meshBasicMaterial
<meshStandardMaterial
map={texture}
transparent={true}
depthWrite={false}
side={THREE.DoubleSide}
onBeforeCompile={ShaderPatch}
emissive="#ffffff"
emissiveMap={texture}
emissiveIntensity={0.15}
/>
{finaleTriggered && (
+34 -2
View File
@@ -19,22 +19,54 @@
justify-content: center;
overflow: hidden;
grid-auto-rows: 2.5vh;
/* filter: invert(100%); */
backdrop-filter: brightness(100%);
grid-auto-rows: 5vh;
/* grid-template-columns: 0; */
grid-template-rows: repeat(auto-fit, max-content);
user-select: none;
will-change: filter;
animation: invertFlicker 0.07s infinite alternate;
}
@keyframes invertFlicker {
0%,
43%,
45%,
88%,
92% {
filter: invert(0%) contrast(100%) brightness(100%);
backdrop-filter: brightness(100%) hue-rotate(0deg);
}
44%,
46%,
89%,
93%,
100% {
filter: invert(100%) contrast(300%) brightness(150%);
backdrop-filter: brightness(30%) hue-rotate(180deg) saturate(500%);
}
}
.finale-text {
font-family: 'VCR', sans-serif;
font-variant-numeric: tabular-nums;
letter-spacing: 0.1em;
height: 0px;
width: 100%;
color: rgb(255, 255, 255);
font-size: 5vh;
font-size: 8vh;
text-align: center;
white-space: nowrap;
}
.scanlines {
+37 -17
View File
@@ -1,36 +1,56 @@
import { JSX, useEffect, useState } from "react"
import { FEAR_SETTINGS, fearState } from "../state"
import { fearState } from "../state"
import './finale-text.css';
const BLOCKS = [
"▀", "▂", "▃", "▄", "▅", "▆", "▇",
"█", "▉", "▊", "▋", "▌", "▍", "▎", "▏",
"▐", "░", "▒", "▓", "▔", "▕", "▖", "▗",
"▘", "▙", "▚", "▛", "▜", "▝", "▞", "▟"
];
export default function FinaleText() {
const [progression, setProgression] = useState(fearState.finaleProgression);
const [wasCaught, setWasCaught] = useState(fearState.isRustActive);
const [wasCaught, setWasCaught] = useState(fearState.wasCaught);
const [elements, setElements] = useState<JSX.Element[]>([]);
useEffect(() => {
const unsubscribe = fearState.subscribe(() => {
setProgression(fearState.finaleProgression);
setWasCaught(fearState.wasCaught)
});
return () => unsubscribe();
});
}, []);
let elementCount = (FEAR_SETTINGS.EVENT_FINALE_TEXT_COUNT / FEAR_SETTINGS.EVENT_FINALE_DURATION) * progression;
useEffect(() => {
if (!wasCaught)
return;
let testElements: Array<JSX.Element> = [];
const interval = setInterval(() => {
if (Math.random() > 0.9) return;
for (let x = 0; x < elementCount; x++)
testElements.push(<span className="finale-text" key={x}>the deal has been sealed</span>)
const baseText = "bwaaaaaaaaa";
const corrupted = baseText
.split("")
.map((char) => (Math.random() > 0.98 ? BLOCKS[Math.floor(Math.random() * BLOCKS.length)] : char))
.join("");
setElements((prev) => [...prev.slice(-30),
<span className="finale-text" key={crypto.randomUUID()}>
{corrupted}
</span>
]);
}, 10);
if (wasCaught)
return (<>
<div className="finale-container">
{testElements}
</div>
<div className="scanlines" />
</>)
return () => clearInterval(interval);
}, [wasCaught]);
return <></>
if (!wasCaught) return null;
return (<>
<div className="finale-container">
{elements}
</div>
<div className="scanlines" />
</>)
}
+25 -21
View File
@@ -4,6 +4,7 @@ import { useTexture, PositionalAudio } from "@react-three/drei";
import * as THREE from "three";
import { useFrame } from "@react-three/fiber";
import { ShaderPatch } from "../shader-patch";
interface DoorProps {
position: [number, number, number];
@@ -12,6 +13,7 @@ interface DoorProps {
function Door({ position, rotation }: DoorProps) {
const [soundUrl, setSoundUrl] = useState<string | null>(null);
const currentSound = useRef<string | null>(null);
const steelTex = useTexture('fear/img/steel.png');
useEffect(() => {
const interval = setInterval(() => {
@@ -33,19 +35,22 @@ function Door({ position, rotation }: DoorProps) {
return (
<group position={position} rotation={rotation}>
<mesh position={[0, 2, -0.14]}>
<boxGeometry args={[2.4, 4.0, 0.2]} />
<meshStandardMaterial color="#8a8585" roughness={0.8} metalness={0.2} />
{/* frame */}
<mesh position={[0, 2, -0.1]}>
<boxGeometry args={[2.4, 4.0, 0.2, 4, 4, 1]} />
<meshStandardMaterial map={steelTex} color="#8d8d8d" onBeforeCompile={ShaderPatch} />
</mesh>
<mesh position={[0, 1.95, -0.08]}>
<boxGeometry args={[2.1, 3.8, 0.1]} />
<meshStandardMaterial color="#4e4b4b" roughness={0.7} metalness={0.2} />
{/* panel */}
<mesh position={[0, 1.95, -0.0]}>
<boxGeometry args={[2.1, 3.8, 0.1, 4, 4, 1]} />
<meshStandardMaterial map={steelTex} color="#4e4a4a" onBeforeCompile={ShaderPatch} />
</mesh>
<mesh position={[0.9, 1.8, 0.08]}>
<boxGeometry args={[0.08, 0.08, 0.15]} />
<meshStandardMaterial color="#4e4b4b" roughness={0.4} metalness={0.2} />
{/* handle */}
<mesh position={[0.75, 1.8, .085]}>
<boxGeometry args={[0.3, 0.08, 0.1]} />
<meshStandardMaterial map={steelTex} color="#ffffff" onBeforeCompile={ShaderPatch} />
</mesh>
{soundUrl && (
@@ -283,6 +288,7 @@ export default function Hallway() {
emissive="#a8a1a1"
emissiveIntensity={0.8}
roughness={0.9}
onBeforeCompile={ShaderPatch}
/>
</mesh>
</group>
@@ -293,12 +299,11 @@ export default function Hallway() {
rotation={[-Math.PI / 2, 0, 0]}
position={[0, 0, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
>
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH]} />
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH, 4, 10]} />
<meshStandardMaterial
ref={(el) => { if (el) floorMaterialsRef.current.push(el); }}
map={floorTex}
roughness={0.8}
metalness={0.2}
onBeforeCompile={ShaderPatch}
/>
</mesh>
@@ -308,24 +313,22 @@ export default function Hallway() {
rotation={[Math.PI / 2, 0, 0]}
position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
>
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH]} />
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH, 4, 10]} />
<meshStandardMaterial
ref={(el) => { if (el) floorMaterialsRef.current.push(el); }}
map={floorTex}
roughness={0.8}
metalness={0.2}
onBeforeCompile={ShaderPatch}
/>
</mesh>
{/* left wall */}
<group name="left-wall-group">
<mesh rotation={[0, Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}>
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT]} />
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT, 10, 4]} />
<meshStandardMaterial
ref={(el) => { if (el) wallMaterialsRef.current.push(el); }}
map={wallTex}
roughness={0.7}
metalness={0.1}
onBeforeCompile={ShaderPatch}
/>
</mesh>
{!isRustActive && (
@@ -339,12 +342,11 @@ export default function Hallway() {
{/* right wall */}
<group name="right-wall-group">
<mesh rotation={[0, -Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}>
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT]} />
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT, 10, 4]} />
<meshStandardMaterial
ref={(el) => { if (el) wallMaterialsRef.current.push(el); }}
map={wallTex}
roughness={0.7}
metalness={0.1}
onBeforeCompile={ShaderPatch}
/>
</mesh>
{!isRustActive && (
@@ -366,6 +368,7 @@ export default function Hallway() {
color="#a5aca8"
roughness={0.0}
metalness={0.4}
onBeforeCompile={ShaderPatch}
/>
</mesh>
))}
@@ -385,6 +388,7 @@ export default function Hallway() {
color="#a5aca8"
roughness={0.0}
metalness={0.4}
onBeforeCompile={ShaderPatch}
/>
</mesh>
);
+41 -9
View File
@@ -54,10 +54,32 @@ export default function Player() {
const confirmedSegment = useRef<number>(0);
const hasTriggeredThisSegment = useRef<boolean>(false);
const footstepAudio = useRef<HTMLAudioElement[]>([]);
const hasStepped = useRef<boolean>(false);
useEffect(() => {
playerRoot.set(camera.position.x, FEAR_SETTINGS.PLAYER_HEIGHT, camera.position.z);
footstepAudio.current = Array.from({ length: 6 }, (_, i) => {
const audio = new Audio(`fear/snd/footstep${i + 1}.mp3`);
audio.volume = 0.4;
return audio;
});
}, []);
const playRandomFootstep = () => {
if (footstepAudio.current.length === 0) return;
const randomIndex = Math.floor(Math.random() * footstepAudio.current.length);
const audio = footstepAudio.current[randomIndex];
audio.currentTime = 0;
audio.play().catch((err) => {
console.warn("Footstep playback blocked by browser autocomplete/interaction rules.", err);
});
};
useFrame((state, delta) => {
const dt = Math.min(delta, 0.1);
@@ -73,7 +95,7 @@ export default function Player() {
if (moveForward !== 0) targetVelocity.addScaledVector(forward, moveForward);
if (moveSide !== 0) targetVelocity.addScaledVector(side, moveSide);
if (targetVelocity.lengthSq() > 0)
if (targetVelocity.lengthSq() > 0)
targetVelocity.normalize().multiplyScalar(FEAR_SETTINGS.PLAYER_SPEED);
currentVelocity.lerp(targetVelocity, 10 * dt);
@@ -86,15 +108,25 @@ export default function Player() {
playerRoot.x = THREE.MathUtils.clamp(playerRoot.x, minX, maxX);
const isMoving = controls.Forward || controls.Backward || controls.Left || controls.Right;
bobIntensity.current = THREE.MathUtils.lerp(bobIntensity.current, isMoving ? 1 : 0, 8 * dt);
if (isMoving)
if (isMoving)
movementCounter.current += dt * 12;
const moveBobY = Math.sin(movementCounter.current) * 0.06 * bobIntensity.current;
const sinWave = Math.sin(movementCounter.current);
const moveBobY = sinWave * 0.06 * bobIntensity.current;
const moveBobX = Math.cos(movementCounter.current / 2) * 0.04 * bobIntensity.current;
if (isMoving && sinWave < -0.9) {
if (!hasStepped.current) {
playRandomFootstep();
hasStepped.current = true;
}
} else if (sinWave > 0) {
hasStepped.current = false;
}
const breatheTime = state.clock.elapsedTime * 1.8;
const breatheBobY = Math.sin(breatheTime) * 0.03 * (1 - bobIntensity.current * 0.5);
@@ -103,7 +135,7 @@ export default function Player() {
camera.position.addScaledVector(side, moveBobX);
if (flashlightRef.current) {
flashlightRef.current.position.copy(camera.position);
flashlightRef.current.position.lerp(camera.position, 7 * dt);
camera.getWorldDirection(viewDirection);
targetDest
@@ -113,13 +145,13 @@ export default function Player() {
flashlightRef.current.target.position.lerp(targetDest, 12 * dt);
flashlightRef.current.target.updateMatrixWorld();
flashlightRef.current.intensity =
FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE +
flashlightRef.current.intensity =
FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE +
Math.sin(state.clock.elapsedTime * 30) * 0.15 * Math.cos(state.clock.elapsedTime * 3);
}
const length = FEAR_SETTINGS.HALLWAY_LENGTH;
const absoluteZ = -playerRoot.z;
const absoluteZ = -playerRoot.z;
const rawSegmentIndex = Math.floor(absoluteZ / length);
const progressZ = ((absoluteZ % length) + length) % length / length;
+57
View File
@@ -0,0 +1,57 @@
export function ShaderPatch(shader: { vertexShader: string, fragmentShader: string, uniforms: Object }) {
shader.vertexShader = `
varying float vDepth;
#ifdef USE_MAP
varying vec2 vAffineUv;
#endif
${shader.vertexShader}
`;
shader.vertexShader = shader.vertexShader.replace(
`#include <project_vertex>`,
`
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
float precisionModifier = 200.0;
gl_Position.xy /= gl_Position.w;
gl_Position.xy = floor(gl_Position.xy * precisionModifier) / precisionModifier;
gl_Position.xy *= gl_Position.w;
vDepth = gl_Position.w;
#ifdef USE_MAP
vAffineUv = vMapUv * gl_Position.w;
#endif
`
);
shader.fragmentShader = `
varying float vDepth;
#ifdef USE_MAP
varying vec2 vAffineUv;
#endif
${shader.fragmentShader}
`;
shader.fragmentShader = shader.fragmentShader.replace(
`#include <map_fragment>`,
`
#ifdef USE_MAP
vec2 flatAffineUV = vAffineUv / max(vDepth, 0.001);
vec2 warpDiff = flatAffineUV - vMapUv;
float warpDist = length(warpDiff);
float maxDistortion = 0.25;
float falloff = maxDistortion / (maxDistortion + warpDist);
vec2 finalUV = vMapUv + (warpDiff * falloff);
vec4 texelColor = texture2D( map, finalUV );
diffuseColor *= texelColor;
#endif
`
);
}
+11 -4
View File
@@ -14,8 +14,9 @@ export const FEAR_SETTINGS = {
EVENT_RUST_LOOP_COUNT: 4,
EVENT_FINALE_LOOP_COUNT: 5,
EVENT_FINALE_DURATION: 3,
EVENT_FINALE_TEXT_COUNT: 128
EVENT_FINALE_DURATION: 1,
TEST_MODE: false
};
const listeners = new Set<() => void>();
@@ -45,8 +46,14 @@ export const fearState = {
this.currentWidth = newWidth;
}
if (this.wasCaught && this.finaleProgression < FEAR_SETTINGS.EVENT_FINALE_DURATION)
this.finaleProgression = Math.min(this.finaleProgression + delta, FEAR_SETTINGS.EVENT_FINALE_DURATION);
if (this.wasCaught) {
if (this.finaleProgression < FEAR_SETTINGS.EVENT_FINALE_DURATION) {
this.finaleProgression = Math.min(this.finaleProgression + delta, FEAR_SETTINGS.EVENT_FINALE_DURATION);
} else {
window.location.href = '/';
}
}
this.emit();
},