Compare commits

..

4 Commits

Author SHA1 Message Date
Gavin McDonald
9ca34540e8 fix tilt settings 2025-07-08 16:04:30 -04:00
Gavin McDonald
6e312d5d2e touch tilt 2025-07-08 15:49:02 -04:00
Gavin McDonald
11245bf4d8 fix mobile spacing and scrolling 2025-07-08 08:24:40 -04:00
Gavin McDonald
62c3b7b557 clean up fonts 2025-07-08 07:52:43 -04:00
11 changed files with 55 additions and 45 deletions

View File

@@ -63,14 +63,15 @@ export function AppProvider({ children }: { children: ReactNode }) {
const { dmID } = gameData; const { dmID } = gameData;
const isDM = !!dmID; const isDM = !!dmID;
const settings = { ...gameData.settings, ...localSettings };
const appInterface = { const appInterface = {
gameData, gameData,
isDM, isDM,
noGame, noGame,
selectCardIndex, selectCardIndex,
settings: { ...gameData.settings, ...localSettings }, settings,
tilts: reduceTilts(gameData, localTilt), tilts: reduceTilts(gameData, localTilt, settings),
emitFlip, emitFlip,
emitSettings, emitSettings,
emitRedraw, emitRedraw,

View File

@@ -24,7 +24,7 @@ export default function GamePage() {
return noGame ? ( return noGame ? (
<NotFound /> <NotFound />
) : ( ) : (
<main className="min-h-screen flex flex-col items-center justify-center gap-4 bg-[url('/img/table3.png')] bg-cover bg-center"> <main className="h-dvh flex flex-col items-center justify-center gap-4 bg-[url('/img/table3.png')] bg-cover bg-center">
<SpectatorLink /> <SpectatorLink />
<Settings /> <Settings />
<TarokkaGrid /> <TarokkaGrid />

View File

@@ -1,26 +1,14 @@
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { Pirata_One, Eagle_Lake, Cinzel_Decorative } from 'next/font/google'; import { Eagle_Lake } from 'next/font/google';
import { AppProvider } from '@/app/AppContext'; import { AppProvider } from '@/app/AppContext';
import './globals.css'; import './globals.css';
const pirataOne = Pirata_One({
variable: '--font-pirata',
subsets: ['latin'],
weight: '400',
});
const eagleLake = Eagle_Lake({ const eagleLake = Eagle_Lake({
variable: '--font-eagle-lake', variable: '--font-eagle-lake',
subsets: ['latin'], subsets: ['latin'],
weight: '400', weight: '400',
}); });
const cinzel = Cinzel_Decorative({
variable: '--font-cinzel',
subsets: ['latin'],
weight: '400',
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Tarokka', title: 'Tarokka',
description: 'Fortune telling for D&Ds Curse of Strahd', description: 'Fortune telling for D&Ds Curse of Strahd',
@@ -37,11 +25,8 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html <html lang="en" className={`${eagleLake.variable} antialiased overscroll-none`}>
lang="en" <body className={`${eagleLake.className} antialiased h-dvh`}>
className={`${pirataOne.variable} ${eagleLake.variable} ${cinzel.variable} antialiased`}
>
<body className={`${eagleLake.className} antialiased`}>
<AppProvider>{children}</AppProvider> <AppProvider>{children}</AppProvider>
</body> </body>
</html> </html>

View File

@@ -16,7 +16,7 @@ export default function Home() {
}; };
return ( return (
<main className="min-h-screen flex justify-center items-center text-yellow-400 bg-[url('/img/table3.png')] bg-cover bg-center"> <main className="flex justify-center items-center h-dvh text-yellow-400 bg-[url('/img/table3.png')] bg-cover bg-center">
<div className="flex flex-col items-center gap-8 text-center"> <div className="flex flex-col items-center gap-8 text-center">
<h1 className="text-5xl font-bold text-center text-primary">Tarokka</h1> <h1 className="text-5xl font-bold text-center text-primary">Tarokka</h1>
<p className="text-l text-center w-[350px] m-auto"> <p className="text-l text-center w-[350px] m-auto">

View File

@@ -54,7 +54,7 @@ export default function Card({ card, cardIndex }: CardProps) {
return ( return (
<ToolTip content={tooltip || getTooltip()}> <ToolTip content={tooltip || getTooltip()}>
<TiltCard <TiltCard
className={`h-[21vh] w-[15vh] relative perspective transition-transform duration-200 z-0 hover:z-10 hover:scale-150 ${isDM ? 'cursor-pointer' : ''} `} className={`h-[21vh] w-[15vh] max-w-[30vw] relative perspective transition-transform duration-200 z-0 hover:z-10 hover:scale-150 ${isDM ? 'cursor-pointer' : ''} `}
cardIndex={cardIndex} cardIndex={cardIndex}
> >
<div <div

View File

@@ -29,7 +29,7 @@ export default function CardStyle({ className }: { className?: string }) {
flex justify-center flex justify-center
cursor-pointer cursor-pointer
w-full px-3 py-2 w-full px-3 py-2
text-xs font-medium text-xs font-medium capitalize
border border-yellow-500 border border-yellow-500
transition hover:text-yellow-300 hover:drop-shadow-[0_0_3px_#ffd700] transition hover:text-yellow-300 hover:drop-shadow-[0_0_3px_#ffd700]
${settings.cardStyle === option ? 'bg-slate-700 text-yellow-300 font-extrabold' : 'bg-slate-800 hover:bg-slate-700'} ${settings.cardStyle === option ? 'bg-slate-700 text-yellow-300 font-extrabold' : 'bg-slate-800 hover:bg-slate-700'}

View File

@@ -2,7 +2,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { CircleX, Settings as Gear } from 'lucide-react'; import { CircleX, Settings as Gear } from 'lucide-react';
import { Cinzel_Decorative } from 'next/font/google';
import { useAppContext } from '@/app/AppContext'; import { useAppContext } from '@/app/AppContext';
import Scrim from '@/components/Scrim'; import Scrim from '@/components/Scrim';
@@ -12,18 +11,12 @@ import ExternalLinks from './ExternalLinks';
import GameLinks from './GameLinks'; import GameLinks from './GameLinks';
import Permissions from './Permissions'; import Permissions from './Permissions';
const cinzel = Cinzel_Decorative({
variable: '--font-cinzel',
subsets: ['latin'],
weight: '400',
});
export default function Settings() { export default function Settings() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { isDM } = useAppContext(); const { isDM } = useAppContext();
return ( return (
<div className={`fixed top-4 right-4 z-25 ${cinzel.className}`}> <div className={`fixed top-4 right-4 z-25`}>
<Scrim <Scrim
clickAction={() => setOpen((prev) => !prev)} clickAction={() => setOpen((prev) => !prev)}
className={`transition-all duration-250 ${open ? 'pointer-events-auto opacity-100' : 'pointer-events-none opacity-0'}`} className={`transition-all duration-250 ${open ? 'pointer-events-auto opacity-100' : 'pointer-events-none opacity-0'}`}

View File

@@ -15,7 +15,7 @@ export default function TarokkaGrid() {
const arrangeCards = (_cell: unknown, index: number) => cards[cardMap[index]]; const arrangeCards = (_cell: unknown, index: number) => cards[cardMap[index]];
return ( return (
<div className="grid grid-cols-3 grid-rows-3 gap-8 w-fit mx-auto"> <div className="grid grid-cols-3 grid-rows-3 gap-2 sm:gap-4 md:gap-8 w-fit mx-auto">
{Array.from({ length: 9 }) {Array.from({ length: 9 })
.map(arrangeCards) .map(arrangeCards)
.map((card, index) => ( .map((card, index) => (

View File

@@ -41,13 +41,14 @@ export default function TiltCard({
card.style.transform = ZERO_ROTATION; card.style.transform = ZERO_ROTATION;
}, [untilt]); }, [untilt]);
const handleMouseMove = throttle((e: React.MouseEvent) => { const handleTilt = (x: number, y: number) => {
const card = cardRef.current; const card = cardRef.current;
if (!card) return; if (!card) return;
const rect = card.getBoundingClientRect(); const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left; x -= rect.left;
const y = e.clientY - rect.top; y -= rect.top;
const centerX = rect.width / 2; const centerX = rect.width / 2;
const centerY = rect.height / 2; const centerY = rect.height / 2;
@@ -65,6 +66,27 @@ export default function TiltCard({
}; };
setLocalTilt(newTilt); setLocalTilt(newTilt);
};
const handleMouseMove = throttle((e: React.MouseEvent) => {
handleTilt(e.clientX, e.clientY);
}, thirtyFPS);
const handleTouchMove = throttle((e: React.TouchEvent) => {
const card = cardRef.current;
const touch = e.touches[0];
if (card && touch) {
const rect = card.getBoundingClientRect();
const x = touch.clientX;
const y = touch.clientY;
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
handleTilt(x, y);
} else {
setLocalTilt([]);
}
}
}, thirtyFPS); }, thirtyFPS);
const handleMouseLeave = () => { const handleMouseLeave = () => {
@@ -75,6 +97,8 @@ export default function TiltCard({
<div <div
className={`group ${className}`} className={`group ${className}`}
onMouseMove={settings.tilt ? handleMouseMove : undefined} onMouseMove={settings.tilt ? handleMouseMove : undefined}
onTouchMove={settings.tilt ? handleTouchMove : undefined}
onTouchEnd={handleMouseLeave}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
> >
<div <div

View File

@@ -1,5 +1,5 @@
import { log, validTilt } from '@/tools'; import { validTilt } from '@/tools';
import { GameUpdate, Tilt } from '@/types'; import { GameUpdate, Settings, Tilt } from '@/types';
const combineTilts = (tilts: Tilt[]) => const combineTilts = (tilts: Tilt[]) =>
tilts.reduce( tilts.reduce(
@@ -13,13 +13,15 @@ const combineTilts = (tilts: Tilt[]) =>
{ pX: 0, pY: 0, rX: 0, rY: 0, count: 0 }, { pX: 0, pY: 0, rX: 0, rY: 0, count: 0 },
); );
export function reduceTilts(gameData: GameUpdate, localTilt: Tilt[]): Tilt[] { export function reduceTilts(
gameData: GameUpdate,
localTilt: Tilt[],
{ tilt, remoteTilt }: Settings,
): Tilt[] {
const remoteTilts = gameData.tilts; const remoteTilts = gameData.tilts;
const tiltEnabled = gameData.settings.tilt;
const remoteTiltEnabled = gameData.settings.remoteTilt;
if (!tiltEnabled) return []; if (!tilt) return [];
if (!remoteTiltEnabled) return Array.from({ length: 5 }, (_, i) => localTilt[i]); if (!remoteTilt) return localTilt;
return Array.from({ length: 5 }, (_, i) => (localTilt[i] ? [localTilt[i]] : [])) return Array.from({ length: 5 }, (_, i) => (localTilt[i] ? [localTilt[i]] : []))
.map((cardTilts, cardIndex) => [...remoteTilts[cardIndex], ...cardTilts]) .map((cardTilts, cardIndex) => [...remoteTilts[cardIndex], ...cardTilts])

View File

@@ -1,4 +1,9 @@
import { Tilt } from '@/types'; import { Tilt } from '@/types';
export const validTilt = ({ percentX, percentY, rotateX, rotateY }: Tilt) => export const validTilt = (tilt: Tilt) => {
percentX >= 0 && percentY >= 0 && !!rotateX && !!rotateY; if (!tilt) return false;
const { percentX, percentY, rotateX, rotateY } = tilt;
return percentX >= 0 && percentY >= 0 && !!rotateX && !!rotateY;
};