style: run format:apply

This commit is contained in:
2026-06-26 08:48:31 -03:00
parent ab3bf047d4
commit eebd87a650
21 changed files with 1856 additions and 1484 deletions
+16 -4
View File
@@ -31,10 +31,22 @@
border-radius: 50%; border-radius: 50%;
box-shadow: 0 0 0 2px #fffdfd; box-shadow: 0 0 0 2px #fffdfd;
} }
.status-dot.online { background-color: #a7f3d0; border: 1px solid #34d399; } .status-dot.online {
.status-dot.idle { background-color: #fef08a; border: 1px solid #facc15; } background-color: #a7f3d0;
.status-dot.dnd { background-color: #fecdd3; border: 1px solid #fb7185; } border: 1px solid #34d399;
.status-dot.offline { background-color: var(--text-dim); border: 1px solid var(--text-main); } }
.status-dot.idle {
background-color: #fef08a;
border: 1px solid #facc15;
}
.status-dot.dnd {
background-color: #fecdd3;
border: 1px solid #fb7185;
}
.status-dot.offline {
background-color: var(--text-dim);
border: 1px solid var(--text-main);
}
.status-details { .status-details {
display: flex; display: flex;
+72 -50
View File
@@ -1,6 +1,6 @@
import './discordstatus.css'; import './discordstatus.css';
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react';
interface LanyardResponse { interface LanyardResponse {
success: boolean; success: boolean;
@@ -98,19 +98,20 @@ interface DiscordActivity {
}; };
} }
function resolveDiscordAsset(applicationId: string | undefined, image: string | undefined): string { function resolveDiscordAsset(
if (!image) return ""; applicationId: string | undefined,
image: string | undefined
): string {
if (!image) return '';
if (image.startsWith("mp:external/")) { if (image.startsWith('mp:external/')) {
const httpsIndex = image.indexOf("/https/"); const httpsIndex = image.indexOf('/https/');
if (httpsIndex !== -1) { if (httpsIndex !== -1) {
return `https://${image.slice(httpsIndex + "/https/".length)}`; return `https://${image.slice(httpsIndex + '/https/'.length)}`;
} }
} }
if (image.startsWith("spotify:")) if (image.startsWith('spotify:')) return '';
return "";
if (applicationId && image) if (applicationId && image)
return `https://cdn.discordapp.com/app-assets/${applicationId}/${image}.png`; return `https://cdn.discordapp.com/app-assets/${applicationId}/${image}.png`;
@@ -119,7 +120,7 @@ function resolveDiscordAsset(applicationId: string | undefined, image: string |
} }
export interface DiscordStatusParams { export interface DiscordStatusParams {
userId: string userId: string;
} }
const STATUS_LABELS: Record<LanyardData['discord_status'], string> = { const STATUS_LABELS: Record<LanyardData['discord_status'], string> = {
@@ -138,12 +139,13 @@ export function DiscordStatus({ userId }: DiscordStatusParams) {
async function fetchRichPresence() { async function fetchRichPresence() {
try { try {
const response = await fetch(`https://api.lanyard.rest/v1/users/${userId}`); const response = await fetch(
`https://api.lanyard.rest/v1/users/${userId}`
);
const json: LanyardResponse = await response.json(); const json: LanyardResponse = await response.json();
if (json.success) if (json.success) setPresence(json.data);
setPresence(json.data);
} catch (error) { } catch (error) {
console.error("Failed to fetch Lanyard presence:", error); console.error('Failed to fetch Lanyard presence:', error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -164,17 +166,12 @@ export function DiscordStatus({ userId }: DiscordStatusParams) {
}; };
const handleVisibilityChange = () => { const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') if (document.visibilityState === 'visible') startPolling();
startPolling(); else stopPolling();
else
stopPolling();
}; };
if (document.visibilityState === 'visible') if (document.visibilityState === 'visible') startPolling();
startPolling(); else setLoading(false);
else
setLoading(false);
document.addEventListener('visibilitychange', handleVisibilityChange); document.addEventListener('visibilitychange', handleVisibilityChange);
@@ -185,68 +182,93 @@ export function DiscordStatus({ userId }: DiscordStatusParams) {
}, [userId]); }, [userId]);
if (loading) if (loading)
return <p style={{ fontSize: '0.75rem', fontStyle: 'italic', color: 'var(--text-dim)' }}>loading status...</p>; return (
<p
style={{
fontSize: '0.75rem',
fontStyle: 'italic',
color: 'var(--text-dim)'
}}
>
loading status...
</p>
);
if (!presence) if (!presence)
return <p style={{ fontSize: '0.75rem', fontStyle: 'italic', color: 'var(--text-dim)' }}>offline</p>; return (
<p
style={{
fontSize: '0.75rem',
fontStyle: 'italic',
color: 'var(--text-dim)'
}}
>
offline
</p>
);
const customActivity = presence.activities.find(act => act.id === "custom"); const customActivity = presence.activities.find((act) => act.id === 'custom');
const customStatusText = customActivity const customStatusText = customActivity
? `${customActivity.emoji?.name || ''} ${customActivity.state || ''}`.trim() ? `${customActivity.emoji?.name || ''} ${customActivity.state || ''}`.trim()
: null; : null;
const gameActivity = presence.activities const gameActivity = presence.activities
.filter(act => act.type === 0) .filter((act) => act.type === 0)
.sort((a, b) => (b.assets ? 1 : 0) - (a.assets ? 1 : 0))[0] as DiscordActivity | undefined; .sort((a, b) => (b.assets ? 1 : 0) - (a.assets ? 1 : 0))[0] as
| DiscordActivity
| undefined;
const isListeningToSpotify = presence.listening_to_spotify && presence.spotify; const isListeningToSpotify =
presence.listening_to_spotify && presence.spotify;
let primaryActivity = null; let primaryActivity = null;
let activityText = ""; let activityText = '';
let activityImage = ""; let activityImage = '';
if (gameActivity) { if (gameActivity) {
primaryActivity = gameActivity; primaryActivity = gameActivity;
activityText = `playing: ${gameActivity.name.toLowerCase()}`; activityText = `playing: ${gameActivity.name.toLowerCase()}`;
if (gameActivity.details) if (gameActivity.details) activityText += `${gameActivity.details}`;
activityText += `${gameActivity.details}`;
if (gameActivity.assets) { if (gameActivity.assets) {
const targetImage = gameActivity.assets.small_image || gameActivity.assets.large_image; const targetImage =
activityImage = resolveDiscordAsset(gameActivity.application_id, targetImage); gameActivity.assets.small_image || gameActivity.assets.large_image;
activityImage = resolveDiscordAsset(
gameActivity.application_id,
targetImage
);
} }
} } else if (isListeningToSpotify && presence.spotify) {
else if (isListeningToSpotify && presence.spotify) { primaryActivity = { name: 'Spotify' } as DiscordActivity;
primaryActivity = { name: "Spotify" } as DiscordActivity;
activityText = `listening to: ${presence.spotify.song}${presence.spotify.artist}`; activityText = `listening to: ${presence.spotify.song}${presence.spotify.artist}`;
activityImage = presence.spotify.album_art_url || ""; activityImage = presence.spotify.album_art_url || '';
} }
return ( return (
<div className="discord-status-compact"> <div className='discord-status-compact'>
<div className="avatar-container"> <div className='avatar-container'>
<img <img
src={`https://api.lanyard.rest/${userId}.png`} src={`https://api.lanyard.rest/${userId}.png`}
alt="Discord avatar" alt='Discord avatar'
className="discord-avatar" className='discord-avatar'
/> />
<span className={`status-dot ${presence.discord_status}`} /> <span className={`status-dot ${presence.discord_status}`} />
</div> </div>
<div className="status-details"> <div className='status-details'>
<span className="status-text"> <span className='status-text'>
{primaryActivity ? ( {primaryActivity ? (
<>{activityText}</> <>{activityText}</>
) : ( ) : (
<>currently: <em>{STATUS_LABELS[presence.discord_status]}</em></> <>
currently: <em>{STATUS_LABELS[presence.discord_status]}</em>
</>
)} )}
</span> </span>
{customStatusText && ( {customStatusText && (
<span className="status-bubble-inline"> <span className='status-bubble-inline'>{customStatusText}</span>
{customStatusText}
</span>
)} )}
</div> </div>
@@ -254,7 +276,7 @@ export function DiscordStatus({ userId }: DiscordStatusParams) {
<img <img
src={activityImage} src={activityImage}
alt={primaryActivity.name} alt={primaryActivity.name}
className="game-icon" className='game-icon'
/> />
)} )}
</div> </div>
+37 -21
View File
@@ -2,9 +2,16 @@
import './page.css'; import './page.css';
import { Canvas, useFrame, useThree } from "@react-three/fiber"; import { Canvas, useFrame, useThree } from '@react-three/fiber';
import { BrightnessContrast, EffectComposer, HueSaturation, Noise, Pixelation, Vignette } from "@react-three/postprocessing"; import {
import { Suspense, useEffect, useState } from "react"; BrightnessContrast,
EffectComposer,
HueSaturation,
Noise,
Pixelation,
Vignette
} from '@react-three/postprocessing';
import { Suspense, useEffect, useState } from 'react';
import { AmbientSound } from './scene-components/ambient-sound'; import { AmbientSound } from './scene-components/ambient-sound';
@@ -27,16 +34,15 @@ function PostProcessing() {
return () => unsubscribe(); return () => unsubscribe();
}, []); }, []);
return (<EffectComposer> return (
<EffectComposer>
<Pixelation granularity={wasCaught ? 18 : 10} /> <Pixelation granularity={wasCaught ? 18 : 10} />
<Vignette /> <Vignette />
<Noise opacity={wasCaught ? 0.01 : 0.003} /> <Noise opacity={wasCaught ? 0.01 : 0.003} />
<BrightnessContrast <BrightnessContrast brightness={-0.01} contrast={0.05} />
brightness={-0.01}
contrast={0.05}
/>
<HueSaturation saturation={wasCaught ? 1 : 0} /> <HueSaturation saturation={wasCaught ? 1 : 0} />
</EffectComposer>) </EffectComposer>
);
} }
function ListenerCreator() { function ListenerCreator() {
@@ -68,12 +74,13 @@ export default function Fear() {
useEffect(() => { useEffect(() => {
const unsubscribe = fearState.subscribe(() => { const unsubscribe = fearState.subscribe(() => {
setIsRustActive(fearState.isRustActive); setIsRustActive(fearState.isRustActive);
setWasCaught(fearState.wasCaught) setWasCaught(fearState.wasCaught);
}); });
return () => unsubscribe(); return () => unsubscribe();
}, []); }, []);
return (<> return (
<>
<Canvas <Canvas
shadows shadows
gl={{ antialias: true }} gl={{ antialias: true }}
@@ -84,11 +91,17 @@ export default function Fear() {
<ListenerCreator /> <ListenerCreator />
<color attach="background" args={['#050505']} /> <color attach='background' args={['#050505']} />
{FEAR_SETTINGS.TEST_MODE ? <ambientLight intensity={2} /> : <ambientLight intensity={0.0225} />} {FEAR_SETTINGS.TEST_MODE ? (
{FEAR_SETTINGS.TEST_MODE ? null : <fogExp2 attach='fog' args={[0x050505, 0.035]} />} <ambientLight intensity={2} />
{FEAR_SETTINGS.TEST_MODE ? null : < PostProcessing />} ) : (
<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}> <Suspense fallback={null}>
<Hallway /> <Hallway />
@@ -97,24 +110,27 @@ export default function Fear() {
</Suspense> </Suspense>
<AmbientSound <AmbientSound
key="ambient-1" key='ambient-1'
url='fear/snd/ambience.mp3' url='fear/snd/ambience.mp3'
volume={isRustActive ? 0 : 0.5} volume={isRustActive ? 0 : 0.5}
/> />
<AmbientSound <AmbientSound
key="ambient-2" key='ambient-2'
url='fear/snd/ambience2.mp3' url='fear/snd/ambience2.mp3'
volume={isRustActive ? 1 : 0} volume={isRustActive ? 1 : 0}
/> />
{wasCaught ? <AmbientSound {wasCaught ? (
key="ambient-glitch" <AmbientSound
key='ambient-glitch'
url='fear/snd/glitch.mp3' url='fear/snd/glitch.mp3'
volume={1} volume={1}
/> : null} />
) : null}
</Canvas> </Canvas>
<FinaleText /> <FinaleText />
</>) </>
);
} }
+37 -34
View File
@@ -1,64 +1,67 @@
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react';
interface AmbientSoundProps { interface AmbientSoundProps {
url: string url: string;
volume?: number volume?: number;
} }
export function AmbientSound({ url, volume = 0.5 }: AmbientSoundProps) { export function AmbientSound({ url, volume = 0.5 }: AmbientSoundProps) {
const audioRef = useRef<HTMLAudioElement | null>(null) const audioRef = useRef<HTMLAudioElement | null>(null);
const targetVolumeRef = useRef<number>(volume) const targetVolumeRef = useRef<number>(volume);
targetVolumeRef.current = volume targetVolumeRef.current = volume;
useEffect(() => { useEffect(() => {
const audio = new Audio(url) const audio = new Audio(url);
audio.loop = true audio.loop = true;
audio.volume = 0 audio.volume = 0;
audioRef.current = audio audioRef.current = audio;
let componentsMounted = true let componentsMounted = true;
const attemptPlay = () => { const attemptPlay = () => {
if (!audioRef.current || !componentsMounted) return if (!audioRef.current || !componentsMounted) return;
audio.volume = targetVolumeRef.current audio.volume = targetVolumeRef.current;
if (audio.volume > 0 && audio.paused) { if (audio.volume > 0 && audio.paused) {
audio.play().catch((err) => { audio.play().catch((err) => {
console.warn('Autoplay management holding clip playback execution.', err) console.warn(
}) 'Autoplay management holding clip playback execution.',
} err
);
});
} }
};
attemptPlay() attemptPlay();
window.addEventListener('click', attemptPlay) window.addEventListener('click', attemptPlay);
window.addEventListener('keydown', attemptPlay) window.addEventListener('keydown', attemptPlay);
return () => { return () => {
componentsMounted = false componentsMounted = false;
window.removeEventListener('click', attemptPlay) window.removeEventListener('click', attemptPlay);
window.removeEventListener('keydown', attemptPlay) window.removeEventListener('keydown', attemptPlay);
audio.pause() audio.pause();
audio.src = '' audio.src = '';
audioRef.current = null audioRef.current = null;
} };
}, [url]) }, [url]);
useEffect(() => { useEffect(() => {
const audio = audioRef.current const audio = audioRef.current;
if (!audio) return if (!audio) return;
if (volume === 0) { if (volume === 0) {
if (!audio.paused) audio.pause() if (!audio.paused) audio.pause();
} else { } else {
audio.volume = volume audio.volume = volume;
if (audio.paused) { if (audio.paused) {
audio.play().catch(() => {}) audio.play().catch(() => {});
} }
} }
}, [volume]) }, [volume]);
return null return null;
} }
+20 -20
View File
@@ -1,10 +1,10 @@
import { useTexture, PositionalAudio } from "@react-three/drei"; import { useTexture, PositionalAudio } from '@react-three/drei';
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from '@react-three/fiber';
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from 'react';
import * as THREE from "three"; import * as THREE from 'three';
import { FEAR_SETTINGS, fearState } from "../state"; import { FEAR_SETTINGS, fearState } from '../state';
import { ShaderPatch } from "../shader-patch"; import { ShaderPatch } from '../shader-patch';
useTexture.preload('fear/img/creature.png'); useTexture.preload('fear/img/creature.png');
@@ -25,7 +25,9 @@ export default function TheCreature() {
const [isSpawned, setIsSpawned] = useState(false); const [isSpawned, setIsSpawned] = useState(false);
const globalDistance = useRef<number>(32); const globalDistance = useRef<number>(32);
const [finaleTriggered, setFinaleTriggered] = useState(fearState.finaleTriggered); const [finaleTriggered, setFinaleTriggered] = useState(
fearState.finaleTriggered
);
const audioPlaying = useRef<boolean>(false); const audioPlaying = useRef<boolean>(false);
@@ -69,9 +71,7 @@ export default function TheCreature() {
} }
if (!hasTriggered) { if (!hasTriggered) {
if (globalDistance.current < 40) if (globalDistance.current < 40) setHasTriggered(true);
setHasTriggered(true);
} }
if (hasTriggered) { if (hasTriggered) {
@@ -99,7 +99,8 @@ export default function TheCreature() {
audioRef.current.play(); audioRef.current.play();
} }
const shakeIntensity = Math.max(0, 1 - (globalDistance.current / 32)) * 0.22; const shakeIntensity =
Math.max(0, 1 - globalDistance.current / 32) * 0.22;
camera.position.x += (Math.random() - 0.5) * shakeIntensity; camera.position.x += (Math.random() - 0.5) * shakeIntensity;
camera.position.y += (Math.random() - 0.5) * shakeIntensity; camera.position.y += (Math.random() - 0.5) * shakeIntensity;
@@ -114,7 +115,7 @@ export default function TheCreature() {
camera.getWorldDirection(forwardVector); camera.getWorldDirection(forwardVector);
const lookDirZ = forwardVector.z < 0 ? -1 : 1; const lookDirZ = forwardVector.z < 0 ? -1 : 1;
const calculatedZ = camera.position.z + (lookDirZ * globalDistance.current); const calculatedZ = camera.position.z + lookDirZ * globalDistance.current;
creature.position.set(0, 1.6, calculatedZ); creature.position.set(0, 1.6, calculatedZ);
creature.lookAt(camera.position.x, creature.position.y, camera.position.z); creature.lookAt(camera.position.x, creature.position.y, camera.position.z);
@@ -132,7 +133,8 @@ export default function TheCreature() {
glitchCooldown.current = 0.03 + Math.random() * 0.08; glitchCooldown.current = 0.03 + Math.random() * 0.08;
} else { } else {
isGlitchSpiking.current = false; isGlitchSpiking.current = false;
glitchCooldown.current = 0.08 + Math.random() * 0.4 * (1 - proximity * 0.7); glitchCooldown.current =
0.08 + Math.random() * 0.4 * (1 - proximity * 0.7);
} }
} }
@@ -150,7 +152,8 @@ export default function TheCreature() {
flickerCooldown.current = 0.02 + Math.random() * 0.05; flickerCooldown.current = 0.02 + Math.random() * 0.05;
} else { } else {
creature.visible = true; creature.visible = true;
flickerCooldown.current = 0.05 + Math.random() * 0.3 * (1 - proximity * 0.5); flickerCooldown.current =
0.05 + Math.random() * 0.3 * (1 - proximity * 0.5);
} }
} }
@@ -166,10 +169,7 @@ export default function TheCreature() {
}); });
return ( return (
<mesh <mesh ref={meshRef} visible={finaleTriggered}>
ref={meshRef}
visible={finaleTriggered}
>
<planeGeometry args={[3.0, 4.8]} /> <planeGeometry args={[3.0, 4.8]} />
<meshStandardMaterial <meshStandardMaterial
map={texture} map={texture}
@@ -177,14 +177,14 @@ export default function TheCreature() {
depthWrite={false} depthWrite={false}
side={THREE.DoubleSide} side={THREE.DoubleSide}
onBeforeCompile={ShaderPatch} onBeforeCompile={ShaderPatch}
emissive="#ffffff" emissive='#ffffff'
emissiveMap={texture} emissiveMap={texture}
emissiveIntensity={0.15} emissiveIntensity={0.15}
/> />
{finaleTriggered && ( {finaleTriggered && (
<PositionalAudio <PositionalAudio
url="fear/snd/riser.mp3" url='fear/snd/riser.mp3'
ref={audioRef} ref={audioRef}
distance={25} distance={25}
loop={false} loop={false}
@@ -30,11 +30,9 @@
will-change: filter; will-change: filter;
animation: invertFlicker 0.07s infinite alternate; animation: invertFlicker 0.07s infinite alternate;
} }
@keyframes invertFlicker { @keyframes invertFlicker {
0%, 0%,
43%, 43%,
45%, 45%,
@@ -66,7 +64,6 @@
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
} }
.scanlines { .scanlines {
@@ -76,9 +73,10 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 900; z-index: 900;
background: repeating-linear-gradient(rgba(0, 0, 0, 0) 0px, background: repeating-linear-gradient(
rgba(0, 0, 0, 0) 0px,
rgba(0, 0, 0, 0) 2px, rgba(0, 0, 0, 0) 2px,
rgba(0, 0, 0, 0.3) 2px, rgba(0, 0, 0, 0.3) 2px,
rgba(0, 0, 0, 0.3) 4px); rgba(0, 0, 0, 0.3) 4px
);
} }
+52 -22
View File
@@ -1,13 +1,40 @@
import { JSX, useEffect, useState } from "react" import { JSX, useEffect, useState } from 'react';
import { fearState } from "../state" import { fearState } from '../state';
import './finale-text.css'; import './finale-text.css';
const BLOCKS = [ const BLOCKS = [
"▀", "▂", "▃", "▄", "▅", "▆", "▇", '▀',
"█", "▉", "▊", "▋", "▌", "▍", "▎", "▏", '▂',
"▐", "░", "▒", "▓", "▔", "▕", "▖", "▗", '▃',
"▘", "▙", "▚", "▛", "▜", "▝", "▞", "▟" '▄',
'▅',
'▆',
'▇',
'█',
'▉',
'▊',
'▋',
'▌',
'▍',
'▎',
'▏',
'▐',
'░',
'▒',
'▓',
'▔',
'▕',
'▖',
'▗',
'▘',
'▙',
'▚',
'▛',
'▜',
'▝',
'▞',
'▟'
]; ];
export default function FinaleText() { export default function FinaleText() {
@@ -16,27 +43,30 @@ export default function FinaleText() {
useEffect(() => { useEffect(() => {
const unsubscribe = fearState.subscribe(() => { const unsubscribe = fearState.subscribe(() => {
setWasCaught(fearState.wasCaught) setWasCaught(fearState.wasCaught);
}); });
return () => unsubscribe(); return () => unsubscribe();
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!wasCaught) if (!wasCaught) return;
return;
const interval = setInterval(() => { const interval = setInterval(() => {
if (Math.random() > 0.9) return; if (Math.random() > 0.9) return;
const baseText = "bwaaaaaaaaa"; const baseText = 'bwaaaaaaaaa';
const corrupted = baseText const corrupted = baseText
.split("") .split('')
.map((char) => (Math.random() > 0.98 ? BLOCKS[Math.floor(Math.random() * BLOCKS.length)] : char)) .map((char) =>
.join(""); Math.random() > 0.98
? BLOCKS[Math.floor(Math.random() * BLOCKS.length)]
: char
)
.join('');
setElements((prev) => [...prev.slice(-30), setElements((prev) => [
<span className="finale-text" key={crypto.randomUUID()}> ...prev.slice(-30),
<span className='finale-text' key={crypto.randomUUID()}>
{corrupted} {corrupted}
</span> </span>
]); ]);
@@ -47,10 +77,10 @@ export default function FinaleText() {
if (!wasCaught) return null; if (!wasCaught) return null;
return (<> return (
<div className="finale-container"> <>
{elements} <div className='finale-container'>{elements}</div>
</div> <div className='scanlines' />
<div className="scanlines" /> </>
</>) );
} }
+193 -62
View File
@@ -1,10 +1,10 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from 'react';
import { FEAR_SETTINGS, fearState } from "../state"; import { FEAR_SETTINGS, fearState } from '../state';
import { useTexture, PositionalAudio } from "@react-three/drei"; import { useTexture, PositionalAudio } from '@react-three/drei';
import * as THREE from "three"; import * as THREE from 'three';
import { useFrame } from "@react-three/fiber"; import { useFrame } from '@react-three/fiber';
import { ShaderPatch } from "../shader-patch"; import { ShaderPatch } from '../shader-patch';
interface DoorProps { interface DoorProps {
position: [number, number, number]; position: [number, number, number];
@@ -18,7 +18,8 @@ function Door({ position, rotation }: DoorProps) {
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
if (Math.random() < 0.02) { if (Math.random() < 0.02) {
const chosenSound = Math.random() < 0.5 ? "fear/snd/knock1.mp3" : "fear/snd/knock2.mp3"; const chosenSound =
Math.random() < 0.5 ? 'fear/snd/knock1.mp3' : 'fear/snd/knock2.mp3';
setSoundUrl(chosenSound); setSoundUrl(chosenSound);
currentSound.current = chosenSound; currentSound.current = chosenSound;
@@ -38,19 +39,31 @@ function Door({ position, rotation }: DoorProps) {
{/* frame */} {/* frame */}
<mesh position={[0, 2, -0.1]}> <mesh position={[0, 2, -0.1]}>
<boxGeometry args={[2.4, 4.0, 0.2, 4, 4, 1]} /> <boxGeometry args={[2.4, 4.0, 0.2, 4, 4, 1]} />
<meshStandardMaterial map={steelTex} color="#8d8d8d" onBeforeCompile={ShaderPatch} /> <meshStandardMaterial
map={steelTex}
color='#8d8d8d'
onBeforeCompile={ShaderPatch}
/>
</mesh> </mesh>
{/* panel */} {/* panel */}
<mesh position={[0, 1.95, -0.0]}> <mesh position={[0, 1.95, -0.0]}>
<boxGeometry args={[2.1, 3.8, 0.1, 4, 4, 1]} /> <boxGeometry args={[2.1, 3.8, 0.1, 4, 4, 1]} />
<meshStandardMaterial map={steelTex} color="#4e4a4a" onBeforeCompile={ShaderPatch} /> <meshStandardMaterial
map={steelTex}
color='#4e4a4a'
onBeforeCompile={ShaderPatch}
/>
</mesh> </mesh>
{/* handle */} {/* handle */}
<mesh position={[0.75, 1.8, .085]}> <mesh position={[0.75, 1.8, 0.085]}>
<boxGeometry args={[0.3, 0.08, 0.1]} /> <boxGeometry args={[0.3, 0.08, 0.1]} />
<meshStandardMaterial map={steelTex} color="#ffffff" onBeforeCompile={ShaderPatch} /> <meshStandardMaterial
map={steelTex}
color='#ffffff'
onBeforeCompile={ShaderPatch}
/>
</mesh> </mesh>
{soundUrl && ( {soundUrl && (
@@ -83,7 +96,6 @@ export default function Hallway() {
}); });
}, [floorTex, wallTex, rustWallTex, rustFloorTex]); }, [floorTex, wallTex, rustWallTex, rustFloorTex]);
const segmentPool = [0, 1, 2, 3, 4]; const segmentPool = [0, 1, 2, 3, 4];
const segmentCount = segmentPool.length; const segmentCount = segmentPool.length;
@@ -138,7 +150,8 @@ export default function Hallway() {
} else { } else {
const baseWave = Math.sin(time * 45) * 0.4 + Math.sin(time * 90) * 0.3; const baseWave = Math.sin(time * 45) * 0.4 + Math.sin(time * 90) * 0.3;
intensity1 = 0.5 + baseWave; intensity1 = 0.5 + baseWave;
if (Math.sin(time * 150) + Math.cos(time * 220) > 1.2) intensity1 *= Math.random() > 0.5 ? 0.0 : 0.15; if (Math.sin(time * 150) + Math.cos(time * 220) > 1.2)
intensity1 *= Math.random() > 0.5 ? 0.0 : 0.15;
} }
} }
if (lightState.current === 'dead') { if (lightState.current === 'dead') {
@@ -173,7 +186,8 @@ export default function Hallway() {
segmentsRef.current.forEach((segGroup, poolIndex) => { segmentsRef.current.forEach((segGroup, poolIndex) => {
if (!segGroup) return; if (!segGroup) return;
let segmentZIndex = poolIndex - Math.floor(segmentCount / 2) + playerSegmentZ; let segmentZIndex =
poolIndex - Math.floor(segmentCount / 2) + playerSegmentZ;
segGroup.position.z = segmentZIndex * length; segGroup.position.z = segmentZIndex * length;
const distance = Math.abs(segGroup.position.z - state.camera.position.z); const distance = Math.abs(segGroup.position.z - state.camera.position.z);
@@ -182,25 +196,26 @@ export default function Hallway() {
closestPoolIndex = poolIndex; closestPoolIndex = poolIndex;
} }
const leftWallGroup = segGroup.getObjectByName("left-wall-group"); const leftWallGroup = segGroup.getObjectByName('left-wall-group');
if (leftWallGroup) leftWallGroup.position.x = -width / 2; if (leftWallGroup) leftWallGroup.position.x = -width / 2;
const rightWallGroup = segGroup.getObjectByName("right-wall-group"); const rightWallGroup = segGroup.getObjectByName('right-wall-group');
if (rightWallGroup) rightWallGroup.position.x = width / 2; if (rightWallGroup) rightWallGroup.position.x = width / 2;
const floorMesh = segGroup.getObjectByName("floor-mesh"); const floorMesh = segGroup.getObjectByName('floor-mesh');
if (floorMesh) floorMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH; if (floorMesh) floorMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;
const ceilingMesh = segGroup.getObjectByName("ceiling-mesh"); const ceilingMesh = segGroup.getObjectByName('ceiling-mesh');
if (ceilingMesh) ceilingMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH; if (ceilingMesh)
ceilingMesh.scale.x = width / FEAR_SETTINGS.HALLWAY_WIDTH;
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const pipe = segGroup.getObjectByName(`pipe-${i}`); const pipe = segGroup.getObjectByName(`pipe-${i}`);
if (pipe) pipe.position.x = -width / 2 + 0.4 + (i * 0.20); if (pipe) pipe.position.x = -width / 2 + 0.4 + i * 0.2;
} }
const bracketGroup = segGroup.getObjectByName("brackets-group"); const bracketGroup = segGroup.getObjectByName('brackets-group');
if (bracketGroup) { if (bracketGroup) {
bracketGroup.children.forEach(b => { bracketGroup.children.forEach((b) => {
b.position.x = -width / 2 + 0.6; b.position.x = -width / 2 + 0.6;
}); });
} }
@@ -217,7 +232,8 @@ export default function Hallway() {
if (light) light.intensity = intensity1 * 1.2; if (light) light.intensity = intensity1 * 1.2;
if (mat) { if (mat) {
mat.emissiveIntensity = intensity1 * 2.5; mat.emissiveIntensity = intensity1 * 2.5;
if (lightState.current !== 'normal') mat.emissive.setHSL(0.07, 0.4, Math.min(intensity1, 0.7)); if (lightState.current !== 'normal')
mat.emissive.setHSL(0.07, 0.4, Math.min(intensity1, 0.7));
else mat.emissive.setHex(0xa8a1a1); else mat.emissive.setHex(0xa8a1a1);
} }
} else { } else {
@@ -232,8 +248,18 @@ export default function Hallway() {
/* /*
materials materials
*/ */
const updateMaterials = (materials: THREE.MeshStandardMaterial[], defaultTex: THREE.Texture, targetRustTex: THREE.Texture, activeColor: string, defaultColor: string, activeRough: number, defaultRough: number, activeMetal: number, defaultMetal: number) => { const updateMaterials = (
materials.forEach(mat => { materials: THREE.MeshStandardMaterial[],
defaultTex: THREE.Texture,
targetRustTex: THREE.Texture,
activeColor: string,
defaultColor: string,
activeRough: number,
defaultRough: number,
activeMetal: number,
defaultMetal: number
) => {
materials.forEach((mat) => {
if (!mat) return; if (!mat) return;
const targetTex = isRustActive ? targetRustTex : defaultTex; const targetTex = isRustActive ? targetRustTex : defaultTex;
if (mat.map !== targetTex) { if (mat.map !== targetTex) {
@@ -246,19 +272,39 @@ export default function Hallway() {
}); });
}; };
updateMaterials(wallMaterialsRef.current, wallTex, rustWallTex, "#c5c0be", "#ffffff", 0.95, 0.7, 0.05, 0.1); updateMaterials(
updateMaterials(floorMaterialsRef.current, floorTex, rustFloorTex, "#cabdb9", "#ffffff", 0.95, 0.8, 0.05, 0.2); wallMaterialsRef.current,
wallTex,
rustWallTex,
'#c5c0be',
'#ffffff',
0.95,
0.7,
0.05,
0.1
);
updateMaterials(
floorMaterialsRef.current,
floorTex,
rustFloorTex,
'#cabdb9',
'#ffffff',
0.95,
0.8,
0.05,
0.2
);
pipeMaterialsRef.current.forEach(mat => { pipeMaterialsRef.current.forEach((mat) => {
if (!mat) return; if (!mat) return;
mat.color.set(isRustActive ? "#3d1b0f" : "#a5aca8"); mat.color.set(isRustActive ? '#3d1b0f' : '#a5aca8');
mat.roughness = isRustActive ? 0.95 : 0.0; mat.roughness = isRustActive ? 0.95 : 0.0;
mat.metalness = isRustActive ? 0.05 : 0.4; mat.metalness = isRustActive ? 0.05 : 0.4;
}); });
bracketMaterialsRef.current.forEach(mat => { bracketMaterialsRef.current.forEach((mat) => {
if (!mat) return; if (!mat) return;
mat.color.set(isRustActive ? "#1b0b05" : "#a5aca8"); mat.color.set(isRustActive ? '#1b0b05' : '#a5aca8');
mat.roughness = isRustActive ? 0.95 : 0.0; mat.roughness = isRustActive ? 0.95 : 0.0;
mat.metalness = isRustActive ? 0.05 : 0.4; mat.metalness = isRustActive ? 0.05 : 0.4;
}); });
@@ -269,23 +315,35 @@ export default function Hallway() {
{segmentPool.map((poolIndex) => ( {segmentPool.map((poolIndex) => (
<group <group
key={poolIndex} key={poolIndex}
ref={(el) => { if (el) segmentsRef.current[poolIndex] = el; }} ref={(el) => {
if (el) segmentsRef.current[poolIndex] = el;
}}
position={[0, 0, 0]} position={[0, 0, 0]}
> >
{/* lights */} {/* lights */}
<group position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.1, -FEAR_SETTINGS.HALLWAY_LENGTH / 4]}> <group
position={[
0,
FEAR_SETTINGS.HALLWAY_HEIGHT - 0.1,
-FEAR_SETTINGS.HALLWAY_LENGTH / 4
]}
>
<pointLight <pointLight
ref={(el) => { lightRefs.current[poolIndex] = el; }} ref={(el) => {
lightRefs.current[poolIndex] = el;
}}
intensity={0.9} intensity={0.9}
distance={15} distance={15}
color="#a8a1a1" color='#a8a1a1'
/> />
<mesh position={[0, 0.09, 0]}> <mesh position={[0, 0.09, 0]}>
<boxGeometry args={[0.3, 0.01, 0.3]} /> <boxGeometry args={[0.3, 0.01, 0.3]} />
<meshStandardMaterial <meshStandardMaterial
ref={(el) => { matRefs.current[poolIndex] = el; }} ref={(el) => {
color="#111111" matRefs.current[poolIndex] = el;
emissive="#a8a1a1" }}
color='#111111'
emissive='#a8a1a1'
emissiveIntensity={0.8} emissiveIntensity={0.8}
roughness={0.9} roughness={0.9}
onBeforeCompile={ShaderPatch} onBeforeCompile={ShaderPatch}
@@ -295,13 +353,22 @@ export default function Hallway() {
{/* floor */} {/* floor */}
<mesh <mesh
name="floor-mesh" name='floor-mesh'
rotation={[-Math.PI / 2, 0, 0]} rotation={[-Math.PI / 2, 0, 0]}
position={[0, 0, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]} position={[0, 0, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}
> >
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH, 4, 10]} /> <planeGeometry
args={[
FEAR_SETTINGS.HALLWAY_WIDTH,
FEAR_SETTINGS.HALLWAY_LENGTH,
4,
10
]}
/>
<meshStandardMaterial <meshStandardMaterial
ref={(el) => { if (el) floorMaterialsRef.current.push(el); }} ref={(el) => {
if (el) floorMaterialsRef.current.push(el);
}}
map={floorTex} map={floorTex}
onBeforeCompile={ShaderPatch} onBeforeCompile={ShaderPatch}
/> />
@@ -309,48 +376,102 @@ export default function Hallway() {
{/* ceiling */} {/* ceiling */}
<mesh <mesh
name="ceiling-mesh" name='ceiling-mesh'
rotation={[Math.PI / 2, 0, 0]} rotation={[Math.PI / 2, 0, 0]}
position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]} position={[
0,
FEAR_SETTINGS.HALLWAY_HEIGHT,
-FEAR_SETTINGS.HALLWAY_LENGTH / 2
]}
> >
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_WIDTH, FEAR_SETTINGS.HALLWAY_LENGTH, 4, 10]} /> <planeGeometry
args={[
FEAR_SETTINGS.HALLWAY_WIDTH,
FEAR_SETTINGS.HALLWAY_LENGTH,
4,
10
]}
/>
<meshStandardMaterial <meshStandardMaterial
ref={(el) => { if (el) floorMaterialsRef.current.push(el); }} ref={(el) => {
if (el) floorMaterialsRef.current.push(el);
}}
map={floorTex} map={floorTex}
onBeforeCompile={ShaderPatch} onBeforeCompile={ShaderPatch}
/> />
</mesh> </mesh>
{/* left wall */} {/* left wall */}
<group name="left-wall-group"> <group name='left-wall-group'>
<mesh rotation={[0, Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}> <mesh
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT, 10, 4]} /> 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,
10,
4
]}
/>
<meshStandardMaterial <meshStandardMaterial
ref={(el) => { if (el) wallMaterialsRef.current.push(el); }} ref={(el) => {
if (el) wallMaterialsRef.current.push(el);
}}
map={wallTex} map={wallTex}
onBeforeCompile={ShaderPatch} onBeforeCompile={ShaderPatch}
/> />
</mesh> </mesh>
{!isRustActive && ( {!isRustActive && (
<> <>
<Door position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.25]} rotation={[0, Math.PI / 2, 0]} /> <Door
<Door position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.85]} rotation={[0, Math.PI / 2, 0]} /> position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.25]}
rotation={[0, Math.PI / 2, 0]}
/>
<Door
position={[0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.85]}
rotation={[0, Math.PI / 2, 0]}
/>
</> </>
)} )}
</group> </group>
{/* right wall */} {/* right wall */}
<group name="right-wall-group"> <group name='right-wall-group'>
<mesh rotation={[0, -Math.PI / 2, 0]} position={[0, FEAR_SETTINGS.HALLWAY_HEIGHT / 2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]}> <mesh
<planeGeometry args={[FEAR_SETTINGS.HALLWAY_LENGTH, FEAR_SETTINGS.HALLWAY_HEIGHT, 10, 4]} /> 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,
10,
4
]}
/>
<meshStandardMaterial <meshStandardMaterial
ref={(el) => { if (el) wallMaterialsRef.current.push(el); }} ref={(el) => {
if (el) wallMaterialsRef.current.push(el);
}}
map={wallTex} map={wallTex}
onBeforeCompile={ShaderPatch} onBeforeCompile={ShaderPatch}
/> />
</mesh> </mesh>
{!isRustActive && ( {!isRustActive && (
<Door position={[-0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.65]} rotation={[0, -Math.PI / 2, 0]} /> <Door
position={[-0.05, 0, -FEAR_SETTINGS.HALLWAY_LENGTH * 0.65]}
rotation={[0, -Math.PI / 2, 0]}
/>
)} )}
</group> </group>
@@ -360,12 +481,18 @@ export default function Hallway() {
key={idx} key={idx}
name={`pipe-${idx}`} name={`pipe-${idx}`}
rotation={[Math.PI / 2, 0, 0]} rotation={[Math.PI / 2, 0, 0]}
position={[-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.4 + (idx * 0.20), FEAR_SETTINGS.HALLWAY_HEIGHT - 0.2, -FEAR_SETTINGS.HALLWAY_LENGTH / 2]} position={[
-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.4 + idx * 0.2,
FEAR_SETTINGS.HALLWAY_HEIGHT - 0.2,
-FEAR_SETTINGS.HALLWAY_LENGTH / 2
]}
> >
<cylinderGeometry args={[0.06, 0.06, FEAR_SETTINGS.HALLWAY_LENGTH, 4]} /> <cylinderGeometry
args={[0.06, 0.06, FEAR_SETTINGS.HALLWAY_LENGTH, 4]}
/>
<meshStandardMaterial <meshStandardMaterial
ref={(el) => el && pipeMaterialsRef.current.push(el)} ref={(el) => el && pipeMaterialsRef.current.push(el)}
color="#a5aca8" color='#a5aca8'
roughness={0.0} roughness={0.0}
metalness={0.4} metalness={0.4}
onBeforeCompile={ShaderPatch} onBeforeCompile={ShaderPatch}
@@ -374,18 +501,22 @@ export default function Hallway() {
))} ))}
{/* brackets */} {/* brackets */}
<group name="brackets-group"> <group name='brackets-group'>
{Array.from({ length: 5 }).map((_, idx) => { {Array.from({ length: 5 }).map((_, idx) => {
const zOffset = -(idx * 8 + 4); const zOffset = -(idx * 8 + 4);
return ( return (
<mesh <mesh
key={`bracket-${idx}`} key={`bracket-${idx}`}
position={[-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.6, FEAR_SETTINGS.HALLWAY_HEIGHT - 0.15, zOffset]} position={[
-FEAR_SETTINGS.HALLWAY_WIDTH / 2 + 0.6,
FEAR_SETTINGS.HALLWAY_HEIGHT - 0.15,
zOffset
]}
> >
<boxGeometry args={[0.7, 0.3, 0.15]} /> <boxGeometry args={[0.7, 0.3, 0.15]} />
<meshStandardMaterial <meshStandardMaterial
ref={(el) => el && bracketMaterialsRef.current.push(el)} ref={(el) => el && bracketMaterialsRef.current.push(el)}
color="#a5aca8" color='#a5aca8'
roughness={0.0} roughness={0.0}
metalness={0.4} metalness={0.4}
onBeforeCompile={ShaderPatch} onBeforeCompile={ShaderPatch}
+61 -33
View File
@@ -1,8 +1,8 @@
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from '@react-three/fiber';
import { useEffect, useRef } from "react"; import { useEffect, useRef } from 'react';
import { FEAR_SETTINGS, fearState } from "../state"; import { FEAR_SETTINGS, fearState } from '../state';
import { PointerLockControls } from "@react-three/drei"; import { PointerLockControls } from '@react-three/drei';
import * as THREE from "three"; import * as THREE from 'three';
const forward = new THREE.Vector3(); const forward = new THREE.Vector3();
const side = new THREE.Vector3(); const side = new THREE.Vector3();
@@ -14,21 +14,33 @@ const targetVelocity = new THREE.Vector3();
const currentVelocity = new THREE.Vector3(); const currentVelocity = new THREE.Vector3();
function usePlayerControls() { function usePlayerControls() {
const keys = useRef({ Forward: false, Backward: false, Left: false, Right: false }); const keys = useRef({
Forward: false,
Backward: false,
Left: false,
Right: false
});
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = true; if (e.code === 'KeyW' || e.code === 'ArrowUp')
if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = true; keys.current.Forward = true;
if (e.code === 'KeyS' || e.code === 'ArrowDown')
keys.current.Backward = true;
if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = true; if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = true;
if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = true; if (e.code === 'KeyD' || e.code === 'ArrowRight')
keys.current.Right = true;
}; };
const handleKeyUp = (e: KeyboardEvent) => { const handleKeyUp = (e: KeyboardEvent) => {
if (e.code === 'KeyW' || e.code === 'ArrowUp') keys.current.Forward = false; if (e.code === 'KeyW' || e.code === 'ArrowUp')
if (e.code === 'KeyS' || e.code === 'ArrowDown') keys.current.Backward = false; keys.current.Forward = false;
if (e.code === 'KeyA' || e.code === 'ArrowLeft') keys.current.Left = false; if (e.code === 'KeyS' || e.code === 'ArrowDown')
if (e.code === 'KeyD' || e.code === 'ArrowRight') keys.current.Right = false; keys.current.Backward = false;
if (e.code === 'KeyA' || e.code === 'ArrowLeft')
keys.current.Left = false;
if (e.code === 'KeyD' || e.code === 'ArrowRight')
keys.current.Right = false;
}; };
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown);
@@ -57,10 +69,12 @@ export default function Player() {
const footstepAudio = useRef<HTMLAudioElement[]>([]); const footstepAudio = useRef<HTMLAudioElement[]>([]);
const hasStepped = useRef<boolean>(false); const hasStepped = useRef<boolean>(false);
useEffect(() => { useEffect(() => {
playerRoot.set(camera.position.x, FEAR_SETTINGS.PLAYER_HEIGHT, camera.position.z); playerRoot.set(
camera.position.x,
FEAR_SETTINGS.PLAYER_HEIGHT,
camera.position.z
);
footstepAudio.current = Array.from({ length: 6 }, (_, i) => { footstepAudio.current = Array.from({ length: 6 }, (_, i) => {
const audio = new Audio(`fear/snd/footstep${i + 1}.mp3`); const audio = new Audio(`fear/snd/footstep${i + 1}.mp3`);
audio.volume = 0.4; audio.volume = 0.4;
@@ -71,12 +85,17 @@ export default function Player() {
const playRandomFootstep = () => { const playRandomFootstep = () => {
if (footstepAudio.current.length === 0) return; if (footstepAudio.current.length === 0) return;
const randomIndex = Math.floor(Math.random() * footstepAudio.current.length); const randomIndex = Math.floor(
Math.random() * footstepAudio.current.length
);
const audio = footstepAudio.current[randomIndex]; const audio = footstepAudio.current[randomIndex];
audio.currentTime = 0; audio.currentTime = 0;
audio.play().catch((err) => { audio.play().catch((err) => {
console.warn("Footstep playback blocked by browser autocomplete/interaction rules.", err); console.warn(
'Footstep playback blocked by browser autocomplete/interaction rules.',
err
);
}); });
}; };
@@ -107,16 +126,21 @@ export default function Player() {
const maxX = fearState.currentWidth / 2 - FEAR_SETTINGS.WALL_BUFFER; const maxX = fearState.currentWidth / 2 - FEAR_SETTINGS.WALL_BUFFER;
playerRoot.x = THREE.MathUtils.clamp(playerRoot.x, minX, maxX); playerRoot.x = THREE.MathUtils.clamp(playerRoot.x, minX, maxX);
const isMoving = controls.Forward || controls.Backward || controls.Left || controls.Right; const isMoving =
controls.Forward || controls.Backward || controls.Left || controls.Right;
bobIntensity.current = THREE.MathUtils.lerp(bobIntensity.current, isMoving ? 1 : 0, 8 * dt); bobIntensity.current = THREE.MathUtils.lerp(
bobIntensity.current,
isMoving ? 1 : 0,
8 * dt
);
if (isMoving) if (isMoving) movementCounter.current += dt * 12;
movementCounter.current += dt * 12;
const sinWave = Math.sin(movementCounter.current); const sinWave = Math.sin(movementCounter.current);
const moveBobY = sinWave * 0.06 * bobIntensity.current; const moveBobY = sinWave * 0.06 * bobIntensity.current;
const moveBobX = Math.cos(movementCounter.current / 2) * 0.04 * bobIntensity.current; const moveBobX =
Math.cos(movementCounter.current / 2) * 0.04 * bobIntensity.current;
if (isMoving && sinWave < -0.9) { if (isMoving && sinWave < -0.9) {
if (!hasStepped.current) { if (!hasStepped.current) {
@@ -128,7 +152,8 @@ export default function Player() {
} }
const breatheTime = state.clock.elapsedTime * 1.8; const breatheTime = state.clock.elapsedTime * 1.8;
const breatheBobY = Math.sin(breatheTime) * 0.03 * (1 - bobIntensity.current * 0.5); const breatheBobY =
Math.sin(breatheTime) * 0.03 * (1 - bobIntensity.current * 0.5);
camera.position.copy(playerRoot); camera.position.copy(playerRoot);
camera.position.y += moveBobY + breatheBobY; camera.position.y += moveBobY + breatheBobY;
@@ -138,22 +163,22 @@ export default function Player() {
flashlightRef.current.position.lerp(camera.position, 7 * dt); flashlightRef.current.position.lerp(camera.position, 7 * dt);
camera.getWorldDirection(viewDirection); camera.getWorldDirection(viewDirection);
targetDest targetDest.copy(camera.position).addScaledVector(viewDirection, 10);
.copy(camera.position)
.addScaledVector(viewDirection, 10);
flashlightRef.current.target.position.lerp(targetDest, 12 * dt); flashlightRef.current.target.position.lerp(targetDest, 12 * dt);
flashlightRef.current.target.updateMatrixWorld(); flashlightRef.current.target.updateMatrixWorld();
flashlightRef.current.intensity = flashlightRef.current.intensity =
FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE + FEAR_SETTINGS.FLASHLIGHT_INTENSITY_BASE +
Math.sin(state.clock.elapsedTime * 30) * 0.15 * Math.cos(state.clock.elapsedTime * 3); Math.sin(state.clock.elapsedTime * 30) *
0.15 *
Math.cos(state.clock.elapsedTime * 3);
} }
const length = FEAR_SETTINGS.HALLWAY_LENGTH; const length = FEAR_SETTINGS.HALLWAY_LENGTH;
const absoluteZ = -playerRoot.z; const absoluteZ = -playerRoot.z;
const rawSegmentIndex = Math.floor(absoluteZ / length); const rawSegmentIndex = Math.floor(absoluteZ / length);
const progressZ = ((absoluteZ % length) + length) % length / length; const progressZ = (((absoluteZ % length) + length) % length) / length;
if (rawSegmentIndex > confirmedSegment.current && progressZ > 0.25) { if (rawSegmentIndex > confirmedSegment.current && progressZ > 0.25) {
if (!hasTriggeredThisSegment.current) { if (!hasTriggeredThisSegment.current) {
@@ -161,8 +186,7 @@ export default function Player() {
hasTriggeredThisSegment.current = true; hasTriggeredThisSegment.current = true;
} }
confirmedSegment.current = rawSegmentIndex; confirmedSegment.current = rawSegmentIndex;
} } else if (rawSegmentIndex < confirmedSegment.current && progressZ < 0.75) {
else if (rawSegmentIndex < confirmedSegment.current && progressZ < 0.75) {
if (!hasTriggeredThisSegment.current) { if (!hasTriggeredThisSegment.current) {
fearState.registerLoop('backward'); fearState.registerLoop('backward');
hasTriggeredThisSegment.current = true; hasTriggeredThisSegment.current = true;
@@ -170,7 +194,11 @@ export default function Player() {
confirmedSegment.current = rawSegmentIndex; confirmedSegment.current = rawSegmentIndex;
} }
if (rawSegmentIndex === confirmedSegment.current && progressZ > 0.35 && progressZ < 0.65) { if (
rawSegmentIndex === confirmedSegment.current &&
progressZ > 0.35 &&
progressZ < 0.65
) {
hasTriggeredThisSegment.current = false; hasTriggeredThisSegment.current = false;
} }
}); });
@@ -184,7 +212,7 @@ export default function Player() {
angle={0.35} angle={0.35}
penumbra={0.8} penumbra={0.8}
intensity={0} intensity={0}
color="#fffaed" color='#fffaed'
decay={2} decay={2}
castShadow castShadow
shadow-bias={-0.001} shadow-bias={-0.001}
+5 -1
View File
@@ -1,4 +1,8 @@
export function ShaderPatch(shader: { vertexShader: string, fragmentShader: string, uniforms: Object }) { export function ShaderPatch(shader: {
vertexShader: string;
fragmentShader: string;
uniforms: Object;
}) {
shader.vertexShader = ` shader.vertexShader = `
varying float vDepth; varying float vDepth;
#ifdef USE_MAP #ifdef USE_MAP
+18 -6
View File
@@ -31,7 +31,9 @@ export const fearState = {
subscribe(listener: () => void) { subscribe(listener: () => void) {
listeners.add(listener); listeners.add(listener);
return () => { listeners.delete(listener); }; return () => {
listeners.delete(listener);
};
}, },
emit() { emit() {
@@ -39,8 +41,15 @@ export const fearState = {
}, },
update(delta: number) { update(delta: number) {
const targetWidth = this.loopCount >= FEAR_SETTINGS.EVENT_NARROW_LOOP_COUNT ? 2.5 : FEAR_SETTINGS.HALLWAY_WIDTH; const targetWidth =
const newWidth = THREE.MathUtils.lerp(this.currentWidth, targetWidth, 2 * delta); this.loopCount >= FEAR_SETTINGS.EVENT_NARROW_LOOP_COUNT
? 2.5
: FEAR_SETTINGS.HALLWAY_WIDTH;
const newWidth = THREE.MathUtils.lerp(
this.currentWidth,
targetWidth,
2 * delta
);
if (Math.abs(this.currentWidth - newWidth) > 0.001) { if (Math.abs(this.currentWidth - newWidth) > 0.001) {
this.currentWidth = newWidth; this.currentWidth = newWidth;
@@ -48,8 +57,10 @@ export const fearState = {
if (this.wasCaught) { if (this.wasCaught) {
if (this.finaleProgression < FEAR_SETTINGS.EVENT_FINALE_DURATION) { if (this.finaleProgression < FEAR_SETTINGS.EVENT_FINALE_DURATION) {
this.finaleProgression = Math.min(
this.finaleProgression = Math.min(this.finaleProgression + delta, FEAR_SETTINGS.EVENT_FINALE_DURATION); this.finaleProgression + delta,
FEAR_SETTINGS.EVENT_FINALE_DURATION
);
} else { } else {
window.location.href = '/'; window.location.href = '/';
} }
@@ -62,7 +73,8 @@ export const fearState = {
this.loopCount += 1; this.loopCount += 1;
this.isRustActive = this.loopCount >= FEAR_SETTINGS.EVENT_RUST_LOOP_COUNT; this.isRustActive = this.loopCount >= FEAR_SETTINGS.EVENT_RUST_LOOP_COUNT;
this.finaleTriggered = this.loopCount >= FEAR_SETTINGS.EVENT_FINALE_LOOP_COUNT; this.finaleTriggered =
this.loopCount >= FEAR_SETTINGS.EVENT_FINALE_LOOP_COUNT;
this.emit(); this.emit();
}, },
+28 -16
View File
@@ -2,10 +2,21 @@
import './page.css'; import './page.css';
import { Environment, OrbitControls, useProgress } from "@react-three/drei"; import { Environment, OrbitControls, useProgress } from '@react-three/drei';
import { Canvas, useLoader } from '@react-three/fiber'; import { Canvas, useLoader } from '@react-three/fiber';
import { Bloom, BrightnessContrast, DepthOfField, EffectComposer, HueSaturation, LUT, Noise, SMAA, SSAO, Vignette } from '@react-three/postprocessing'; import {
import { useLayoutEffect, useState } from "react"; Bloom,
BrightnessContrast,
DepthOfField,
EffectComposer,
HueSaturation,
LUT,
Noise,
SMAA,
SSAO,
Vignette
} from '@react-three/postprocessing';
import { useLayoutEffect, useState } from 'react';
import { folder, useControls, Leva } from 'leva'; import { folder, useControls, Leva } from 'leva';
import SealCube from './scene-components/sealcube'; import SealCube from './scene-components/sealcube';
import Terrain from './scene-components/terrain'; import Terrain from './scene-components/terrain';
@@ -62,7 +73,7 @@ function Scene() {
hillScale: { value: 0.15, min: 0.01, max: 0.5, step: 0.01 }, hillScale: { value: 0.15, min: 0.01, max: 0.5, step: 0.01 },
hillHeight: { value: 4.0, min: 0.0, max: 20.0, step: 0.5 }, hillHeight: { value: 4.0, min: 0.0, max: 20.0, step: 0.5 },
detailScale: { value: 1.0, min: 0.1, max: 5.0, step: 0.1 }, detailScale: { value: 1.0, min: 0.1, max: 5.0, step: 0.1 },
detailHeight: { value: 0.3, min: 0.0, max: 2.0, step: 0.05 }, detailHeight: { value: 0.3, min: 0.0, max: 2.0, step: 0.05 }
}), }),
Grass: folder({ Grass: folder({
grassDryColor: '#495a17', grassDryColor: '#495a17',
@@ -73,11 +84,12 @@ function Scene() {
grassBlades: { value: 3, min: 1, max: 5, step: 1 }, grassBlades: { value: 3, min: 1, max: 5, step: 1 },
grassSegments: { value: 4, min: 1, max: 5, step: 1 }, grassSegments: { value: 4, min: 1, max: 5, step: 1 },
grassLODStart: { value: 0.15, min: 0.0, max: 0.9, step: 0.05 }, grassLODStart: { value: 0.15, min: 0.0, max: 0.9, step: 0.05 },
grassLODExponent: { value: 1.8, min: 0.5, max: 3.0, step: 0.1 }, grassLODExponent: { value: 1.8, min: 0.5, max: 3.0, step: 0.1 }
}) })
}); });
return (<> return (
<>
<Environment <Environment
files={'niko/hdr/sky.hdr'} files={'niko/hdr/sky.hdr'}
environmentIntensity={0.85} environmentIntensity={0.85}
@@ -87,10 +99,7 @@ function Scene() {
<fogExp2 attach='fog' args={[0xa3a5ba, 0.0125]} /> <fogExp2 attach='fog' args={[0xa3a5ba, 0.0125]} />
<ambientLight intensity={0.5} /> <ambientLight intensity={0.5} />
<directionalLight <directionalLight position={[15, 25, 15]} intensity={1} />
position={[15, 25, 15]}
intensity={1}
/>
<Terrain <Terrain
chunks={chunks} chunks={chunks}
@@ -115,7 +124,8 @@ function Scene() {
/> />
<SealCube /> <SealCube />
</>) </>
);
} }
function LutEffect() { function LutEffect() {
@@ -124,7 +134,8 @@ function LutEffect() {
} }
function PostProcessing() { function PostProcessing() {
return (<EffectComposer> return (
<EffectComposer>
<DepthOfField target={[0, 3, 0]} focalLength={10} bokehScale={5} /> <DepthOfField target={[0, 3, 0]} focalLength={10} bokehScale={5} />
<Vignette /> <Vignette />
<Noise opacity={0.05} /> <Noise opacity={0.05} />
@@ -137,7 +148,8 @@ function PostProcessing() {
<HueSaturation saturation={0.3} /> <HueSaturation saturation={0.3} />
<BrightnessContrast brightness={0.05} contrast={-0.1} /> <BrightnessContrast brightness={0.05} contrast={-0.1} />
<LutEffect /> <LutEffect />
</EffectComposer>) </EffectComposer>
);
} }
export default function Seal() { export default function Seal() {
@@ -151,11 +163,11 @@ export default function Seal() {
<Canvas <Canvas
shadows shadows
camera={{ position: [0, 5, 15], fov: 50, far: 100 }} camera={{ position: [0, 5, 15], fov: 50, far: 100 }}
gl={{ antialias: false, powerPreference: "high-performance" }} gl={{ antialias: false, powerPreference: 'high-performance' }}
className='canvas' className='canvas'
> >
<AmbientSound url="niko/snd/wind.mp3" volume={0.4} /> <AmbientSound url='niko/snd/wind.mp3' volume={0.4} />
<AmbientSound url="niko/snd/birds.mp3" volume={0.1} /> <AmbientSound url='niko/snd/birds.mp3' volume={0.1} />
<Scene /> <Scene />
<PostProcessing /> <PostProcessing />
+23 -23
View File
@@ -1,43 +1,43 @@
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react';
interface AmbientSoundProps { interface AmbientSoundProps {
url: string url: string;
volume?: number volume?: number;
} }
export function AmbientSound({ url, volume = 0.5 }: AmbientSoundProps) { export function AmbientSound({ url, volume = 0.5 }: AmbientSoundProps) {
const audioRef = useRef<HTMLAudioElement | null>(null) const audioRef = useRef<HTMLAudioElement | null>(null);
useEffect(() => { useEffect(() => {
const audio = new Audio(url) const audio = new Audio(url);
audio.loop = true audio.loop = true;
audio.volume = volume audio.volume = volume;
audioRef.current = audio audioRef.current = audio;
const startAudio = () => { const startAudio = () => {
audio.play().catch((err) => { audio.play().catch((err) => {
console.warn('Autoplay blocked. Waiting for user interaction.', err) console.warn('Autoplay blocked. Waiting for user interaction.', err);
}) });
} };
startAudio() startAudio();
window.addEventListener('click', startAudio, { once: true }) window.addEventListener('click', startAudio, { once: true });
window.addEventListener('keydown', startAudio, { once: true }) window.addEventListener('keydown', startAudio, { once: true });
return () => { return () => {
window.removeEventListener('click', startAudio) window.removeEventListener('click', startAudio);
window.removeEventListener('keydown', startAudio) window.removeEventListener('keydown', startAudio);
audio.pause() audio.pause();
audioRef.current = null audioRef.current = null;
} };
}, [url]) }, [url]);
useEffect(() => { useEffect(() => {
if (audioRef.current) { if (audioRef.current) {
audioRef.current.volume = volume audioRef.current.volume = volume;
} }
}, [volume]) }, [volume]);
return null return null;
} }
+15 -5
View File
@@ -1,7 +1,16 @@
import { useFrame, useLoader } from "@react-three/fiber"; import { useFrame, useLoader } from '@react-three/fiber';
import { useLayoutEffect, useMemo, useRef } from "react"; import { useLayoutEffect, useMemo, useRef } from 'react';
import { BufferAttribute, BufferGeometry, Color, DoubleSide, InstancedMesh, MeshStandardMaterial, Object3D, TextureLoader } from "three"; import {
import { getTerrainHeight, Shader } from "./helpers"; BufferAttribute,
BufferGeometry,
Color,
DoubleSide,
InstancedMesh,
MeshStandardMaterial,
Object3D,
TextureLoader
} from 'three';
import { getTerrainHeight, Shader } from './helpers';
import grassVert from './shaders/grass.vert'; import grassVert from './shaders/grass.vert';
import grassFrag from './shaders/grass.frag'; import grassFrag from './shaders/grass.frag';
@@ -187,7 +196,8 @@ export default function Grass({
const perBladeRandom = Math.random() * 0.4; const perBladeRandom = Math.random() * 0.4;
const grassWidth = grassSize * (0.7 + Math.random() * 0.5); const grassWidth = grassSize * (0.7 + Math.random() * 0.5);
const grassHeight = grassSize * (0.4 + macroHeight * 0.8 + microHeight + perBladeRandom); const grassHeight =
grassSize * (0.4 + macroHeight * 0.8 + microHeight + perBladeRandom);
dummy.scale.set(grassWidth, grassHeight, grassWidth); dummy.scale.set(grassWidth, grassHeight, grassWidth);
+5 -4
View File
@@ -1,6 +1,6 @@
import { useFrame, useLoader } from "@react-three/fiber"; import { useFrame, useLoader } from '@react-three/fiber';
import { forwardRef, useImperativeHandle, useRef } from "react"; import { forwardRef, useImperativeHandle, useRef } from 'react';
import { Mesh, TextureLoader } from "three"; import { Mesh, TextureLoader } from 'three';
const SealCube = forwardRef<Mesh>((props, ref) => { const SealCube = forwardRef<Mesh>((props, ref) => {
const texture = useLoader(TextureLoader, 'niko/img/niko.jpg'); const texture = useLoader(TextureLoader, 'niko/img/niko.jpg');
@@ -12,7 +12,8 @@ const SealCube = forwardRef<Mesh>((props, ref) => {
if (meshRef.current) { if (meshRef.current) {
meshRef.current.rotation.x += delta * 0.5; meshRef.current.rotation.x += delta * 0.5;
meshRef.current.rotation.y += delta * 0.5; meshRef.current.rotation.y += delta * 0.5;
meshRef.current.position.y = 3 + Math.sin(state.clock.getElapsedTime() * 1) * 0.15; meshRef.current.position.y =
3 + Math.sin(state.clock.getElapsedTime() * 1) * 0.15;
} }
}); });
+9 -7
View File
@@ -1,8 +1,8 @@
import { useMemo, useRef } from "react"; import { useMemo, useRef } from 'react';
import { BufferAttribute, BufferGeometry, Color, Mesh } from "three"; import { BufferAttribute, BufferGeometry, Color, Mesh } from 'three';
import { getTerrainHeight } from "./helpers"; import { getTerrainHeight } from './helpers';
import Grass from "./grass"; import Grass from './grass';
import { createNoise2D } from "simplex-noise"; import { createNoise2D } from 'simplex-noise';
interface TerrainChunkProps { interface TerrainChunkProps {
x: number; x: number;
@@ -54,8 +54,10 @@ function TerrainChunk({
const worldXBase = x * size; const worldXBase = x * size;
const worldZBase = y * size; const worldZBase = y * size;
const minX = Math.abs(worldXBase) <= halfSize ? 0 : Math.abs(worldXBase) - halfSize; const minX =
const minZ = Math.abs(worldZBase) <= halfSize ? 0 : Math.abs(worldZBase) - halfSize; Math.abs(worldXBase) <= halfSize ? 0 : Math.abs(worldXBase) - halfSize;
const minZ =
Math.abs(worldZBase) <= halfSize ? 0 : Math.abs(worldZBase) - halfSize;
const chunkMinDist = Math.sqrt(minX * minX + minZ * minZ); const chunkMinDist = Math.sqrt(minX * minX + minZ * minZ);
const shouldRenderGrass = chunkMinDist < grassLOD; const shouldRenderGrass = chunkMinDist < grassLOD;
+27 -10
View File
@@ -22,12 +22,14 @@
} }
* { * {
cursor: url('/cur/kuromi.webp') 32 32, auto; cursor:
url('/cur/kuromi.webp') 32 32,
auto;
} }
body { body {
color: var(--text-main); color: var(--text-main);
font-family: "Times New Roman", Times, serif; font-family: 'Times New Roman', Times, serif;
text-align: center; text-align: center;
display: flex; display: flex;
@@ -68,7 +70,9 @@ body {
border: 1px solid var(--pink-accent); border: 1px solid var(--pink-accent);
outline: 4px solid #fff; outline: 4px solid #fff;
box-shadow: 0 0 0 5px var(--accent), 8px 8px 0px rgba(215, 230, 255, 0.5); box-shadow:
0 0 0 5px var(--accent),
8px 8px 0px rgba(215, 230, 255, 0.5);
border-radius: 2px; border-radius: 2px;
animation: float 5s ease-in-out infinite; animation: float 5s ease-in-out infinite;
@@ -79,14 +83,20 @@ body {
top: 10px; top: 10px;
font-size: 1.5rem; font-size: 1.5rem;
color: var(--sparkle); color: var(--sparkle);
text-shadow: 0 0 20px var(--sparkle-glow), 0 0 15px var(--sparkle-glow), 0 0 10px var(--sparkle-glow), 0 0 5px var(--sparkle-glow); text-shadow:
0 0 20px var(--sparkle-glow),
0 0 15px var(--sparkle-glow),
0 0 10px var(--sparkle-glow),
0 0 5px var(--sparkle-glow);
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
line-height: 1; line-height: 1;
height: 24px; height: 24px;
width: 24px; width: 24px;
cursor: url('/cur/kuromi-hearts.webp') 0 0, auto !important; cursor:
url('/cur/kuromi-hearts.webp') 0 0,
auto !important;
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
@@ -104,7 +114,7 @@ body {
} }
.content-box::before { .content-box::before {
content: "•"; content: '•';
position: absolute; position: absolute;
top: -4px; top: -4px;
left: 6px; left: 6px;
@@ -204,15 +214,17 @@ h1 {
} }
.directory a::before { .directory a::before {
content: " " content: ' ';
} }
.directory a::after { .directory a::after {
content: " " content: ' ';
} }
.directory a:hover { .directory a:hover {
cursor: url('/cur/kuromi-hearts.webp') 0 0, auto !important; cursor:
url('/cur/kuromi-hearts.webp') 0 0,
auto !important;
color: var(--text-header); color: var(--text-header);
box-shadow: 1px 1px 0px var(--pink-accent); box-shadow: 1px 1px 0px var(--pink-accent);
background: var(--pink-accent); background: var(--pink-accent);
@@ -234,7 +246,12 @@ h1 {
border-top: 1px double var(--pink-accent); border-top: 1px double var(--pink-accent);
border-bottom: 1px double var(--pink-accent); border-bottom: 1px double var(--pink-accent);
background: linear-gradient(to right, transparent, rgba(255, 214, 245, 0.3), transparent); background: linear-gradient(
to right,
transparent,
rgba(255, 214, 245, 0.3),
transparent
);
} }
.marquee-track { .marquee-track {
+120 -46
View File
@@ -4,10 +4,10 @@ import { useEffect, useState } from 'react';
import './page.css'; import './page.css';
import { DiscordStatus } from './components/discordstatus'; import { DiscordStatus } from './components/discordstatus';
const TWITTER_LINK = "https://x.com/neruu444" const TWITTER_LINK = 'https://x.com/neruu444';
const DISCORD_USER = "neru444" const DISCORD_USER = 'neru444';
const DISCORD_ID = "1104474057916809226" const DISCORD_ID = '1104474057916809226';
const STEAM_LINK = "https://steamcommunity.com/profiles/76561198440714757/" const STEAM_LINK = 'https://steamcommunity.com/profiles/76561198440714757/';
function Content() { function Content() {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@@ -16,66 +16,136 @@ function Content() {
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) if (e.key === 'Escape' && isOpen) setIsOpen(false);
setIsOpen(false);
}; };
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown);
}, [isOpen]); }, [isOpen]);
const marqueeText = "✧ ꒰ა˵• ﻌ •˵ა꒱ ✧ ฅ^•ﻌ•^ฅ ✧ ᶻ 𝗓 𐰁 /ᐠ. 。 .ᐟ\\ ✧ ฅ/ᐠ. ̫ .ᐟ\\ฅ ✧ ꒰ა≽^•⩊•^≼໒꒱ ✧ ₍˄·͈༝·͈˄₎ ✧ /ᐠ. ⩊ .ᐟ\\ノ ✧ 𓏲ּ ֶָ ࣪ /ᐠ .ᆺ. ᐟ\\ノ ✧"; const marqueeText =
'✧ ꒰ა˵• ﻌ •˵ა꒱ ✧ ฅ^•ﻌ•^ฅ ✧ ᶻ 𝗓 𐰁 /ᐠ. 。 .ᐟ\\ ✧ ฅ/ᐠ. ̫ .ᐟ\\ฅ ✧ ꒰ა≽^•⩊•^≼໒꒱ ✧ ₍˄·͈༝·͈˄₎ ✧ /ᐠ. ⩊ .ᐟ\\ノ ✧ 𓏲ּ ֶָ ࣪ /ᐠ .ᆺ. ᐟ\\ノ ✧';
return ( return (
<> <>
<div className="main-frame"> <div className='main-frame'>
<a href="/niko" className="decorative-sparkle" title="✧" style={{ left: '10px' }}></a> <a
<a href="/fear" className="decorative-sparkle" title="✧" style={{ right: '10px' }}></a> href='/niko'
className='decorative-sparkle'
title='✧'
style={{ left: '10px' }}
>
</a>
<a
href='/fear'
className='decorative-sparkle'
title='✧'
style={{ right: '10px' }}
>
</a>
<header> <header>
<h1>neru</h1> <h1>neru</h1>
<p className="motto">˚ 𓂋 ˚</p> <p className='motto'>˚ 𓂋 ˚</p>
</header> </header>
<nav className="social-links"> <nav className='social-links'>
<a href={TWITTER_LINK} target="_blank" rel="noopener noreferrer">twitter</a> <a href={TWITTER_LINK} target='_blank' rel='noopener noreferrer'>
<button onClick={toggleModal}>discord</button> twitter
<a href={STEAM_LINK} target="_blank" rel="noopener noreferrer">steam</a> </a>{' '}
<button onClick={toggleModal}>discord</button>
<a href={STEAM_LINK} target='_blank' rel='noopener noreferrer'>
steam
</a>
</nav> </nav>
<section className="content-box"> <section className='content-box'>
<h2 className="title"> discord </h2> <h2 className='title'> discord </h2>
<DiscordStatus userId={DISCORD_ID} /> <DiscordStatus userId={DISCORD_ID} />
</section> </section>
<section className="content-box"> <section className='content-box'>
<h2 className="title"> projects im currently working on </h2> <h2 className='title'> projects im currently working on </h2>
<ul className="directory"> <ul className='directory'>
<li><a href="https://git.neru.rip/neru/seallib" target="_blank" rel="noopener noreferrer">seallib</a></li> <li>
<li><a href="https://git.neru.rip/neru/tinymitm" target="_blank" rel="noopener noreferrer">tinymitm</a></li> <a
<li><a href="https://git.neru.rip/neru/luma" target="_blank" rel="noopener noreferrer">luma</a></li> href='https://git.neru.rip/neru/seallib'
target='_blank'
rel='noopener noreferrer'
>
seallib
</a>
</li>
<li>
<a
href='https://git.neru.rip/neru/tinymitm'
target='_blank'
rel='noopener noreferrer'
>
tinymitm
</a>
</li>
<li>
<a
href='https://git.neru.rip/neru/luma'
target='_blank'
rel='noopener noreferrer'
>
luma
</a>
</li>
</ul> </ul>
</section> </section>
<section className="content-box"> <section className='content-box'>
<h2 className="title"> sites </h2> <h2 className='title'> sites </h2>
<ul className="directory"> <ul className='directory'>
<li><a href="https://git.neru.rip" target="_blank" rel="noopener noreferrer">gitea</a></li> <li>
<li><a href="https://zl.neru.rip" target="_blank" rel="noopener noreferrer">zipline</a></li> <a
<li><a href="https://files.neru.rip" target="_blank" rel="noopener noreferrer">files</a></li> href='https://git.neru.rip'
target='_blank'
rel='noopener noreferrer'
>
gitea
</a>
</li>
<li>
<a
href='https://zl.neru.rip'
target='_blank'
rel='noopener noreferrer'
>
zipline
</a>
</li>
<li>
<a
href='https://files.neru.rip'
target='_blank'
rel='noopener noreferrer'
>
files
</a>
</li>
</ul> </ul>
</section> </section>
<section className="content-box"> <section className='content-box'>
<h2 className="title"> dumb stuff </h2> <h2 className='title'> dumb stuff </h2>
<ul className="directory"> <ul className='directory'>
<li><a href="discord://-/apps">break discord</a></li> <li>
<li><a href="discord://-/channels/@me/">fix discord</a></li> <a href='discord://-/apps'>break discord</a>
</li>
<li>
<a href='discord://-/channels/@me/'>fix discord</a>
</li>
</ul> </ul>
</section> </section>
<footer> <footer>
<div className="marquee"> <div className='marquee'>
<div className="marquee-track"> <div className='marquee-track'>
<span>{marqueeText}</span> <span>{marqueeText}</span>
<span>{marqueeText}</span> <span>{marqueeText}</span>
</div> </div>
@@ -83,14 +153,20 @@ function Content() {
</footer> </footer>
{isOpen && ( {isOpen && (
<div className="modal-overlay" onClick={toggleModal}> <div className='modal-overlay' onClick={toggleModal}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}> <div className='modal-content' onClick={(e) => e.stopPropagation()}>
<div className="modal-header"> discord info </div> <div className='modal-header'> discord info </div>
<div className="modal-body"> <div className='modal-body'>
<p><strong>User:</strong> {DISCORD_USER}</p> <p>
<p><strong>ID:</strong> {DISCORD_ID}</p> <strong>User:</strong> {DISCORD_USER}
</p>
<p>
<strong>ID:</strong> {DISCORD_ID}
</p>
</div> </div>
<button className="modal-close-btn" onClick={toggleModal}>[ close ]</button> <button className='modal-close-btn' onClick={toggleModal}>
[ close ]
</button>
</div> </div>
</div> </div>
)} )}
@@ -100,7 +176,5 @@ function Content() {
} }
export default function Home() { export default function Home() {
return ( return <Content />;
<Content />
)
} }