Compare commits

...

10 Commits

Author SHA1 Message Date
Vikeo
63aace2b07 fix typo and adjust touch roulette timings 2024-04-07 23:58:39 +02:00
Vikeo
ba9ca354fc fix crown being visible after reset 2024-04-01 18:48:06 +02:00
Viktor Rådberg
e79c728e6a Save game (#35)
* save

* hide scrollbar on desktop

* start menu styling

* bump
2024-04-01 00:28:59 +02:00
Viktor Rådberg
97f9bb75e5 bump 2024-03-31 19:58:00 +02:00
Viktor Rådberg
341cb3cde0 revert new api key 2024-03-31 19:57:16 +02:00
Viktor Rådberg
ce9c9ca997 default api key 2024-03-31 19:31:16 +02:00
Viktor Rådberg
ad485f059d debug release 2024-03-31 19:25:44 +02:00
Viktor Rådberg
92f954130f bump 2024-03-31 19:13:27 +02:00
Viktor Rådberg
112023bdd5 log if it works or not 2024-03-31 19:08:17 +02:00
Viktor Rådberg
4e6dc56d99 add api key back 2024-03-31 19:02:59 +02:00
12 changed files with 203 additions and 99 deletions

View File

@@ -1,12 +1,12 @@
index.html,1711189442688,fa2549e32940c356ac5cee88c8db61076ad62fb4e599858c8e45cfc68cd901c4
manifest.json,1711189442512,7ff5111aa04a42adff3b38924ec467b6f345ed0309dba1486dc9b783b60c2a9d
registerSW.js,1711189442688,5b6445b5215737c53ef0d379c151d57de165a056de2d1c5812ed614f158ebcbd
sw.js,1711189443521,9c09d33ea573bb818864bfad526fa911839637171773eca8e31905458679846d
robots.txt,1711189442512,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2
manifest.webmanifest,1711189442688,f2bf253209f6e292a6b0dbfa06fb4ac188eb5f2dba568c3ad5511b9ed52c1f51
workbox-3e911b1d.js,1711189443521,d5dbc868a5c07af633d29de7ba3ffe37542aaaabdf33713b4298df31f92f11ff
assets/index-WLCHZTqE.css,1711189442688,877e5ea9bfd3a1ca0e6449e8213da8a3c7717e530370f12669bb5c70dd21e700
favicon.ico,1711189442511,8cefe5adbf00d337d8633fb744b9f2c4914f769b319be4bb7e184b7a4aa17160
logo192.png,1711189442511,3d1e2e6f064d4fd325828f21bb6493ff0dbf2390b0e7d2aba9f2b6def4829799
logo512.png,1711189442511,892a4da1cc5434929a83a71fcbcb0d0d80aa82f44e3c21e9b20ffe9267197133
assets/index-OHs0lOr7.js,1711189442688,aa0dca732cd5b6f621ecb7c6dbcbfdbccde78941cfad954f6626d4ff83040c7f
index.html,1711905710499,4b604b23faec8d63a58e07b96d724a1aea56a7c86d57c9af791832ce87a552e7
manifest.webmanifest,1711905710499,f2bf253209f6e292a6b0dbfa06fb4ac188eb5f2dba568c3ad5511b9ed52c1f51
manifest.json,1711905710294,7ff5111aa04a42adff3b38924ec467b6f345ed0309dba1486dc9b783b60c2a9d
sw.js,1711905711506,1ef2f4f40ec8dca15cc42d547462ade1e84314ea9722276f5994ccee7bbdf80f
robots.txt,1711905710294,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2
registerSW.js,1711905710499,5b6445b5215737c53ef0d379c151d57de165a056de2d1c5812ed614f158ebcbd
workbox-3e911b1d.js,1711905711507,d5dbc868a5c07af633d29de7ba3ffe37542aaaabdf33713b4298df31f92f11ff
assets/index-7m_Zw4yH.css,1711905710499,37997d06b32b3d0c724c054913e3c0583b86f786358121cb1615e6646ff46b56
favicon.ico,1711905710294,8cefe5adbf00d337d8633fb744b9f2c4914f769b319be4bb7e184b7a4aa17160
logo192.png,1711905710294,3d1e2e6f064d4fd325828f21bb6493ff0dbf2390b0e7d2aba9f2b6def4829799
logo512.png,1711905710294,892a4da1cc5434929a83a71fcbcb0d0d80aa82f44e3c21e9b20ffe9267197133
assets/index-CLJVONOc.js,1711905710499,22f3835412f82bb3f8a62e74a7f4602a9c43b447224790365dbcc6cbffb4e665

View File

@@ -1,7 +1,7 @@
{
"name": "life-trinket",
"private": true,
"version": "0.9.41",
"version": "0.9.7",
"type": "commonjs",
"engines": {
"node": ">=18",

View File

@@ -6,7 +6,7 @@ import { Cross } from '../../Icons/generated';
import { useEffect } from 'react';
import { useAnalytics } from '../../Hooks/useAnalytics';
export const ModalWrapper = twc.div`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 h-[85vh] bg-background-default p-4 overflow-scroll rounded-2xl border-none text-text-primary w-[95vw] max-w-[548px]`;
export const ModalWrapper = twc.div`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-[47.5%] h-[95%] bg-background-default p-4 overflow-scroll rounded-2xl border-none text-text-primary w-[95vw] max-w-[548px]`;
type InfoModalProps = {
isOpen: boolean;

View File

@@ -2,6 +2,7 @@ import { Checkbox } from '@mui/material';
import { useRef } from 'react';
import { twc } from 'react-twc';
import { theme } from '../../Data/theme';
import { useAnalytics } from '../../Hooks/useAnalytics';
import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { usePlayers } from '../../Hooks/usePlayers';
import { useSafeRotate } from '../../Hooks/useSafeRotate';
@@ -17,8 +18,8 @@ import {
ResetGame,
} from '../../Icons/generated';
import { Player, Rotation } from '../../Types/Player';
import { PreStartMode } from '../../Types/Settings';
import { RotationDivProps } from '../Buttons/CommanderDamage';
import { useAnalytics } from '../../Hooks/useAnalytics';
const PlayerMenuWrapper = twc.div`
flex
@@ -110,11 +111,14 @@ const PlayerMenu = ({
settings,
setPlaying,
setRandomizingPlayer,
saveCurrentGame,
initialGameSettings,
setPreStartCompleted,
} = useGlobalSettings();
const analytics = useAnalytics();
const { updatePlayer, resetCurrentGame } = usePlayers();
const { updatePlayer, resetCurrentGame, players } = usePlayers();
const handleColorChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const updatedPlayer = { ...player, color: event.target.value };
@@ -131,12 +135,19 @@ const PlayerMenu = ({
const handleResetGame = () => {
resetCurrentGame();
setShowPlayerMenu(false);
setPlaying(false);
setRandomizingPlayer(true);
if (settings.preStartMode === PreStartMode.RandomKing) {
setRandomizingPlayer(true);
setPreStartCompleted(false);
}
analytics.trackEvent('reset_game');
};
const handleGoToStart = () => {
saveCurrentGame({ players, initialGameSettings });
goToStart();
setRandomizingPlayer(true);
};
@@ -443,8 +454,14 @@ const PlayerMenu = ({
className="text-center text-text-primary"
style={{ fontSize: extraCountersSize }}
>
End Game?
Go to start?
</h1>
<div
style={{ fontSize: iconSize }}
className="text-center text-text-primary"
>
(Game will be saved)
</div>
<div className="flex justify-evenly gap-2">
<button
className="bg-primary-main border border-primary-dark text-text-primary rounded-lg flex-grow"

View File

@@ -14,6 +14,10 @@ const getOrientation = () => {
: 'landscape';
};
const ANIMATION_INTRO_LENGTH = 500;
const BEFORE_INTRO_TIMER_LENGTH = 100;
export const FingerGame = () => {
const { players } = usePlayers();
@@ -34,7 +38,7 @@ export const FingerGame = () => {
aboutToStartTimerRef.current = setTimeout(() => {
setSelectedTouchPoint(undefined);
setPlaying(true);
}, 500);
}, ANIMATION_INTRO_LENGTH);
setTimerStarted(true);
return;
@@ -46,7 +50,7 @@ export const FingerGame = () => {
const randomIndex = Math.floor(Math.random() * touchPoints.length);
const randomTouchPoint = touchPoints[randomIndex];
setSelectedTouchPoint(randomTouchPoint);
}, 250);
}, BEFORE_INTRO_TIMER_LENGTH);
return;
}
@@ -99,7 +103,7 @@ export const FingerGame = () => {
aboutToStartTimerRef.current = setTimeout(() => {
setSelectedTouchPoint(undefined);
setPlaying(true);
}, 500);
}, ANIMATION_INTRO_LENGTH);
setTimerStarted(true);
return;
}
@@ -180,7 +184,7 @@ export const FingerGame = () => {
key={`touch-point-${index}`}
data-is-selected={selectedTouchPoint?.id === point.id}
data-unloading={timerStarted}
className="absolute rounded-full translate-x-[-50%] translate-y-[-50%] transition-all duration-1000
className="absolute rounded-full translate-x-[-50%] translate-y-[-50%] transition-all duration-500
h-[75px] w-[75px]
data-[unloading=false]:data-[is-selected=true]:h-[250px] data-[unloading=false]:data-[is-selected=true]:w-[250px]
data-[unloading=true]:h-[0px] data-[unloading=true]:w-[0px] data-[unloading=true]:duration-[400ms]

View File

@@ -8,8 +8,7 @@ const questions = [
'Who has the most piercings?',
'Who has the most expensive shoes?',
'Who has the most most amount of teeth?',
'Who has the most least amount of teeth?',
'Who has the most least amount of teeth?',
'Who has the least amount of teeth?',
'Who lives closest to the equator?',
'Who is the tallest person in the group?',
'Who is the shortest person in the group?',

View File

@@ -95,6 +95,7 @@ export const Play = () => {
}, []);
if (
players.length > 1 &&
!preStartCompleted &&
settings.preStartMode !== PreStartMode.None &&
!playing &&

View File

@@ -19,9 +19,9 @@ import { SettingsModal } from '../../Misc/SettingsModal';
import { SupportMe } from '../../Misc/SupportMe';
import { LayoutOptions } from './LayoutOptions';
const MainWrapper = twc.div`w-[100dvw] h-fit pb-14 overflow-hidden items-center flex flex-col`;
const MainWrapper = twc.div`w-[100dvw] h-fit pb-24 overflow-hidden items-center flex flex-col min-[349px]:pb-10`;
const StartButtonFooter = twc.div`w-full max-w-[548px] fixed bottom-4 z-1 items-center flex flex-col px-4 z-10`;
const StartButtonFooter = twc.div`w-full max-w-[548px] fixed bottom-4 z-1 items-center flex flex-row flex-wrap px-4 z-10 gap-4`;
const SliderWrapper = twc.div`mx-8`;
@@ -92,6 +92,9 @@ const Start = () => {
isPWA,
setRandomizingPlayer,
version,
setPlaying,
savedGame,
saveCurrentGame,
} = useGlobalSettings();
const [openInfoModal, setOpenInfoModal] = useState(false);
@@ -124,7 +127,7 @@ const Start = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playerOptions.numberOfPlayers]);
const doStartGame = () => {
const doStartNewGame = () => {
if (!initialGameSettings) {
return;
}
@@ -149,10 +152,40 @@ const Start = () => {
setInitialGameSettings(initialGameSettings);
setPlayers(createInitialPlayers(initialGameSettings));
setShowPlay(true);
setRandomizingPlayer(settings.preStartMode === PreStartMode.RandomKing);
localStorage.setItem('playing', 'false');
localStorage.setItem('showPlay', 'true');
setShowPlay(true);
setPlaying(false);
};
const doResumeGame = () => {
if (!savedGame) {
return;
}
analytics.trackEvent('game_resumed', {
...initialGameSettings,
...settings,
isPWA,
});
try {
if (settings.goFullscreenOnStart) {
fullscreen.enableFullscreen();
}
} catch (error) {
console.error(error);
}
if (settings.keepAwake && !wakeLock.active) {
wakeLock.request();
}
setInitialGameSettings(savedGame.initialGameSettings);
setPlayers(savedGame.players);
saveCurrentGame(null);
setRandomizingPlayer(false);
setShowPlay(true);
setPlaying(true);
};
const valueText = (value: number) => {
@@ -196,48 +229,6 @@ const Start = () => {
<div className="overflow-hidden items-center flex flex-col max-w-[548px] w-full mb-8 px-4">
<FormControl focused={false} style={{ width: '100%' }}>
<FormLabel>Number of Players</FormLabel>
<SliderWrapper>
<Slider
title="Number of Players"
max={6}
min={1}
aria-label="Custom marks"
value={playerOptions?.numberOfPlayers ?? 4}
getAriaValueText={valueText}
step={null}
marks={playerMarks}
onChange={(_e, value) => {
setPlayerOptions({
...playerOptions,
numberOfPlayers: value as number,
orientation: Orientation.Landscape,
});
}}
/>
</SliderWrapper>
<FormLabel className="mt-[0.7rem]">Starting Health</FormLabel>
<SliderWrapper>
<Slider
title="Starting Health"
max={60}
min={20}
aria-label="Custom marks"
value={playerOptions?.startingLifeTotal ?? 40}
getAriaValueText={valueText}
step={10}
marks={healthMarks}
onChange={(_e, value) =>
setPlayerOptions({
...playerOptions,
startingLifeTotal: value as number,
orientation: Orientation.Landscape,
})
}
/>
</SliderWrapper>
<ToggleButtonsWrapper className="mt-4">
<ToggleContainer>
<FormLabel>Commander</FormLabel>
@@ -295,6 +286,47 @@ const Start = () => {
</div>
</div>
</ToggleButtonsWrapper>
<FormLabel>Number of Players</FormLabel>
<SliderWrapper>
<Slider
title="Number of Players"
max={6}
min={1}
aria-label="Custom marks"
value={playerOptions?.numberOfPlayers ?? 4}
getAriaValueText={valueText}
step={null}
marks={playerMarks}
onChange={(_e, value) => {
setPlayerOptions({
...playerOptions,
numberOfPlayers: value as number,
orientation: Orientation.Landscape,
});
}}
/>
</SliderWrapper>
<FormLabel className="mt-[0.7rem]">Starting Health</FormLabel>
<SliderWrapper>
<Slider
title="Starting Health"
max={60}
min={20}
aria-label="Custom marks"
value={playerOptions?.startingLifeTotal ?? 40}
getAriaValueText={valueText}
step={10}
marks={healthMarks}
onChange={(_e, value) =>
setPlayerOptions({
...playerOptions,
startingLifeTotal: value as number,
orientation: Orientation.Landscape,
})
}
/>
</SliderWrapper>
<FormLabel>Layout</FormLabel>
<LayoutOptions
@@ -318,14 +350,25 @@ const Start = () => {
</div>
<StartButtonFooter>
<Button
size="large"
variant="contained"
onClick={doStartGame}
fullWidth
<button
className="flex flex-grow basis-0 justify-center self-center items-center bg-primary-main px-3 py-2 rounded-md text-text-primary min-w-[150px]"
onClick={doStartNewGame}
>
START GAME
</Button>
NEW GAME
</button>
{savedGame && (
<button
className="flex flex-grow basis-0 justify-center self-center items-center bg-primary-dark px-3 py-2 rounded-md text-text-primary min-w-[150px]"
onClick={doResumeGame}
>
RESUME&nbsp;
<span className="text-xs">
({savedGame.players.length}&nbsp;
{savedGame.players.length > 1 ? 'players' : 'player'})
</span>
</button>
)}
</StartButtonFooter>
</MainWrapper>
);

View File

@@ -1,5 +1,6 @@
import { createContext } from 'react';
import { InitialGameSettings, Settings } from '../Types/Settings';
import { Player } from '../Types/Player';
type Version = {
installedVersion: string;
@@ -8,6 +9,11 @@ type Version = {
remoteVersion?: string;
};
export type SavedGame = {
initialGameSettings: InitialGameSettings;
players: Player[];
} | null;
export type GlobalSettingsContextType = {
fullscreen: {
isFullscreen: boolean;
@@ -25,7 +31,7 @@ export type GlobalSettingsContextType = {
goToStart: () => void;
showPlay: boolean;
setShowPlay: (showPlay: boolean) => void;
initialGameSettings: InitialGameSettings | null;
initialGameSettings: InitialGameSettings;
setInitialGameSettings: (initialGameSettings: InitialGameSettings) => void;
settings: Settings;
setSettings: (settings: Settings) => void;
@@ -36,8 +42,9 @@ export type GlobalSettingsContextType = {
isPWA: boolean;
preStartCompleted: boolean;
setPreStartCompleted: (completed: boolean) => void;
version: Version;
savedGame: SavedGame;
saveCurrentGame: (currentGame: SavedGame) => void;
};
export const GlobalSettingsContext =

View File

@@ -2,7 +2,7 @@ import { initializeApp } from 'firebase/app';
import { getAnalytics, logEvent } from 'firebase/analytics';
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
apiKey: 'AIzaSyCZ1AHMb5zmWS4VoRnC-OBxTswUfrJ0mlY',
authDomain: 'life-trinket.firebaseapp.com',
projectId: 'life-trinket',
storageBucket: 'life-trinket.appspot.com',

View File

@@ -3,6 +3,7 @@ import { useWakeLock } from 'react-screen-wake-lock';
import {
GlobalSettingsContext,
GlobalSettingsContextType,
SavedGame,
} from '../Contexts/GlobalSettingsContext';
import { useAnalytics } from '../Hooks/useAnalytics';
import {
@@ -21,12 +22,21 @@ export const GlobalSettingsProvider = ({
}) => {
const analytics = useAnalytics();
const savedShowPlay = localStorage.getItem('showPlay');
const savedGameSettings = localStorage.getItem('initialGameSettings');
const savedSettings = localStorage.getItem('settings');
const savedPlaying = localStorage.getItem('playing');
const savedPreStartComplete = localStorage.getItem('preStartComplete');
const localSavedGame = localStorage.getItem('savedGame');
const [savedGame, setCurrentGame] = useState<SavedGame>(
localSavedGame ? JSON.parse(localSavedGame) : null
);
const setCurrentGameAndLocalStorage = (savedGame: SavedGame) => {
if (!savedGame) {
setCurrentGame(savedGame);
localStorage.removeItem('savedGame');
return;
}
setCurrentGame(savedGame);
localStorage.setItem('savedGame', JSON.stringify(savedGame));
};
const savedPlaying = localStorage.getItem('playing');
const [playing, setPlaying] = useState<boolean>(
savedPlaying ? savedPlaying === 'true' : false
);
@@ -35,23 +45,42 @@ export const GlobalSettingsProvider = ({
localStorage.setItem('playing', String(playing));
};
const savedPreStartComplete = localStorage.getItem('preStartComplete');
const [preStartCompleted, setPreStartCompleted] = useState<boolean>(
savedPreStartComplete ? savedPreStartComplete === 'true' : false
);
const savedShowPlay = localStorage.getItem('showPlay');
const [showPlay, setShowPlay] = useState<boolean>(
savedShowPlay ? savedShowPlay === 'true' : false
);
const setShowPlayAndLocalStorage = (showPlay: boolean) => {
setShowPlay(showPlay);
localStorage.setItem('showPlay', String(showPlay));
};
const savedSettings = localStorage.getItem('settings');
const [randomizingPlayer, setRandomizingPlayer] = useState<boolean>(
savedSettings
? Boolean(JSON.parse(savedSettings).preStartMode === 'random-king')
: true
);
const [settings, setSettings] = useState<Settings>(
savedSettings ? JSON.parse(savedSettings) : defaultSettings
);
const setSettingsAndLocalStorage = (settings: Settings) => {
setSettings(settings);
localStorage.setItem('settings', JSON.stringify(settings));
};
const savedGameSettings = localStorage.getItem('initialGameSettings');
const [initialGameSettings, setInitialGameSettings] =
useState<InitialGameSettings | null>(
savedGameSettings ? JSON.parse(savedGameSettings) : null
useState<InitialGameSettings>(
savedGameSettings
? JSON.parse(savedGameSettings)
: defaultInitialGameSettings
);
const setInitialGameSettingsAndLocalStorage = (
@@ -64,15 +93,6 @@ export const GlobalSettingsProvider = ({
);
};
const [settings, setSettings] = useState<Settings>(
savedSettings ? JSON.parse(savedSettings) : defaultSettings
);
const setSettingsAndLocalStorage = (settings: Settings) => {
setSettings(settings);
localStorage.setItem('settings', JSON.stringify(settings));
};
const removeLocalStorage = async () => {
localStorage.removeItem('initialGameSettings');
localStorage.removeItem('players');
@@ -252,7 +272,7 @@ export const GlobalSettingsProvider = ({
},
goToStart,
showPlay,
setShowPlay,
setShowPlay: setShowPlayAndLocalStorage,
playing,
setPlaying: setPlayingAndLocalStorage,
initialGameSettings,
@@ -264,6 +284,8 @@ export const GlobalSettingsProvider = ({
isPWA: window?.matchMedia('(display-mode: standalone)').matches,
preStartCompleted,
setPreStartCompleted: setPreStartCompletedAndLocalStorage,
savedGame,
saveCurrentGame: setCurrentGameAndLocalStorage,
version: {
installedVersion: import.meta.env.VITE_APP_VERSION,
@@ -285,6 +307,7 @@ export const GlobalSettingsProvider = ({
settings,
randomizingPlayer,
preStartCompleted,
savedGame,
remoteVersion,
isLatestVersion,
analytics,

View File

@@ -31,6 +31,16 @@ code {
monospace;
}
// hide scrollbar globally
::-webkit-scrollbar {
display: none;
}
* {
scrollbar-width: none;
-ms-overflow-style: none;
}
@layer utilities {
.pointer-events-all {
pointer-events: all;