Compare commits

...

29 Commits
0.5.3 ... 0.6.1

Author SHA1 Message Date
Viktor Rådberg
89e1eaff4e bump 2024-01-27 16:25:40 +01:00
Viktor Rådberg
0f4e896342 Merge pull request #31 from Vikeo/swipable-settings
Swipable settings
2024-01-27 16:23:54 +01:00
Viktor Rådberg
dc1d5fe01d tsc 2024-01-27 16:20:09 +01:00
Viktor Rådberg
41e73d2c0c swipe 2024-01-27 11:05:54 +01:00
Viktor Rådberg
724dcf086c is side 2024-01-27 09:32:00 +01:00
Viktor Rådberg
51f9c4d20e initial test 2024-01-26 21:24:40 +01:00
Viktor Rådberg
354c0dbbb2 bump 2024-01-20 11:11:03 +01:00
Viktor Rådberg
3770d13beb fix some styling 2024-01-20 10:56:53 +01:00
Viktor Rådberg
13733242a2 bump 2024-01-14 14:39:20 +01:00
Viktor Rådberg
81f3891b20 add better pwa support 2024-01-14 14:38:56 +01:00
Viktor Rådberg
e153de9093 Release 0.5.51 2024-01-14 13:42:11 +01:00
Viktor Rådberg
07775f85d2 fix start menu style 2024-01-14 13:41:51 +01:00
Viktor Rådberg
10039175a1 bump 2024-01-14 13:14:53 +01:00
Viktor Rådberg
bcf2a0a840 new colors 2024-01-14 13:14:33 +01:00
Viktor Rådberg
d25da5d97b fix styling 2024-01-14 12:31:57 +01:00
Viktor Rådberg
f5a80e573e cache 2024-01-14 10:41:14 +01:00
Viktor Rådberg
1f36264e39 update package json 2024-01-14 10:40:48 +01:00
Viktor Rådberg
d615cfd3ba reset game styling 2024-01-14 10:38:28 +01:00
Viktor Rådberg
4453b12ce6 bump 2024-01-13 20:32:10 +01:00
Viktor Rådberg
d601a820f8 remove log 2024-01-13 20:32:00 +01:00
Viktor Rådberg
0455f43794 test 2024-01-13 20:26:07 +01:00
Viktor Rådberg
f94103fe51 fix 2024-01-13 20:20:50 +01:00
Viktor Rådberg
c36668b933 fix 2024-01-13 20:20:20 +01:00
Viktor Rådberg
f9d0346300 bump 2024-01-13 20:18:50 +01:00
Viktor Rådberg
2f3ee74c74 test 2024-01-13 20:15:43 +01:00
Viktor Rådberg
f8f0788b97 bump 2024-01-13 19:53:02 +01:00
Viktor Rådberg
bfe25eacb7 fix lint 2024-01-13 19:44:27 +01:00
Viktor Rådberg
7b0965c0dd test release 2024-01-13 19:40:36 +01:00
Viktor Rådberg
e55ea6a83a final test 2024-01-13 19:32:16 +01:00
22 changed files with 2616 additions and 592 deletions

View File

@@ -1,8 +1,8 @@
robots.txt,1693082171694,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2 index.html,1705225256081,6ef0d7e2de82bf64addbb9294fb28845fd06daaa544b010a47422c12ae3ad97f
manifest.json,1693082171694,91ce94afb71f33a477f5d8d48c3f98bd7de422279c74f17b6500eec72003ac1a robots.txt,1705225255906,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2
assets/index-5265c558.css,1693082171837,08c4451946bbdf520fe337edb365417a8bbf91914c018b83866723ef52d57b43 manifest.json,1705225255906,91ce94afb71f33a477f5d8d48c3f98bd7de422279c74f17b6500eec72003ac1a
index.html,1693082171837,09e1919fbaaa3a0bf08f43eb46c29136d62a7747b41f8b5d0f4a7ed23337c344 assets/index-08359bdb.css,1705225256081,d2766260d28230d960d75362810713efaddf40687205e697432b52869f162af7
logo192.png,1693082171693,4309255bccbdbb341b5ab88708677e3d43b9e171d2666528ff932295a8257e4e logo192.png,1705225255905,3b0fcf91fe2128f493de0bce2f6e2d35520a4260a04e05b8d855181359b3d3fe
favicon.ico,1693082171692,48d8c1b9714dbc9bcb012d9c9f04112d229f20e6c889bda588ac159f973e6a8d favicon.ico,1705225255905,75661e6187b524767554b4f28ec09a64bc72b0bb102a0b453aaead88519d9ed3
logo512.png,1693082171694,92c7c05dc98170596d04f48e5e60eaae9535f409bcaeff129fd98fef8aba9f4e logo512.png,1705225255906,cf49739c9e6890bbfcd4157f299dde425df60759b7320ae9188d7ab9dc51e8ca
assets/index-5023e89e.js,1693082171838,8a6177168e95e1ca90e5ad8774252a8a02a9a78765bd329b7deae729c01aedf3 assets/index-20658f4b.js,1705225256081,742f2c10740beea3a23f269aa6266b3c288d1fd9c7e20b6829034e8a898bf1e1

View File

@@ -4,32 +4,32 @@ name: Deploy to Firebase Hosting
tags: tags:
- '*' - '*'
jobs: jobs:
# build_and_deploy: build_and_deploy:
# runs-on: ubuntu-latest runs-on: ubuntu-latest
# env: env:
# REPO_READ_ACCESS_TOKEN: ${{ secrets.REPO_READ_ACCESS_TOKEN }} REPO_READ_ACCESS_TOKEN: ${{ secrets.REPO_READ_ACCESS_TOKEN }}
# steps: steps:
# - name: Checkout repository - name: Checkout repository
# uses: actions/checkout@v3 uses: actions/checkout@v3
# - name: Setup bun - name: Setup bun
# uses: oven-sh/setup-bun@v1 uses: oven-sh/setup-bun@v1
# - name: Build, lint, and deploy - name: Build, lint, and deploy
# run: | run: |
# bun install bun install
# bun run build bun run build
# bun run lint bun run lint
# - name: Deploy to Firebase Hosting - name: Deploy to Firebase Hosting
# uses: FirebaseExtended/action-hosting-deploy@v0 uses: FirebaseExtended/action-hosting-deploy@v0
# with: with:
# repoToken: '${{ secrets.GITHUB_TOKEN }}' repoToken: '${{ secrets.GITHUB_TOKEN }}'
# firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_LIFE_TRINKET }}' firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_LIFE_TRINKET }}'
# channelId: live channelId: live
# projectId: life-trinket projectId: life-trinket
release: release:
# needs: build_and_deploy needs: build_and_deploy
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
working-directory: ${{ github.workspace }} working-directory: ${{ github.workspace }}
@@ -52,7 +52,7 @@ jobs:
- name: Create Release - name: Create Release
uses: ncipollo/release-action@v1.13.0 uses: ncipollo/release-action@v1.13.0
with: with:
body: release_note.txt bodyFile: release_note.txt
commit: ${{ github.sha }} commit: ${{ github.sha }}
tag: '${{ steps.version.outputs.prop }}' tag: '${{ steps.version.outputs.prop }}'
token: ${{ secrets.RELEASE_TOKEN }} token: ${{ secrets.RELEASE_TOKEN }}

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{ {
"name": "life-trinket", "name": "life-trinket",
"private": true, "private": true,
"version": "0.5.3", "version": "0.6.1",
"type": "commonjs", "type": "commonjs",
"engines": { "engines": {
"node": ">=18", "node": ">=18",
@@ -13,7 +13,7 @@
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview", "preview": "vite preview",
"generate-icons": "npx @svgr/cli src/Icons/svgs", "generate-icons": "npx @svgr/cli src/Icons/svgs",
"deploy": "bun build && firebase deploy --only hosting" "deploy": "bun run build && firebase deploy --only hosting"
}, },
"dependencies": { "dependencies": {
"@mui/material": "^5.13.6", "@mui/material": "^5.13.6",
@@ -22,6 +22,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-screen-wake-lock": "^3.0.2", "react-screen-wake-lock": "^3.0.2",
"react-swipeable": "^7.0.1",
"react-twc": "^1.3.0", "react-twc": "^1.3.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
@@ -43,8 +44,9 @@
"install": "^0.13.0", "install": "^0.13.0",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"prettier": "2.8.8", "prettier": "2.8.8",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.1",
"typescript": "^5.0.2", "typescript": "^5.3.3",
"vite": "^4.4.5" "vite": "^5.0.12",
"vite-plugin-pwa": "^0.17.4"
} }
} }

View File

@@ -66,10 +66,6 @@ export const CommanderDamage = ({
const [timeoutFinished, setTimeoutFinished] = useState(false); const [timeoutFinished, setTimeoutFinished] = useState(false);
const [hasPressedDown, setHasPressedDown] = useState(false); const [hasPressedDown, setHasPressedDown] = useState(false);
const isSide =
player.settings.rotation === Rotation.Side ||
player.settings.rotation === Rotation.SideFlipped;
const handleCommanderDamageChange = ( const handleCommanderDamageChange = (
index: number, index: number,
increment: number, increment: number,
@@ -132,9 +128,9 @@ export const CommanderDamage = ({
}; };
const opponentIndex = opponent.index; const opponentIndex = opponent.index;
const fontSize = isSide ? '4vmax' : '7vmin'; const fontSize = player.isSide ? '4vmax' : '7vmin';
const fontWeight = 'bold'; const fontWeight = 'bold';
const strokeWidth = isSide ? '0.4vmax' : '0.7vmin'; const strokeWidth = player.isSide ? '0.4vmax' : '0.7vmin';
return ( return (
<CommanderDamageContainer <CommanderDamageContainer

View File

@@ -47,6 +47,7 @@ type ExtraCounterProps = {
type: CounterType; type: CounterType;
setCounterTotal: (updatedCounterTotal: number, type: CounterType) => void; setCounterTotal: (updatedCounterTotal: number, type: CounterType) => void;
rotation: number; rotation: number;
isSide: boolean;
playerIndex: number; playerIndex: number;
}; };
@@ -56,15 +57,13 @@ const ExtraCounter = ({
setCounterTotal, setCounterTotal,
type, type,
rotation, rotation,
isSide,
playerIndex, playerIndex,
}: ExtraCounterProps) => { }: ExtraCounterProps) => {
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined); const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
const [timeoutFinished, setTimeoutFinished] = useState(false); const [timeoutFinished, setTimeoutFinished] = useState(false);
const [hasPressedDown, setHasPressedDown] = useState(false); const [hasPressedDown, setHasPressedDown] = useState(false);
const isSide =
rotation === Rotation.Side || rotation === Rotation.SideFlipped;
const handleCountChange = (increment: number) => { const handleCountChange = (increment: number) => {
if (!counterTotal) { if (!counterTotal) {
setCounterTotal(increment, type); setCounterTotal(increment, type);

View File

@@ -4,13 +4,11 @@ import { Rotation } from '../../Types/Player';
import { RotationDivProps } from './CommanderDamage'; import { RotationDivProps } from './CommanderDamage';
const LoseButton = twc.div<RotationDivProps>((props) => [ const LoseButton = twc.div<RotationDivProps>((props) => [
'absolute flex-grow border-none outline-none cursor-pointer bg-interface-loseButton-background rounded-lg select-none z-[1] webkit-user-select-none', 'absolute flex-grow border-none outline-none cursor-pointer bg-interface-loseButton-background rounded-lg select-none z-[1] webkit-user-select-none py-2 px-4 ',
props.$rotation === Rotation.SideFlipped props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
? `right-auto top-[15%] left-[27%]` ? `left-[19%]`
: props.$rotation === Rotation.Side : 'top-[21%]',
? `right-auto top-[15%] left-[27%]`
: 'right-[15%] top-1/4',
]); ]);
type LoseButtonProps = { type LoseButtonProps = {
@@ -24,6 +22,8 @@ export const LoseGameButton = ({ rotation, onClick }: LoseButtonProps) => {
? rotation ? rotation
: rotation === Rotation.Side : rotation === Rotation.Side
? rotation - 180 ? rotation - 180
: rotation === Rotation.Flipped
? rotation - 180
: rotation; : rotation;
return ( return (
@@ -33,7 +33,7 @@ export const LoseGameButton = ({ rotation, onClick }: LoseButtonProps) => {
aria-label={`Lose Game`} aria-label={`Lose Game`}
style={{ rotate: `${calcRotation}deg` }} style={{ rotate: `${calcRotation}deg` }}
> >
<Skull size="5vmin" color="black" opacity={0.5} /> <Skull size="8vmin" color="black" opacity={0.5} />
</LoseButton> </LoseButton>
); );
}; };

View File

@@ -100,6 +100,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
(counter) => counter.type === 'commanderTax' (counter) => counter.type === 'commanderTax'
)?.value )?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />
@@ -114,6 +115,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
(counter) => counter.type === 'partnerTax' (counter) => counter.type === 'partnerTax'
)?.value )?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />
@@ -127,6 +129,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
player.extraCounters?.find((counter) => counter.type === 'poison') player.extraCounters?.find((counter) => counter.type === 'poison')
?.value ?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />
@@ -140,6 +143,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
player.extraCounters?.find((counter) => counter.type === 'energy') player.extraCounters?.find((counter) => counter.type === 'energy')
?.value ?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />
@@ -154,6 +158,7 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => {
(counter) => counter.type === 'experience' (counter) => counter.type === 'experience'
)?.value )?.value
} }
isSide={player.isSide}
setCounterTotal={handleCounterChange} setCounterTotal={handleCounterChange}
playerIndex={player.index} playerIndex={player.index}
/> />

View File

@@ -8,7 +8,7 @@ import {
import LifeCounterButton from '../Buttons/LifeCounterButton'; import LifeCounterButton from '../Buttons/LifeCounterButton';
import { OutlinedText } from '../Misc/OutlinedText'; import { OutlinedText } from '../Misc/OutlinedText';
const LifeCountainer = twc.div<RotationDivProps>((props) => [ const LifeContainer = twc.div<RotationDivProps>((props) => [
'flex flex-grow relative w-full h-full justify-between items-center', 'flex flex-grow relative w-full h-full justify-between items-center',
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
? 'flex-col-reverse' ? 'flex-col-reverse'
@@ -49,7 +49,6 @@ type HealthProps = {
const Health = ({ const Health = ({
player, player,
rotation,
handleLifeChange, handleLifeChange,
differenceKey, differenceKey,
recentDifference, recentDifference,
@@ -99,12 +98,13 @@ const Health = ({
}, [textContainerRef]); }, [textContainerRef]);
const calculateFontSize = (container: HTMLDivElement) => { const calculateFontSize = (container: HTMLDivElement) => {
const isSide = const widthRatio = player.isSide
rotation === Rotation.SideFlipped || rotation === Rotation.Side; ? container.clientHeight
: container.clientWidth;
const widthRatio = isSide ? container.clientHeight : container.clientWidth; const heightRatio = player.isSide
? container.clientWidth
const heightRatio = isSide ? container.clientWidth : container.clientHeight; : container.clientHeight;
const minRatio = Math.min(widthRatio, heightRatio); const minRatio = Math.min(widthRatio, heightRatio);
@@ -116,7 +116,7 @@ const Health = ({
}; };
return ( return (
<LifeCountainer $rotation={player.settings.rotation}> <LifeContainer $rotation={player.settings.rotation}>
<LifeCounterButton <LifeCounterButton
lifeTotal={player.lifeTotal} lifeTotal={player.lifeTotal}
setLifeTotal={handleLifeChange} setLifeTotal={handleLifeChange}
@@ -154,7 +154,7 @@ const Health = ({
operation="add" operation="add"
increment={1} increment={1}
/> />
</LifeCountainer> </LifeContainer>
); );
}; };

View File

@@ -1,11 +1,11 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useSwipeable } from 'react-swipeable';
import { twc } from 'react-twc'; import { twc } from 'react-twc';
import { useGlobalSettings } from '../../Hooks/useGlobalSettings'; import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { usePlayers } from '../../Hooks/usePlayers'; import { usePlayers } from '../../Hooks/usePlayers';
import { Player, Rotation } from '../../Types/Player'; import { Player, Rotation } from '../../Types/Player';
import { RotationDivProps } from '../Buttons/CommanderDamage'; import { RotationDivProps } from '../Buttons/CommanderDamage';
import { LoseGameButton } from '../Buttons/LoseButton'; import { LoseGameButton } from '../Buttons/LoseButton';
import SettingsButton from '../Buttons/SettingsButton';
import CommanderDamageBar from '../Counters/CommanderDamageBar'; import CommanderDamageBar from '../Counters/CommanderDamageBar';
import ExtraCountersBar from '../Counters/ExtraCountersBar'; import ExtraCountersBar from '../Counters/ExtraCountersBar';
import PlayerMenu from '../Player/PlayerMenu'; import PlayerMenu from '../Player/PlayerMenu';
@@ -24,13 +24,13 @@ const LifeCounterWrapper = twc.div<RotationDivProps>((props) => [
const StartingPlayerNoticeWrapper = twc.div`z-[1] flex absolute w-full h-full justify-center items-center pointer-events-none select-none webkit-user-select-none bg-primary-main`; const StartingPlayerNoticeWrapper = twc.div`z-[1] flex absolute w-full h-full justify-center items-center pointer-events-none select-none webkit-user-select-none bg-primary-main`;
const PlayerLostWrapper = twc.div<RotationDivProps>((props) => [ const PlayerLostWrapper = twc.div<RotationDivProps>((props) => [
'z-[1] flex absolute w-full h-full justify-center items-center pointer-events-none select-none webkit-user-select-none bg-lifeCounter-lostWrapper', 'z-[1] flex absolute w-full h-full justify-center items-center pointer-events-none select-none webkit-user-select-none bg-lifeCounter-lostWrapper opacity-75',
props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side props.$rotation === Rotation.SideFlipped || props.$rotation === Rotation.Side
? `rotate-[${props.$rotation - 90}deg]` ? `rotate-[${props.$rotation - 90}deg]`
: '', : '',
]); ]);
const DynamicText = twc.div`text-[8vmin]`; const DynamicText = twc.div`text-[8vmin] whitespace-nowrap`;
const hasCommanderDamageReached21 = (player: Player) => { const hasCommanderDamageReached21 = (player: Player) => {
const commanderDamageTotals = player.commanderDamage.map( const commanderDamageTotals = player.commanderDamage.map(
@@ -71,14 +71,51 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
const [showPlayerMenu, setShowPlayerMenu] = useState(false); const [showPlayerMenu, setShowPlayerMenu] = useState(false);
const [recentDifference, setRecentDifference] = useState(0); const [recentDifference, setRecentDifference] = useState(0);
const [differenceKey, setDifferenceKey] = useState(Date.now()); const [differenceKey, setDifferenceKey] = useState(Date.now());
const [isLandscape, setIsLandscape] = useState(false);
const calcRot = player.isSide
? player.settings.rotation - 180
: player.settings.rotation;
const rotationAngle = isLandscape ? calcRot : calcRot + 90;
const handlers = useSwipeable({
trackMouse: true,
onSwipedDown: () => {
console.log(`User DOWN Swiped on player ${player.index}`);
setShowPlayerMenu(true);
},
onSwipedUp: () => {
console.log(`User UP Swiped on player ${player.index}`);
setShowPlayerMenu(false);
},
swipeDuration: 500,
onSwiping: (eventData) => console.log(eventData),
rotationAngle,
});
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
setRecentDifference(0); setRecentDifference(0);
}, 3_000); }, 3_000);
return () => clearTimeout(timer); const resizeObserver = new ResizeObserver(() => {
}, [recentDifference]); if (document.body.clientWidth > document.body.clientHeight)
setIsLandscape(true);
else setIsLandscape(false);
return;
});
resizeObserver.observe(document.body);
return () => {
clearTimeout(timer);
// Cleanup: disconnect the ResizeObserver when the component unmounts.
resizeObserver.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [recentDifference, document.body.clientHeight, document.body.clientWidth]);
useEffect(() => { useEffect(() => {
if (player.showStartingPlayer) { if (player.showStartingPlayer) {
@@ -124,6 +161,7 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
<LifeCounterWrapper <LifeCounterWrapper
$rotation={player.settings.rotation} $rotation={player.settings.rotation}
style={{ rotate: `${calcRotation}deg` }} style={{ rotate: `${calcRotation}deg` }}
{...handlers}
> >
{settings.showStartingPlayer && {settings.showStartingPlayer &&
player.isStartingPlayer && player.isStartingPlayer &&
@@ -131,7 +169,11 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
<StartingPlayerNoticeWrapper <StartingPlayerNoticeWrapper
style={{ rotate: `${calcRotation}deg` }} style={{ rotate: `${calcRotation}deg` }}
> >
<DynamicText style={{ rotate: `${calcTextRotation}deg` }}> <DynamicText
style={{
rotate: `${calcTextRotation}deg`,
}}
>
You start! You start!
</DynamicText> </DynamicText>
</StartingPlayerNoticeWrapper> </StartingPlayerNoticeWrapper>
@@ -146,12 +188,6 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
key={player.index} key={player.index}
handleLifeChange={handleLifeChange} handleLifeChange={handleLifeChange}
/> />
<SettingsButton
onClick={() => {
setShowPlayerMenu(!showPlayerMenu);
}}
rotation={player.settings.rotation}
/>
{playerCanLose(player) && ( {playerCanLose(player) && (
<LoseGameButton <LoseGameButton
rotation={player.settings.rotation} rotation={player.settings.rotation}
@@ -166,9 +202,12 @@ const LifeCounter = ({ player, opponents }: LifeCounterProps) => {
handleLifeChange={handleLifeChange} handleLifeChange={handleLifeChange}
/> />
<ExtraCountersBar player={player} /> <ExtraCountersBar player={player} />
{showPlayerMenu && (
<PlayerMenu player={player} setShowPlayerMenu={setShowPlayerMenu} /> <PlayerMenu
)} isShown={showPlayerMenu}
player={player}
setShowPlayerMenu={setShowPlayerMenu}
/>
</LifeCounterWrapper> </LifeCounterWrapper>
</LifeCounterContentWrapper> </LifeCounterContentWrapper>
); );

View File

@@ -1,8 +1,9 @@
import { Modal } from '@mui/material'; import { Modal } from '@mui/material';
import { theme } from '../../Data/theme';
import { twc } from 'react-twc'; import { twc } from 'react-twc';
import { Separator } from './Separator';
import { Paragraph } from './TextComponents';
export const ModalWrapper = twc.div`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[80vw] h-[85vh] bg-background-default p-4 overflow-scroll rounded-2xl border-none text-text-primary`; 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]`;
type InfoModalProps = { type InfoModalProps = {
isOpen: boolean; isOpen: boolean;
@@ -11,29 +12,44 @@ type InfoModalProps = {
export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => { export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => {
return ( return (
<Modal open={isOpen} onClose={closeModal}> <Modal
open={isOpen}
onClose={closeModal}
style={{ display: 'flex', justifyContent: 'center' }}
>
<>
<div className="flex relative w-full max-w-[548px]">
<button
onClick={closeModal}
className="flex absolute top-10 right-0 z-10 w-10 h-10 text-common-white bg-primary-main items-center justify-center rounded-full border-solid border-primary-dark border-2"
>
X
</button>
</div>
<ModalWrapper> <ModalWrapper>
<div> <div>
<h2 style={{ textAlign: 'center' }}>📋 Usage Guide</h2> <h2 className="text-2xl text-center mb-4">📋 Usage Guide</h2>
<p> <Separator height="1px" />
<Paragraph className="my-4">
There are some controls that you might not know about, so here's a There are some controls that you might not know about, so here's a
short list of them. short list of them.
</p> </Paragraph>
<h3 className="text-lg font-bold mb-2">Life counter</h3>
<h3>Life counter</h3> <ul className="list-disc ml-6 mb-4">
<ul>
<li> <li>
<strong>Tap</strong> on a player's + or - button to add or <strong>Tap</strong> on a player's + or - button to add or
subtract <strong>1 life</strong>. subtract <strong>1 life</strong>.
</li> </li>
<li> <li>
<strong>Long press</strong> on a player's + or - button to add or <strong>Long press</strong> on a player's + or - button to add
subtract <strong>10 life</strong>. or subtract <strong>10 life</strong>.
</li> </li>
</ul> </ul>
<h3>Commander damage and other counters</h3> <h3 className="text-lg font-bold mb-2">
<ul> Commander damage and other counters
</h3>
<ul className="list-disc ml-6 mb-4">
<li> <li>
<strong>Tap</strong> on the counter to add{' '} <strong>Tap</strong> on the counter to add{' '}
<strong>1 counter</strong>. <strong>1 counter</strong>.
@@ -44,33 +60,21 @@ export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => {
</li> </li>
</ul> </ul>
<h3>Other</h3> <h3 className="text-lg font-bold mb-2">Other</h3>
<p> <Paragraph className="mb-4">
When a player is <strong>at or below 0 life</strong>, has taken{' '} When a player is <strong>at or below 0 life</strong>, has taken{' '}
<strong>21 or more Commander Damage</strong> or has{' '} <strong>21 or more Commander Damage</strong> or has{' '}
<strong>10 or more poison counters</strong>, a button with a skull <strong>10 or more poison counters</strong>, a button with a skull
will appear on that player's card. will appear on that player's card. Tapping it will dim the
</p> player's card.
<p> </Paragraph>
Tap on the button to mark that player as lost, dimming their player
card.
</p>
</div> </div>
<br /> <div className="text-center mt-4">
<div
style={{
textAlign: 'center',
marginTop: '1rem',
}}
>
Visit my Visit my
<a <a
href="https://github.com/Vikeo/LifeTrinket" href="https://github.com/Vikeo/LifeTrinket"
target="_blank" target="_blank"
style={{ className="text-text-secondary underline"
textDecoration: 'none',
color: theme.palette.primary.light,
}}
> >
{' '} {' '}
GitHub{' '} GitHub{' '}
@@ -78,6 +82,7 @@ export const InfoModal = ({ isOpen, closeModal }: InfoModalProps) => {
for more info about this web app. for more info about this web app.
</div> </div>
</ModalWrapper> </ModalWrapper>
</>
</Modal> </Modal>
); );
}; };

View File

@@ -7,7 +7,7 @@ export const Separator = ({
}) => { }) => {
return ( return (
<div <div
className={`bg-common-black bg-opacity-30 rounded-full mt-2 mb-2`} className={`bg-common-white bg-opacity-30 rounded-full mt-2 mb-2`}
style={{ width, height }} style={{ width, height }}
/> />
); );

View File

@@ -4,7 +4,7 @@ import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { ModalWrapper } from './InfoModal'; import { ModalWrapper } from './InfoModal';
import { Separator } from './Separator'; import { Separator } from './Separator';
import { Paragraph } from './TextComponents'; import { Paragraph } from './TextComponents';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useState } from 'react';
const SettingContainer = twc.div`w-full flex flex-col`; const SettingContainer = twc.div`w-full flex flex-col`;
@@ -22,7 +22,7 @@ type SettingsModalProps = {
export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => { export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
const { settings, setSettings, isPWA } = useGlobalSettings(); const { settings, setSettings, isPWA } = useGlobalSettings();
const [isLatestVersion, setIsLatestVersion] = useState(false); const [isLatestVersion, setIsLatestVersion] = useState(false);
const newVersion = useRef<string | undefined>(undefined); const [newVersion, setNewVersion] = useState<string | undefined>(undefined);
useEffect(() => { useEffect(() => {
if (!isOpen) { if (!isOpen) {
@@ -44,17 +44,19 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
const data = await result.json(); const data = await result.json();
if (!data.name) { if (!data.name) {
setNewVersion(undefined);
setIsLatestVersion(false); setIsLatestVersion(false);
newVersion.current = undefined;
return; return;
} }
setNewVersion(data.name);
/* @ts-expect-error is defined in vite.config.ts*/ /* @ts-expect-error is defined in vite.config.ts*/
if (data.name === APP_VERSION) { if (data.name === APP_VERSION) {
newVersion.current = data.name;
setIsLatestVersion(true); setIsLatestVersion(true);
return; return;
} }
setIsLatestVersion(false); setIsLatestVersion(false);
} catch (error) { } catch (error) {
console.error('error getting latest version string', error); console.error('error getting latest version string', error);
@@ -65,9 +67,19 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
return ( return (
<Modal open={isOpen} onClose={closeModal}> <Modal open={isOpen} onClose={closeModal}>
<>
<div className="flex relative w-full max-w-[548px]">
<button
onClick={closeModal}
className="flex absolute top-10 right-0 z-10 w-10 h-10 text-common-white bg-primary-main items-center justify-center rounded-full border-solid border-primary-dark border-2"
>
X
</button>
</div>
<ModalWrapper> <ModalWrapper>
<Container> <Container>
<h2 style={{ textAlign: 'center' }}> Settings </h2> <h2 className="text-center text-2xl mb-2"> Settings </h2>
<Separator height="1px" />
<SettingContainer> <SettingContainer>
<ToggleContainer> <ToggleContainer>
<FormLabel>Show Start Player</FormLabel> <FormLabel>Show Start Player</FormLabel>
@@ -92,13 +104,16 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
<Switch <Switch
checked={settings.keepAwake} checked={settings.keepAwake}
onChange={() => { onChange={() => {
setSettings({ ...settings, keepAwake: !settings.keepAwake }); setSettings({
...settings,
keepAwake: !settings.keepAwake,
});
}} }}
/> />
</ToggleContainer> </ToggleContainer>
<Description> <Description>
Will prevent device from going to sleep while this app is open if Will prevent device from going to sleep while this app is open
this is enabled. if this is enabled.
</Description> </Description>
</SettingContainer> </SettingContainer>
<SettingContainer> <SettingContainer>
@@ -132,8 +147,8 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
</Paragraph> </Paragraph>
</ToggleContainer> </ToggleContainer>
<Description className="mt-1"> <Description className="mt-1">
If you do, this app will work offline and the toolbar will be If you do, this app will work offline and the toolbar will
automatically hidden. be automatically hidden.
</Description> </Description>
</SettingContainer> </SettingContainer>
</> </>
@@ -147,13 +162,13 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
<span className="text-sm text-text-secondary">(latest)</span> <span className="text-sm text-text-secondary">(latest)</span>
)} )}
</Paragraph> </Paragraph>
{!isLatestVersion && newVersion.current && ( {!isLatestVersion && newVersion && (
<Paragraph className="text-text-secondary text-lg text-center"> <Paragraph className="text-text-secondary text-lg text-center">
New version ({newVersion.current}) is available!{' '} New version ({newVersion}) is available!{' '}
</Paragraph> </Paragraph>
)} )}
</SettingContainer> </SettingContainer>
{!isLatestVersion && newVersion.current && ( {!isLatestVersion && newVersion && (
<Button <Button
variant="contained" variant="contained"
style={{ marginTop: '0.25rem', marginBottom: '0.25rem' }} style={{ marginTop: '0.25rem', marginBottom: '0.25rem' }}
@@ -174,6 +189,7 @@ export const SettingsModal = ({ isOpen, closeModal }: SettingsModalProps) => {
</Button> </Button>
</Container> </Container>
</ModalWrapper> </ModalWrapper>
</>
</Modal> </Modal>
); );
}; };

View File

@@ -6,7 +6,6 @@ import { useGlobalSettings } from '../../Hooks/useGlobalSettings';
import { usePlayers } from '../../Hooks/usePlayers'; import { usePlayers } from '../../Hooks/usePlayers';
import { useSafeRotate } from '../../Hooks/useSafeRotate'; import { useSafeRotate } from '../../Hooks/useSafeRotate';
import { import {
Cross,
Energy, Energy,
Exit, Exit,
Experience, Experience,
@@ -17,10 +16,7 @@ import {
ResetGame, ResetGame,
} from '../../Icons/generated'; } from '../../Icons/generated';
import { Player, Rotation } from '../../Types/Player'; import { Player, Rotation } from '../../Types/Player';
import { import { RotationDivProps } from '../Buttons/CommanderDamage';
RotationButtonProps,
RotationDivProps,
} from '../Buttons/CommanderDamage';
const CheckboxContainer = twc.div``; const CheckboxContainer = twc.div``;
@@ -35,6 +31,7 @@ const PlayerMenuWrapper = twc.div`
justify-center justify-center
z-[2] z-[2]
webkit-user-select-none webkit-user-select-none
transition-all
`; `;
const BetterRowContainer = twc.div` const BetterRowContainer = twc.div`
@@ -85,21 +82,17 @@ const SettingsContainer = twc.div<RotationDivProps>((props) => [
: 'flex-row', : 'flex-row',
]); ]);
const CloseButton = twc.button<RotationButtonProps>((props) => [
'absolute border-none outline-none cursor-pointer bg-transparent z-[99]',
props.$rotation === Rotation.Side
? `top-[5%] right-auto left-[5%]`
: props.$rotation === Rotation.SideFlipped
? 'top-auto left-auto bottom-[5%] right-[5%]'
: 'top-[15%] right-[5%]',
]);
type PlayerMenuProps = { type PlayerMenuProps = {
player: Player; player: Player;
setShowPlayerMenu: (showPlayerMenu: boolean) => void; setShowPlayerMenu: (showPlayerMenu: boolean) => void;
isShown: boolean;
}; };
const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => { const PlayerMenu = ({
player,
setShowPlayerMenu,
isShown,
}: PlayerMenuProps) => {
const settingsContainerRef = useRef<HTMLDivElement | null>(null); const settingsContainerRef = useRef<HTMLDivElement | null>(null);
const dialogRef = useRef<HTMLDialogElement | null>(null); const dialogRef = useRef<HTMLDialogElement | null>(null);
@@ -108,9 +101,6 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
containerRef: settingsContainerRef, containerRef: settingsContainerRef,
}); });
const handleOnClick = () => {
setShowPlayerMenu(false);
};
const { fullscreen, wakeLock, goToStart } = useGlobalSettings(); const { fullscreen, wakeLock, goToStart } = useGlobalSettings();
const { updatePlayer, resetCurrentGame } = usePlayers(); const { updatePlayer, resetCurrentGame } = usePlayers();
@@ -142,7 +132,6 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
const buttonFontSize = isSide ? '1.5vmax' : '3vmin'; const buttonFontSize = isSide ? '1.5vmax' : '3vmin';
const iconSize = isSide ? '6vmin' : '3vmax'; const iconSize = isSide ? '6vmin' : '3vmax';
const extraCountersSize = isSide ? '8vmin' : '4vmax'; const extraCountersSize = isSide ? '8vmin' : '4vmax';
const closeButtonSize = isSide ? '6vmin' : '3vmax';
const calcRotation = const calcRotation =
player.settings.rotation === Rotation.Side player.settings.rotation === Rotation.Side
@@ -156,33 +145,10 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
//TODO: Fix hacky solution to rotation for SideFlipped //TODO: Fix hacky solution to rotation for SideFlipped
style={{ style={{
rotate: rotate:
player.settings.rotation === Rotation.SideFlipped ? '180deg' : '', player.settings.rotation === Rotation.SideFlipped ? `180deg` : '',
translate: isShown ? '' : player.isSide ? `-100%` : `0 -100%`,
}} }}
> >
<CloseButton
$rotation={player.settings.rotation}
style={{
rotate:
player.settings.rotation === Rotation.Side ||
player.settings.rotation === Rotation.SideFlipped
? `${player.settings.rotation - 180}deg`
: '',
}}
>
<Button
variant="text"
onClick={handleOnClick}
style={{
margin: 0,
padding: 0,
height: closeButtonSize,
width: closeButtonSize,
}}
>
<Cross size={closeButtonSize} />
</Button>
</CloseButton>
<SettingsContainer <SettingsContainer
$rotation={player.settings.rotation} $rotation={player.settings.rotation}
style={{ style={{
@@ -373,10 +339,11 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
</BetterRowContainer> </BetterRowContainer>
<dialog <dialog
ref={dialogRef} ref={dialogRef}
className="z-[9999] bg-background-default text-text-primary rounded-2xl border-none absolute top-[10%]" className="z-[9999] min-h-2/4 bg-background-default text-text-primary rounded-2xl border-none absolute bottom-[20%]"
> >
<h1>Reset Game?</h1> <div className="h-full flex flex-col p-4 gap-2">
<div style={{ display: 'flex', justifyContent: 'space-evenly' }}> <h1 className="text-center">Reset Game?</h1>
<div className="flex justify-evenly gap-4">
<Button <Button
variant="contained" variant="contained"
onClick={() => dialogRef.current?.close()} onClick={() => dialogRef.current?.close()}
@@ -393,6 +360,7 @@ const PlayerMenu = ({ player, setShowPlayerMenu }: PlayerMenuProps) => {
Yes Yes
</Button> </Button>
</div> </div>
</div>
</dialog> </dialog>
</SettingsContainer> </SettingsContainer>
</PlayerMenuWrapper> </PlayerMenuWrapper>

View File

@@ -18,7 +18,7 @@ import { twc } from 'react-twc';
import OnePlayerLandscape from '../../../Icons/generated/Layouts/OnePlayerLandscape'; import OnePlayerLandscape from '../../../Icons/generated/Layouts/OnePlayerLandscape';
import { Orientation } from '../../../Types/Settings'; import { Orientation } from '../../../Types/Settings';
const LayoutWrapper = twc.div`flex flex-row justify-between self-center`; const LayoutWrapper = twc.div`flex flex-row justify-center items-center self-center w-full`;
type LayoutOptionsProps = { type LayoutOptionsProps = {
numberOfPlayers: number; numberOfPlayers: number;
@@ -31,14 +31,16 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
selectedOrientation, selectedOrientation,
onChange, onChange,
}) => { }) => {
const iconHeight = '30vmin'; const iconWidth = '21vmin';
const iconWidth = '20vmin'; const iconHeight = '40vmin';
const iconMaxWidth = '124px';
const iconMaxHeight = '196px';
const renderLayoutOptions = () => { const renderLayoutOptions = () => {
switch (numberOfPlayers) { switch (numberOfPlayers) {
case 1: case 1:
return ( return (
<> <div>
<FormControlLabel <FormControlLabel
value={Orientation.Landscape} value={Orientation.Landscape}
control={ control={
@@ -58,6 +60,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
/> />
} }
TouchRippleProps={{ style: { display: 'none' } }} TouchRippleProps={{ style: { display: 'none' } }}
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
/> />
} }
label="" label=""
@@ -81,11 +84,12 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
/> />
} }
TouchRippleProps={{ style: { display: 'none' } }} TouchRippleProps={{ style: { display: 'none' } }}
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
/> />
} }
label="" label=""
/> />
</> </div>
); );
case 2: case 2:
return ( return (
@@ -94,6 +98,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape} value={Orientation.Landscape}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<TwoPlayersSameSide <TwoPlayersSameSide
height={iconHeight} height={iconHeight}
@@ -117,6 +122,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Portrait} value={Orientation.Portrait}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<TwoPlayersOppositePortrait <TwoPlayersOppositePortrait
height={iconHeight} height={iconHeight}
@@ -140,6 +146,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.OppositeLandscape} value={Orientation.OppositeLandscape}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<TwoPlayersOppositeLandscape <TwoPlayersOppositeLandscape
height={iconHeight} height={iconHeight}
@@ -168,6 +175,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape} value={Orientation.Landscape}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<ThreePlayers <ThreePlayers
height={iconHeight} height={iconHeight}
@@ -191,6 +199,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Portrait} value={Orientation.Portrait}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<ThreePlayersSide <ThreePlayersSide
height={iconHeight} height={iconHeight}
@@ -220,6 +229,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape} value={Orientation.Landscape}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<FourPlayers <FourPlayers
height={iconHeight} height={iconHeight}
@@ -243,6 +253,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Portrait} value={Orientation.Portrait}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<FourPlayersSide <FourPlayersSide
height={iconHeight} height={iconHeight}
@@ -272,6 +283,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape} value={Orientation.Landscape}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<FivePlayers <FivePlayers
height={iconHeight} height={iconHeight}
@@ -324,6 +336,7 @@ export const LayoutOptions: React.FC<LayoutOptionsProps> = ({
value={Orientation.Landscape} value={Orientation.Landscape}
control={ control={
<Radio <Radio
style={{ maxWidth: iconMaxWidth, maxHeight: iconMaxHeight }}
icon={ icon={
<SixPlayers <SixPlayers
height={iconHeight} height={iconHeight}

View File

@@ -20,7 +20,9 @@ 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-14 overflow-hidden items-center flex flex-col`;
const StartButtonFooter = twc.div`fixed bottom-4 z-1`; const StartButtonFooter = twc.div`w-full max-w-[548px] fixed bottom-4 z-1 items-center flex flex-col px-4`;
const SliderWrapper = twc.div`mx-8`;
const ToggleButtonsWrapper = twc.div`flex flex-row justify-between items-center`; const ToggleButtonsWrapper = twc.div`flex flex-row justify-between items-center`;
@@ -174,8 +176,10 @@ const Start = () => {
Life Trinket Life Trinket
</h1> </h1>
<FormControl focused={false} style={{ width: '80vw' }}> <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> <FormLabel>Number of Players</FormLabel>
<SliderWrapper>
<Slider <Slider
title="Number of Players" title="Number of Players"
max={6} max={6}
@@ -193,7 +197,10 @@ const Start = () => {
}); });
}} }}
/> />
</SliderWrapper>
<FormLabel className="mt-[0.7rem]">Starting Health</FormLabel> <FormLabel className="mt-[0.7rem]">Starting Health</FormLabel>
<SliderWrapper>
<Slider <Slider
title="Starting Health" title="Starting Health"
max={60} max={60}
@@ -211,6 +218,7 @@ const Start = () => {
}) })
} }
/> />
</SliderWrapper>
<ToggleButtonsWrapper className="mt-4"> <ToggleButtonsWrapper className="mt-4">
<ToggleContainer> <ToggleContainer>
@@ -277,21 +285,21 @@ const Start = () => {
}} }}
/> />
</FormControl> </FormControl>
{!isPWA && ( {!isPWA && (
<p className="text-center, max-w-[75%] text-xs text-text-primary"> <p className="text-center text-xs text-text-primary w-11/12 mt-4">
If you're on iOS, this page works better if you{' '} If you're on iOS, this page works better if you{' '}
<strong>hide the toolbar</strong> or{' '} <strong>hide the toolbar</strong> or{' '}
<strong>add the app to your home screen</strong>. <strong>add the app to your home screen</strong>.
</p> </p>
)} )}
</div>
<StartButtonFooter> <StartButtonFooter>
<Button <Button
size="large" size="large"
variant="contained" variant="contained"
onClick={doStartGame} onClick={doStartGame}
style={{ width: '90dvw' }} fullWidth
> >
START GAME START GAME
</Button> </Button>

View File

@@ -229,6 +229,7 @@ export const createInitialPlayers = ({
extraCounters: [], extraCounters: [],
commanderDamage, commanderDamage,
hasLost: false, hasLost: false,
isSide: rotation === Rotation.Side || rotation === Rotation.SideFlipped,
}; };
players.push(player); players.push(player);

View File

@@ -0,0 +1,53 @@
import { useEffect, useState } from 'react';
export interface OrientationState {
angle: number;
type: string;
}
const defaultState: OrientationState = {
angle: 0,
type: 'landscape-primary',
};
export default function useOrientation(
initialState: OrientationState = defaultState
) {
const [state, setState] = useState(initialState);
const [isLandscape, setIsLandscape] = useState(false);
useEffect(() => {
const screen = window.screen;
let mounted = true;
const onChange = () => {
if (mounted) {
const { orientation } = screen;
console.log(orientation);
if (orientation) {
const { angle, type } = orientation;
setState({ angle, type });
if (type.includes('landscape')) {
setIsLandscape(true);
} else if (type.includes('portrait')) {
setIsLandscape(false);
}
} else if (window.orientation !== undefined) {
setState({
angle:
typeof window.orientation === 'number' ? window.orientation : 0,
type: '',
});
}
}
};
onChange();
return () => {
mounted = false;
};
}, [isLandscape]);
return { state, isLandscape };
}

View File

@@ -8,6 +8,7 @@ export type Player = {
isStartingPlayer: boolean; isStartingPlayer: boolean;
showStartingPlayer: boolean; showStartingPlayer: boolean;
hasLost: boolean; hasLost: boolean;
isSide: boolean;
}; };
export type PlayerSettings = { export type PlayerSettings = {

View File

@@ -6,6 +6,9 @@ import type { Config } from 'tailwindcss';
export default { export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: { theme: {
screens: {
modalSm: '548px',
},
extend: { extend: {
gridTemplateAreas: { gridTemplateAreas: {
onePlayerLandscape: ['player0 player0'], onePlayerLandscape: ['player0 player0'],
@@ -39,23 +42,24 @@ export default {
}, },
colors: { colors: {
primary: { primary: {
main: '#7F9172', main: '#3E7D78',
dark: '#57654F', dark: '#2D5F5B',
}, },
secondary: { secondary: {
main: '#5E714C', main: '#284F4C',
dark: '#1B3B38',
}, },
background: { background: {
default: '#495E35', default: '#08253B',
backdrop: 'rgba(0, 0, 0, 0.3)', backdrop: 'rgba(0, 0, 0, 0.3)',
settings: 'rgba(20, 20, 0, 0.9)', settings: 'rgba(20, 20, 0, 0.9)',
}, },
text: { text: {
primary: '#F5F5F5', primary: '#F5F5F5',
secondary: '#b3b39b', secondary: '#76A6A5',
}, },
action: { action: {
disabled: '#5E714C', disabled: '#234A47',
}, },
common: { common: {
white: '#F9FFE3', white: '#F9FFE3',
@@ -63,7 +67,7 @@ export default {
}, },
lifeCounter: { lifeCounter: {
text: 'rgba(0, 0, 0, 0.4)', text: 'rgba(0, 0, 0, 0.4)',
lostWrapper: '#00000070', lostWrapper: '#000000',
}, },
interface: { interface: {
loseButton: { loseButton: {
@@ -95,15 +99,4 @@ export default {
}, },
plugins: [tailwindcssGridAreas], plugins: [tailwindcssGridAreas],
} satisfies Config; } satisfies Config;
// #98FF98
// const fadeOut = keyframes`
// 0% {
// opacity: 1;
// }
// 33% {
// opacity: 0.6;
// }
// 100% {
// opacity: 0;
// }
// `;

View File

@@ -1,9 +1,19 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc'; import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [
react(),
VitePWA({
registerType: 'autoUpdate',
workbox: {
clientsClaim: true,
skipWaiting: true,
},
}),
],
build: { build: {
minify: 'esbuild', minify: 'esbuild',
rollupOptions: { rollupOptions: {

2231
yarn.lock

File diff suppressed because it is too large Load Diff