diff --git a/package.json b/package.json index 4371640..8001815 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "life-trinket", "private": true, - "version": "0.6.4", + "version": "0.6.5", "type": "commonjs", "engines": { "node": ">=18", diff --git a/src/Components/Buttons/CommanderDamage.tsx b/src/Components/Buttons/CommanderDamage.tsx index e72aa23..1ea9d9b 100644 --- a/src/Components/Buttons/CommanderDamage.tsx +++ b/src/Components/Buttons/CommanderDamage.tsx @@ -66,8 +66,7 @@ export const CommanderDamage = ({ }: CommanderDamageButtonComponentProps) => { const { updatePlayer } = usePlayers(); const timeoutRef = useRef(undefined); - const [timeoutFinished, setTimeoutFinished] = useState(false); - const [hasPressedDown, setHasPressedDown] = useState(false); + const [downLongPressed, setDownLongPressed] = useState(false); const downPositionRef = useRef({ x: 0, y: 0 }); const handleCommanderDamageChange = ( @@ -109,16 +108,16 @@ export const CommanderDamage = ({ const handleDownInput = ({ opponentIndex, isPartner, event }: InputProps) => { downPositionRef.current = { x: event.clientX, y: event.clientY }; - setTimeoutFinished(false); - setHasPressedDown(true); + setDownLongPressed(false); + timeoutRef.current = setTimeout(() => { - setTimeoutFinished(true); + setDownLongPressed(true); handleCommanderDamageChange(opponentIndex, -1, isPartner); }, decrementTimeoutMs); }; const handleUpInput = ({ opponentIndex, isPartner, event }: InputProps) => { - if (!(hasPressedDown && !timeoutFinished)) { + if (downLongPressed) { return; } @@ -134,14 +133,14 @@ export const CommanderDamage = ({ return; } + clearTimeout(timeoutRef.current); + handleCommanderDamageChange(opponentIndex, 1, isPartner); - setHasPressedDown(false); }; const handleLeaveInput = () => { - setTimeoutFinished(true); + setDownLongPressed(true); clearTimeout(timeoutRef.current); - setHasPressedDown(false); }; const opponentIndex = opponent.index; diff --git a/src/Components/Buttons/LifeCounterButton.tsx b/src/Components/Buttons/LifeCounterButton.tsx index 007da84..a5743f5 100644 --- a/src/Components/Buttons/LifeCounterButton.tsx +++ b/src/Components/Buttons/LifeCounterButton.tsx @@ -1,8 +1,9 @@ -import { useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { TwcComponentProps, twc } from 'react-twc'; import { lifeLongPressMultiplier } from '../../Data/constants'; -import { Rotation } from '../../Types/Player'; +import { Player, Rotation } from '../../Types/Player'; import { MAX_TAP_MOVE_DISTANCE } from './CommanderDamage'; +import { checkContrast } from '../../Utils/checkContrast'; type RotationButtonProps = TwcComponentProps<'div'> & { $align?: string; @@ -13,7 +14,6 @@ const LifeCounterButtonTwc = twc.button` h-full w-full flex - text-lifeCounter-text font-semibold bg-transparent border-none @@ -40,17 +40,15 @@ const TextContainer = twc.div((props) => [ ]); type LifeCounterButtonProps = { - lifeTotal: number; + player: Player; setLifeTotal: (lifeTotal: number) => void; - rotation: number; operation: 'add' | 'subtract'; increment: number; }; const LifeCounterButton = ({ - lifeTotal, + player, setLifeTotal, - rotation, operation, increment, }: LifeCounterButtonProps) => { @@ -59,8 +57,20 @@ const LifeCounterButton = ({ const [hasPressedDown, setHasPressedDown] = useState(false); const downPositionRef = useRef({ x: 0, y: 0 }); + const [iconColor, setIconColor] = useState<'dark' | 'light'>('dark'); + + useEffect(() => { + const contrast = checkContrast(player.color, '#00000080'); + + if (contrast === 'Fail') { + setIconColor('light'); + } else { + setIconColor('dark'); + } + }, [player.color]); + const handleLifeChange = (increment: number) => { - setLifeTotal(lifeTotal + increment); + setLifeTotal(player.lifeTotal + increment); }; const handleDownInput = (event: React.PointerEvent) => { @@ -102,7 +112,8 @@ const LifeCounterButton = ({ }; const fontSize = - rotation === Rotation.SideFlipped || rotation === Rotation.Side + player.settings.rotation === Rotation.SideFlipped || + player.settings.rotation === Rotation.Side ? '8vmax' : '12vmin'; @@ -118,8 +129,11 @@ const LifeCounterButton = ({ aria-label={`${operation === 'add' ? 'Add' : 'Subtract'} life`} > {operation === 'add' ? '\u002B' : '\u2212'} diff --git a/src/Components/Buttons/LoseButton.tsx b/src/Components/Buttons/LoseButton.tsx index 8a2f039..c405dda 100644 --- a/src/Components/Buttons/LoseButton.tsx +++ b/src/Components/Buttons/LoseButton.tsx @@ -7,7 +7,7 @@ const LoseButton = twc.div((props) => [ '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.Side - ? `left-[19%]` + ? `left-[21%]` : 'top-[21%]', ]); diff --git a/src/Components/Counters/ExtraCountersBar.tsx b/src/Components/Counters/ExtraCountersBar.tsx index d63890c..ed04244 100644 --- a/src/Components/Counters/ExtraCountersBar.tsx +++ b/src/Components/Counters/ExtraCountersBar.tsx @@ -10,6 +10,8 @@ import { import { CounterType, Player, Rotation } from '../../Types/Player'; import { RotationDivProps } from '../Buttons/CommanderDamage'; import ExtraCounter from '../Buttons/ExtraCounter'; +import { useEffect, useState } from 'react'; +import { checkContrast } from '../../Utils/checkContrast'; const Container = twc.div((props) => [ 'flex', @@ -31,6 +33,17 @@ type ExtraCountersBarProps = { const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => { const { updatePlayer } = usePlayers(); + const [iconColor, setIconColor] = useState<'dark' | 'light'>('dark'); + + useEffect(() => { + const contrast = checkContrast(player.color, '#00000080'); + + if (contrast === 'Fail') { + setIconColor('light'); + } else { + setIconColor('dark'); + } + }, [player.color]); const handleCounterChange = ( updatedCounterTotal: number, @@ -93,7 +106,13 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => { {useCommanderDamage && ( } + Icon={ + + } type={CounterType.CommanderTax} counterTotal={ player.extraCounters?.find( @@ -108,7 +127,13 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => { {Boolean(useCommanderDamage && usePartner) && ( } + Icon={ + + } type={CounterType.PartnerTax} counterTotal={ player.extraCounters?.find( @@ -123,7 +148,13 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => { {usePoison && ( } + Icon={ + + } type={CounterType.Poison} counterTotal={ player.extraCounters?.find((counter) => counter.type === 'poison') @@ -137,7 +168,13 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => { {useEnergy && ( } + Icon={ + + } type={CounterType.Energy} counterTotal={ player.extraCounters?.find((counter) => counter.type === 'energy') @@ -151,7 +188,13 @@ const ExtraCountersBar = ({ player }: ExtraCountersBarProps) => { {useExperience && ( } + Icon={ + + } type={CounterType.Experience} counterTotal={ player.extraCounters?.find( diff --git a/src/Components/LifeCounter/Health.tsx b/src/Components/LifeCounter/Health.tsx index a3b1e2b..2128776 100644 --- a/src/Components/LifeCounter/Health.tsx +++ b/src/Components/LifeCounter/Health.tsx @@ -118,9 +118,8 @@ const Health = ({ return ( @@ -148,9 +147,8 @@ const Health = ({ diff --git a/src/Components/Player/PlayerMenu.tsx b/src/Components/Player/PlayerMenu.tsx index 6d852ea..87d5d49 100644 --- a/src/Components/Player/PlayerMenu.tsx +++ b/src/Components/Player/PlayerMenu.tsx @@ -66,17 +66,15 @@ const ButtonsSections = twc.div` flex-wrap `; -const ColorPicker = twc.input` +const ColorPickerButton = twc.div` h-[8vmax] w-[8vmax] + relative max-h-12 - max-w-11 - border-none - outline-none + max-w-12 + rounded-full cursor-pointer - bg-transparent - user-select-none - text-common-white + overflow-hidden `; const SettingsContainer = twc.div((props) => [ @@ -98,7 +96,7 @@ const PlayerMenu = ({ isShown, }: PlayerMenuProps) => { const settingsContainerRef = useRef(null); - const dialogRef = useRef(null); + const resetGameDialogRef = useRef(null); const { isSide } = useSafeRotate({ rotation: player.settings.rotation, @@ -162,13 +160,14 @@ const PlayerMenu = ({ > - + + + {player.settings.useCommanderDamage && ( )} - - - dialogRef.current?.show()} + onClick={() => resetGameDialogRef.current?.show()} role="checkbox" aria-checked={wakeLock.active} aria-label="Reset Game" @@ -342,27 +338,30 @@ const PlayerMenu = ({ resetGameDialogRef.current?.close()} > -
-

Reset Game?

-
- - +
+
+

Reset Game?

+
+ + +
diff --git a/src/Utils/checkContrast.ts b/src/Utils/checkContrast.ts new file mode 100644 index 0000000..549b0ad --- /dev/null +++ b/src/Utils/checkContrast.ts @@ -0,0 +1,87 @@ +type RGBA = { + red: number; + green: number; + blue: number; + alpha: number; +}; + +export const hexToRgb = (hex: string): RGBA => { + hex = hex.replace(/^#/, ''); + let alpha = 255; + + if (hex.length === 8) { + alpha = parseInt(hex.slice(6, 8), 16); + hex = hex.substring(0, 6); + } + + if (hex.length === 4) { + alpha = parseInt(hex.slice(3, 4).repeat(2), 16); + hex = hex.substring(0, 3); + } + + if (hex.length === 3) { + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + + const num = parseInt(hex, 16); + const red = num >> 16; + const green = (num >> 8) & 255; + const blue = num & 255; + + return { red, green, blue, alpha }; +}; + +export const luminance = (a: number, b: number) => { + const l1 = Math.max(a, b); + const l2 = Math.min(a, b); + return (l1 + 0.05) / (l2 + 0.05); +}; + +export const rgbContrast = (a: RGBA, b: RGBA) => { + return luminance(relativeLuminance(a), relativeLuminance(b)); +}; + +// calculate the color contrast ratio +export const checkContrast = (hexC1: string, hexC2: string) => { + const color1rgb = hexToRgb(hexC1); + const color2rgb = hexToRgb(hexC2); + + const contrast = rgbContrast(color1rgb, color2rgb); + + if (contrast >= 7) { + return 'AAA'; + } + + if (contrast >= 4.5) { + return 'AA'; + } + + if (contrast >= 3) { + return 'AA Large'; + } + + return 'Fail'; +}; + +// red, green, and blue coefficients +const rc = 0.2126; +const gc = 0.7152; +const bc = 0.0722; +// low-gamma adjust coefficient +const lowc = 1 / 12.92; + +function adjustGamma(input: number) { + return Math.pow((input + 0.055) / 1.055, 2.4); +} + +export const relativeLuminance = (rgb: RGBA) => { + const rsrgb = rgb.red / 255; + const gsrgb = rgb.green / 255; + const bsrgb = rgb.blue / 255; + + const r = rsrgb <= 0.03928 ? rsrgb * lowc : adjustGamma(rsrgb); + const g = gsrgb <= 0.03928 ? gsrgb * lowc : adjustGamma(gsrgb); + const b = bsrgb <= 0.03928 ? bsrgb * lowc : adjustGamma(bsrgb); + + return r * rc + g * gc + b * bc; +}; diff --git a/tailwind.config.ts b/tailwind.config.ts index 714d343..df02515 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -54,6 +54,10 @@ export default { backdrop: 'rgba(0, 0, 0, 0.3)', settings: 'rgba(20, 20, 0, 0.9)', }, + icons: { + dark: '#00000080', + light: '#ffffff4f', + }, text: { primary: '#F5F5F5', secondary: '#76A6A5',