diff --git a/src/Components/Buttons/CommanderDamage.tsx b/src/Components/Buttons/CommanderDamage.tsx index 78e233c..133d552 100644 --- a/src/Components/Buttons/CommanderDamage.tsx +++ b/src/Components/Buttons/CommanderDamage.tsx @@ -127,6 +127,10 @@ export const CommanderDamage = ({ const [timeoutFinished, setTimeoutFinished] = useState(false); const [hasPressedDown, setHasPressedDown] = useState(false); + const isSide = + player.settings.rotation === Rotation.Side || + player.settings.rotation === Rotation.SideFlipped; + const handleCommanderDamageChange = ( index: number, increment: number, @@ -189,9 +193,9 @@ export const CommanderDamage = ({ }; const opponentIndex = opponent.index; - const fontSize = '6vmin'; + const fontSize = isSide ? '4vmax' : '7vmin'; const fontWeight = 'bold'; - const strokeWidth = '0.5vmin'; + const strokeWidth = isSide ? '0.4vmax' : '0.7vmin'; return ( ` @@ -64,6 +46,13 @@ const IconContainer = styled.div<{ }} `; +const TextContainer = styled.div` + position: absolute; + translate: -50%; + top: 50%; + left: 50%; +`; + type ExtraCounterProps = { Icon: ReactNode; counterTotal?: number; @@ -83,6 +72,9 @@ const ExtraCounter = ({ const [timeoutFinished, setTimeoutFinished] = useState(false); const [hasPressedDown, setHasPressedDown] = useState(false); + const isSide = + rotation === Rotation.Side || rotation === Rotation.SideFlipped; + const handleCountChange = (increment: number) => { if (!counterTotal) { setCounterTotal(increment, type); @@ -115,6 +107,10 @@ const ExtraCounter = ({ setHasPressedDown(false); }; + const fontSize = isSide ? '4vmax' : '7vmin'; + const fontWeight = 'bold'; + const strokeWidth = isSide ? '0.4vmax' : '0.7vmin'; + return ( {Icon} - - {counterTotal ? counterTotal : undefined} - + + + {counterTotal ? counterTotal : undefined} + + diff --git a/src/Components/Buttons/LifeCounterButton.tsx b/src/Components/Buttons/LifeCounterButton.tsx index 6b7f388..980c020 100644 --- a/src/Components/Buttons/LifeCounterButton.tsx +++ b/src/Components/Buttons/LifeCounterButton.tsx @@ -23,10 +23,6 @@ export const StyledLifeCounterButton = styled.button` -moz-user-select: -moz-none; -webkit-user-select: none; -ms-user-select: none; - @media (orientation: landscape) { - max-width: 50vmin; - max-height: 50vmax; - } `; const TextContainer = styled.div<{ @@ -34,7 +30,6 @@ const TextContainer = styled.div<{ rotation: number; }>` position: relative; - top: -10%; ${(props) => { if ( @@ -45,13 +40,11 @@ const TextContainer = styled.div<{ return css` rotate: -90deg; bottom: 25%; - left: -10%; top: auto; `; } return css` rotate: -90deg; - left: -10%; top: 25%; `; } @@ -117,7 +110,7 @@ const LifeCounterButton = ({ const fontSize = rotation === Rotation.SideFlipped || rotation === Rotation.Side ? '8vmax' - : '16vmin'; + : '12vmin'; return ( void; gridAreas: string; resetCurrentGame: () => void; + wakeLock: WakeLock; }; const Counters = ({ @@ -47,6 +49,7 @@ const Counters = ({ onPlayerChange, gridAreas, resetCurrentGame, + wakeLock, }: CountersProps) => { return ( @@ -65,6 +68,7 @@ const Counters = ({ )} onPlayerChange={onPlayerChange} resetCurrentGame={resetCurrentGame} + wakeLock={wakeLock} /> ); diff --git a/src/Components/Counters/ExtraCountersBar.tsx b/src/Components/Counters/ExtraCountersBar.tsx index a420aea..bef6397 100644 --- a/src/Components/Counters/ExtraCountersBar.tsx +++ b/src/Components/Counters/ExtraCountersBar.tsx @@ -11,15 +11,31 @@ import { Poison, } from '../../Icons/generated'; +const Container = styled.div<{ rotation: number }>` + width: 100%; + height: 20vmin; + display: flex; + + ${(props) => { + if ( + props.rotation === Rotation.SideFlipped || + props.rotation === Rotation.Side + ) { + return css` + height: 100%; + width: 9.3vmax; + `; + } + }} +`; + const ExtraCountersGrid = styled.div<{ rotation: number }>` display: flex; position: absolute; + width: 100%; flex-direction: row; flex-grow: 1; - width: 100%; - justify-content: space-evenly; bottom: 0; - width: 100%; pointer-events: none; ${(props) => { @@ -81,76 +97,95 @@ const ExtraCountersBar = ({ onPlayerChange(updatedPlayer); }; - const iconSize = '8vmin'; + const iconSize = + player.settings.rotation === Rotation.SideFlipped || + player.settings.rotation === Rotation.Side + ? '5vmax' + : '10vmin'; + + const { + useCommanderDamage, + usePartner, + usePoison, + useEnergy, + useExperience, + } = player.settings; + + const hasExtraCounters = + useCommanderDamage || usePartner || usePoison || useEnergy || useExperience; + + if (!hasExtraCounters) { + return null; + } return ( - - {player.settings.useCommanderDamage && ( - } - type={CounterType.CommanderTax} - counterTotal={ - player.extraCounters?.find( - (counter) => counter.type === 'commanderTax' - )?.value - } - setCounterTotal={handleCounterChange} - /> - )} - {Boolean( - player.settings.useCommanderDamage && player.settings.usePartner - ) && ( - } - type={CounterType.PartnerTax} - counterTotal={ - player.extraCounters?.find( - (counter) => counter.type === 'partnerTax' - )?.value - } - setCounterTotal={handleCounterChange} - /> - )} - {player.settings.usePoison && ( - } - type={CounterType.Poison} - counterTotal={ - player.extraCounters?.find((counter) => counter.type === 'poison') - ?.value - } - setCounterTotal={handleCounterChange} - /> - )} - {player.settings.useEnergy && ( - } - type={CounterType.Energy} - counterTotal={ - player.extraCounters?.find((counter) => counter.type === 'energy') - ?.value - } - setCounterTotal={handleCounterChange} - /> - )} - {player.settings.useExperience && ( - } - type={CounterType.Experience} - counterTotal={ - player.extraCounters?.find( - (counter) => counter.type === 'experience' - )?.value - } - setCounterTotal={handleCounterChange} - /> - )} - + + + {useCommanderDamage && ( + } + type={CounterType.CommanderTax} + counterTotal={ + player.extraCounters?.find( + (counter) => counter.type === 'commanderTax' + )?.value + } + setCounterTotal={handleCounterChange} + /> + )} + {Boolean(useCommanderDamage && usePartner) && ( + } + type={CounterType.PartnerTax} + counterTotal={ + player.extraCounters?.find( + (counter) => counter.type === 'partnerTax' + )?.value + } + setCounterTotal={handleCounterChange} + /> + )} + {usePoison && ( + } + type={CounterType.Poison} + counterTotal={ + player.extraCounters?.find((counter) => counter.type === 'poison') + ?.value + } + setCounterTotal={handleCounterChange} + /> + )} + {useEnergy && ( + } + type={CounterType.Energy} + counterTotal={ + player.extraCounters?.find((counter) => counter.type === 'energy') + ?.value + } + setCounterTotal={handleCounterChange} + /> + )} + {useExperience && ( + } + type={CounterType.Experience} + counterTotal={ + player.extraCounters?.find( + (counter) => counter.type === 'experience' + )?.value + } + setCounterTotal={handleCounterChange} + /> + )} + + ); }; diff --git a/src/Components/LifeCounter/Health.tsx b/src/Components/LifeCounter/Health.tsx new file mode 100644 index 0000000..fd54448 --- /dev/null +++ b/src/Components/LifeCounter/Health.tsx @@ -0,0 +1,234 @@ +import { useEffect, useRef, useState } from 'react'; +import styled, { css, keyframes } from 'styled-components'; +import { Player, Rotation } from '../../Types/Player'; +import LifeCounterButton from '../Buttons/LifeCounterButton'; +import { OutlinedText } from '../Misc/OutlinedText'; + +const LifeCountainer = styled.div<{ + rotation: Rotation; +}>` + position: relative; + display: flex; + flex-direction: row; + flex-grow: 1; + width: 100%; + height: 100%; + justify-content: space-between; + align-items: center; + + ${(props) => { + if ( + props.rotation === Rotation.SideFlipped || + props.rotation === Rotation.Side + ) { + return css` + flex-direction: column-reverse; + `; + } + }} +`; + +const LifeCounterTextContainer = styled.div<{ + rotation: Rotation; +}>` + position: absolute; + width: 60%; + height: 100%; + margin: 0; + padding: 0; + pointer-events: none; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: transparent; + user-select: none; + -moz-user-select: -moz-none; + -webkit-user-select: none; + -ms-user-select: none; + + ${(props) => { + if ( + props.rotation === Rotation.SideFlipped || + props.rotation === Rotation.Side + ) { + return css` + width: 100%; + height: 60%; + `; + } + }} +`; + +const TextWrapper = styled.div` + display: flex; + position: absolute; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + z-index: -1; +`; + +const fadeOut = keyframes` + 0% { + opacity: 1; + } + 33% { + opacity: 0.6; + } + 100% { + opacity: 0; + } +`; + +export const RecentDifference = styled.span` + position: absolute; + top: 40%; + left: 50%; + transform: translate(-50%, -50%); + text-shadow: none; + background-color: rgba(255, 255, 255, 0.6); + font-variant-numeric: tabular-nums; + border-radius: 50%; + padding: 5px 10px; + font-size: 8vmin; + color: #333333; + animation: ${fadeOut} 3s 1s ease-out forwards; +`; + +type HealthProps = { + player: Player; + onPlayerChange: (updatedPlayer: Player) => void; + differenceKey: number; + setDifferenceKey: (key: number) => void; + rotation: Rotation; +}; + +const Health = ({ + player, + onPlayerChange, + differenceKey, + setDifferenceKey, + rotation, +}: HealthProps) => { + const handleLifeChange = (updatedLifeTotal: number) => { + const difference = updatedLifeTotal - player.lifeTotal; + const updatedPlayer = { + ...player, + lifeTotal: updatedLifeTotal, + hasLost: false, + }; + setRecentDifference(recentDifference + difference); + onPlayerChange(updatedPlayer); + setDifferenceKey(Date.now()); + }; + + const [recentDifference, setRecentDifference] = useState(0); + const [showStartingPlayer, setShowStartingPlayer] = useState( + localStorage.getItem('playing') === 'true' + ); + const [fontSize, setFontSize] = useState(16); + const textContainerRef = useRef(null); + + useEffect(() => { + const timer = setTimeout(() => { + setRecentDifference(0); + }, 3_000); + + return () => clearTimeout(timer); + }, [recentDifference]); + + useEffect(() => { + if (!showStartingPlayer) { + const playingTimer = setTimeout(() => { + localStorage.setItem('playing', 'true'); + setShowStartingPlayer(localStorage.getItem('playing') === 'true'); + }, 3_000); + + return () => clearTimeout(playingTimer); + } + }, [showStartingPlayer]); + + useEffect(() => { + if (!textContainerRef.current) { + return; + } + + const textContainer = textContainerRef.current; + const resizeObserver = new ResizeObserver(() => { + const calcFontSize = calculateFontSize(textContainer); + setFontSize(calcFontSize); + }); + + // Initially calculate font size + const initialCalculation = () => { + const calcFontSize = calculateFontSize(textContainer); + setFontSize(calcFontSize); + }; + initialCalculation(); + + resizeObserver.observe(textContainer); + + return () => { + // Cleanup: disconnect the ResizeObserver when the component unmounts. + resizeObserver.disconnect(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [textContainerRef]); + + const calculateFontSize = (container: HTMLDivElement) => { + const isSide = + rotation === Rotation.SideFlipped || rotation === Rotation.Side; + + const widthRatio = isSide ? container.clientHeight : container.clientWidth; + + const heightRatio = isSide ? container.clientWidth : container.clientHeight; + + const minRatio = Math.min(widthRatio, heightRatio); + + const heightIsLarger = heightRatio > widthRatio; + + const scaleFactor = heightIsLarger ? 0.8 : 1; + + return minRatio * scaleFactor * 1; + }; + + return ( + + + + + + {player.lifeTotal} + + {recentDifference !== 0 && ( + + {recentDifference > 0 ? '+' : ''} + {recentDifference} + + )} + + + + + ); +}; + +export default Health; diff --git a/src/Components/LifeCounter/LifeCounter.tsx b/src/Components/LifeCounter/LifeCounter.tsx index 802277a..8b5f5e0 100644 --- a/src/Components/LifeCounter/LifeCounter.tsx +++ b/src/Components/LifeCounter/LifeCounter.tsx @@ -2,13 +2,19 @@ import { useEffect, useState } from 'react'; import styled, { css, keyframes } from 'styled-components'; import { theme } from '../../Data/theme'; import { Player, Rotation } from '../../Types/Player'; -import LifeCounterButton from '../Buttons/LifeCounterButton'; import { LoseGameButton } from '../Buttons/LoseButton'; import SettingsButton from '../Buttons/SettingsButton'; import CommanderDamageBar from '../Counters/CommanderDamageBar'; import ExtraCountersBar from '../Counters/ExtraCountersBar'; -import { OutlinedText } from '../Misc/OutlinedText'; import PlayerMenu from '../PlayerMenu/PlayerMenu'; +import Health from './Health'; +import { WakeLock } from '../../Types/WakeLock'; + +const Lmao = styled.div` + position: absolute; + width: 100%; + height: 100%; +`; const LifeCounterContentWrapper = styled.div<{ backgroundColor: string; @@ -59,29 +65,6 @@ const LifeCounterWrapper = styled.div<{ }} `; -const LifeCountainer = styled.div<{ - rotation: Rotation; -}>` - display: flex; - flex-direction: row; - flex-grow: 1; - width: 100%; - height: 100%; - justify-content: space-between; - align-items: center; - - ${(props) => { - if ( - props.rotation === Rotation.SideFlipped || - props.rotation === Rotation.Side - ) { - return css` - flex-direction: column-reverse; - `; - } - }} -`; - const PlayerNoticeWrapper = styled.div<{ rotation: Rotation; backgroundColor: string; @@ -128,31 +111,6 @@ const DynamicText = styled.div<{ rotation: Rotation }>` }} `; -const LifeCounterTextContainer = styled.p<{ - rotation: Rotation; -}>` - margin: 0; - padding: 0; - pointer-events: none; - -webkit-touch-callout: none; - -webkit-tap-highlight-color: transparent; - user-select: none; - -moz-user-select: -moz-none; - -webkit-user-select: none; - -ms-user-select: none; - ${(props) => { - if ( - props.rotation === Rotation.SideFlipped || - props.rotation === Rotation.Side - ) { - return css` - rotate: 270deg; - margin-right: 25%; - `; - } - }} -`; - const fadeOut = keyframes` 0% { opacity: 1; @@ -180,14 +138,6 @@ export const RecentDifference = styled.span` animation: ${fadeOut} 3s 1s ease-out forwards; `; -interface LifeCounterProps { - backgroundColor: string; - player: Player; - opponents: Player[]; - onPlayerChange: (updatedPlayer: Player) => void; - resetCurrentGame: () => void; -} - const hasCommanderDamageReached21 = (player: Player) => { const commanderDamageTotals = player.commanderDamage.map( (commanderDamage) => commanderDamage.damageTotal @@ -215,12 +165,22 @@ const playerCanLose = (player: Player) => { ); }; +type LifeCounterProps = { + backgroundColor: string; + player: Player; + opponents: Player[]; + onPlayerChange: (updatedPlayer: Player) => void; + resetCurrentGame: () => void; + wakeLock: WakeLock; +}; + const LifeCounter = ({ backgroundColor, player, opponents, onPlayerChange, resetCurrentGame, + wakeLock, }: LifeCounterProps) => { const handleLifeChange = (updatedLifeTotal: number) => { const difference = updatedLifeTotal - player.lifeTotal; @@ -231,7 +191,7 @@ const LifeCounter = ({ }; setRecentDifference(recentDifference + difference); onPlayerChange(updatedPlayer); - setKey(Date.now()); + setDifferenceKey(Date.now()); }; const toggleGameLost = () => { @@ -244,7 +204,7 @@ const LifeCounter = ({ const [showStartingPlayer, setShowStartingPlayer] = useState( localStorage.getItem('playing') === 'true' ); - const [key, setKey] = useState(Date.now()); + const [differenceKey, setDifferenceKey] = useState(Date.now()); useEffect(() => { const timer = setTimeout(() => { @@ -268,92 +228,55 @@ const LifeCounter = ({ player.settings.rotation === Rotation.SideFlipped || player.settings.rotation === Rotation.Side; - const size = - player.settings.rotation === Rotation.SideFlipped || - player.settings.rotation === Rotation.Side - ? 15 - : 30; - - const fontSize = - player.settings.rotation === Rotation.SideFlipped || - player.settings.rotation === Rotation.Side - ? `${size}vmax` - : `${size}vmin`; - - const strokeWidth = - player.settings.rotation === Rotation.SideFlipped || - player.settings.rotation === Rotation.Side - ? `${size / 20}vmax` - : `${size / 20}vmin`; - return ( - - {player.isStartingPlayer && !showStartingPlayer && ( - - - You start! - - - )} + + + {player.isStartingPlayer && !showStartingPlayer && ( + + + You start! + + + )} - {player.hasLost && ( - - )} - - { - setShowPlayerMenu(!showPlayerMenu); - }} - rotation={player.settings.rotation} - /> - {playerCanLose(player) && ( - - )} - - + )} + - - - {player.lifeTotal} - - {recentDifference !== 0 && ( - - {recentDifference > 0 ? '+' : ''} - {recentDifference} - - )} - - { + setShowPlayerMenu(!showPlayerMenu); + }} rotation={player.settings.rotation} - operation="add" - increment={1} /> - - - - + {playerCanLose(player) && ( + + )} + + + + {showPlayerMenu && ( )} diff --git a/src/Components/Misc/OutlinedText.tsx b/src/Components/Misc/OutlinedText.tsx index 4c8af1b..4439089 100644 --- a/src/Components/Misc/OutlinedText.tsx +++ b/src/Components/Misc/OutlinedText.tsx @@ -1,5 +1,15 @@ -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; import { theme } from '../../Data/theme'; +import { Rotation } from '../../Types/Player'; + +const Container = styled.div` + display: flex; + position: relative; + width: 100%; + height: 100%; + align-items: center; + justify-content: center; +`; const CenteredText = styled.div<{ strokeWidth?: string; @@ -7,11 +17,9 @@ const CenteredText = styled.div<{ fillColor?: string; fontSize?: string; fontWeight?: string; + rotation?: Rotation; }>` position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); font-weight: ${(props) => props.fontWeight || ''}; font-variant-numeric: tabular-nums; user-select: none; @@ -26,6 +34,17 @@ const CenteredText = styled.div<{ -webkit-text-stroke: ${(props) => props.strokeWidth || '1vmin'} ${(props) => props.strokeColor || theme.palette.common.white}; -webkit-text-fill-color: ${(props) => props.fillColor || theme.palette.common.black}; + + ${(props) => { + if ( + props.rotation === Rotation.SideFlipped || + props.rotation === Rotation.Side + ) { + return css` + rotate: 270deg; + `; + } + }} `; const CenteredTextOutline = styled.span` @@ -42,6 +61,7 @@ type OutlinedTextProps = { strokeWidth?: string; strokeColor?: string; fillColor?: string; + rotation?: Rotation; }; export const OutlinedText: React.FC = ({ @@ -51,17 +71,21 @@ export const OutlinedText: React.FC = ({ strokeWidth, strokeColor, fillColor, + rotation, }) => { return ( - - {children} - {children} - + + + {children} + {children} + + ); };