new theme

This commit is contained in:
pb-coding 2023-10-19 15:30:33 +02:00
parent 11754f060e
commit 0ed91dc36d
13 changed files with 130 additions and 51 deletions

View file

@ -1,10 +1,11 @@
# Skylo Frontend # Skylo Frontend
Skylo is a digital card game, which has some similarities to the popular card game Skyjo ;) This is a tribute to the creator of my favorite game, Skyjo! Please purchase the original game to discover the true gaming experience: https://www.magilano.com/produkt/skyjo/
Play latest release here: https://skyjo.voltvector.org/ Play latest release here: https://skylo-game.com
This is the frontend of this web based game using: This is the frontend of this web based game using:
- Typescript - Typescript
- React - React
- Vite - Vite
@ -21,8 +22,6 @@ When two players have joined the same session they can start the game:
Game play: Game play:
![image](https://github.com/pb-coding/skyjo-fe/assets/71174645/73111d78-3e7e-4916-9b9d-4db6d8fee317) ![image](https://github.com/pb-coding/skyjo-fe/assets/71174645/73111d78-3e7e-4916-9b9d-4db6d8fee317)
![image](https://github.com/pb-coding/skyjo-fe/assets/71174645/717cff3d-0d4b-4fcd-845d-ef52c934090f) ![image](https://github.com/pb-coding/skyjo-fe/assets/71174645/717cff3d-0d4b-4fcd-845d-ef52c934090f)
Backend: https://github.com/pb-coding/skyjo-be Backend: https://github.com/pb-coding/skyjo-be

View file

@ -7,7 +7,12 @@
rel="stylesheet" rel="stylesheet"
as="style" as="style"
/> />
<link rel="icon" type="image/svg+xml" href="/card-back.svg" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Alegreya:wght@400;700&display=swap"
rel="stylesheet"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Play Skylo!</title> <title>Play Skylo!</title>
</head> </head>

2
package-lock.json generated
View file

@ -32,7 +32,7 @@
"eslint": "^8.45.0", "eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3", "eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.30", "postcss": "^8.4.31",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.4.5", "vite": "^4.4.5",

View file

@ -34,7 +34,7 @@
"eslint": "^8.45.0", "eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3", "eslint-plugin-react-refresh": "^0.4.3",
"postcss": "^8.4.30", "postcss": "^8.4.31",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"typescript": "^5.0.2", "typescript": "^5.0.2",
"vite": "^4.4.5", "vite": "^4.4.5",

View file

View file

@ -0,0 +1,43 @@
import { FC, useRef, useEffect } from "react";
const CardFanAnimation: FC = () => {
const cardContainerRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const container = cardContainerRef.current;
if (!container) return;
const cards = Array.from(container.children) as HTMLImageElement[];
const angle = -30;
const angleIncrement = 20;
cards.forEach((card, index) => {
const calculatedAngle = angle + index * angleIncrement;
const rotation = `rotate(${calculatedAngle}deg)`;
const translateX = `translate(${index * -40 + 40}px)`;
const translateY = `translateY(${
Math.abs(calculatedAngle) * 3.5 - 20
}px)`;
card.style.transform = `${rotation} ${translateX} ${translateY}`;
});
}, []);
const imageClasses = "w-40 transition-transform duration-1000";
return (
<div
ref={cardContainerRef}
className="fixed bottom-0 w-full flex justify-center space-x-1"
>
<img src="/textures/card-7.png" className={imageClasses} alt="Card 7" />
<img src="/textures/card-3.png" className={imageClasses} alt="Card 3" />
<img src="/textures/card-12.png" className={imageClasses} alt="Card 12" />
<img
src="/textures/card-minus2.png"
className={imageClasses}
alt="Card -2"
/>
</div>
);
};
export default CardFanAnimation;

View file

@ -1,9 +1,9 @@
import { FC } from "react"; import { FC } from "react";
export const ConnectedIndicator: FC = () => ( export const ConnectedIndicator: FC = () => (
<span className="ml-2 flex w-2.5 h-2.5 bg-green-500 rounded-full"></span> <span className="ml-2 flex w-2.5 h-2.5 bg-theme-primary rounded-full border border-black"></span>
); );
export const DisconnectedIndicator: FC = () => ( export const DisconnectedIndicator: FC = () => (
<span className="ml-2 flex w-2 h-2 bg-red-500 rounded-full"></span> <span className="ml-2 flex w-2 h-2 bg-theme-secondary rounded-full"></span>
); );

View file

@ -3,6 +3,7 @@ import { socket } from "../socket";
import { ConnectedIndicator, DisconnectedIndicator } from "./Indicators"; import { ConnectedIndicator, DisconnectedIndicator } from "./Indicators";
import Button from "../global/Button"; import Button from "../global/Button";
import CardFanAnimation from "./CardFanAnimation";
type SessionManagerProps = { type SessionManagerProps = {
isConnected: boolean; isConnected: boolean;
@ -48,12 +49,12 @@ export const SessionManager: FC<SessionManagerProps> = ({
return ( return (
<section> <section>
<div className="w-full h-full absolute top-0 left-0 z-0 bg-teal-500"> <div className="w-full h-full absolute top-0 left-0 z-0 bg-gradient-to-b from-theme-bg to-teal-500">
<div className="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 z-10 relative"> <div className="font-theme py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 z-10 relative">
<h1 className="mb-4 text-4xl font-extrabold tracking-tight leading-none md:text-5xl lg:text-6xl text-white"> <h1 className="drop-shadow-black mb-4 text-7xl font-extrabold tracking-tight leading-none md:text-8xl lg:text-8xl text-white">
Skylo SKYLO
</h1> </h1>
<p className="mb-8 text-lg font-normal lg:text-xl sm:px-16 lg:px-48 text-gray-200"> <p className="mb-8 text-lg font-bold lg:text-xl sm:px-16 lg:px-48 text-theme-primary">
Play Skylo online with your friends! Play Skylo online with your friends!
</p> </p>
<div className="p-4"> <div className="p-4">
@ -61,13 +62,13 @@ export const SessionManager: FC<SessionManagerProps> = ({
<form onSubmit={joinSession}> <form onSubmit={joinSession}>
<label <label
htmlFor="first_name" htmlFor="first_name"
className="block mb-2 text-sm font-medium text-white" className="block mb-2 text-md font-medium text-theme-font drop-shadow-white"
> >
Join Skylo Session Join Skylo Session
</label> </label>
<div className="flex space-x-1 items-center"> <div className="flex space-x-1 items-center">
<input <input
className="border text-sm rounded-lg block w-full p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" className="border text-sm rounded-lg block w-full p-2.5 bg-theme-tertiary border-black placeholder-gray-400 text-theme-font focus:ring-theme-primary focus:border-theme-primary"
placeholder="Session name" placeholder="Session name"
required required
onChange={(e) => setSessionField(e.target.value)} onChange={(e) => setSessionField(e.target.value)}
@ -78,23 +79,44 @@ export const SessionManager: FC<SessionManagerProps> = ({
)} )}
</div> </div>
<p className="text-gray-200 text-3xl my-2">Session: {session}</p> <div className="mx-4 py-4 bg-teal-200 border border-black rounded-lg">
<p className="text-gray-200 text-3xl my-4"> {isActiveSession && (
Players: {clientsInRoom} <p className="text-theme-font text-3xl drop-shadow-white my-2">
</p> Session: {session}
<br /> </p>
{showStartGameButton && ( )}
<Button onClick={startGame}>Start Game</Button> <p className="text-theme-font text-3xl drop-shadow-white mt-4">
)} Players: {clientsInRoom}
{isActiveSession && ( </p>
<Button variant="secondary" onClick={() => leaveSession(session)}> <br />
Leave Session {showStartGameButton && (
</Button> <Button onClick={startGame}>Start Game</Button>
)} )}
<div className="mt-8 flex justify-center items-center"> {isActiveSession && (
<span className="text-gray-200">Game Server: </span> <Button variant="secondary" onClick={() => leaveSession(session)}>
Leave Session
</Button>
)}
</div>
<div className="mt-4 flex justify-center items-center">
<span className="text-theme-font drop-shadow-white">
Game Server:{" "}
</span>
{isConnected ? <ConnectedIndicator /> : <DisconnectedIndicator />} {isConnected ? <ConnectedIndicator /> : <DisconnectedIndicator />}
</div> </div>
<div className="mt-4">
<p className="font-bold text-xs text-theme-font drop-shadow-white">
This is a tribute to the creator of my favorite game, Skyjo!
Please purchase the game to discover the true gaming experience:
</p>
<a
className="text-theme-primary text-sm font-extrabold"
href="https://www.magilano.com/produkt/skyjo/"
>
Buy Skyjo here
</a>
</div>
<CardFanAnimation />
</div> </div>
</div> </div>
</section> </section>

View file

@ -154,7 +154,7 @@ const VoiceChat: FC<VoiceChatProps> = ({ session }) => {
{/*<audio ref={localAudioRef} autoPlay muted></audio>*/} {/*<audio ref={localAudioRef} autoPlay muted></audio>*/}
<audio ref={remoteAudioRef} autoPlay></audio> <audio ref={remoteAudioRef} autoPlay></audio>
<button onClick={toggleAudio}> <button onClick={toggleAudio}>
<HeadsetIcon /> <HeadsetIcon enabled={isAudioEnabled} />
</button> </button>
</div> </div>
); );

View file

@ -9,7 +9,7 @@ const Button: FC<ButtonProps> = ({ variant, children, ...rest }) => {
if (variant === "secondary") { if (variant === "secondary") {
return ( return (
<button <button
className="text-white focus:outline-none focus:ring-4 font-medium rounded-lg text-sm px-3 py-2 mr-2 bg-gray-800 hover:bg-gray-700 focus:ring-gray-700 border-gray-700" className="border border-black text-white focus:outline-none focus:ring-4 font-medium rounded-lg text-sm px-3 py-2 mr-2 bg-theme-secondary hover:bg-theme-secondary-hover focus:ring-gray-700"
{...rest} {...rest}
> >
{children} {children}
@ -18,7 +18,7 @@ const Button: FC<ButtonProps> = ({ variant, children, ...rest }) => {
} }
return ( return (
<button <button
className="text-white focus:ring-4 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 bg-lime-700 hover:bg-blue-700 focus:outline-none focus:ring-blue-800" className="border border-black text-white focus:ring-4 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 bg-theme-primary hover:bg-theme-primary-hover focus:outline-none focus:ring-blue-800"
{...rest} {...rest}
> >
{children} {children}

View file

@ -7,7 +7,7 @@ type RoundChipProps = {
const RoundChip: FC<RoundChipProps> = ({ description, children }) => { const RoundChip: FC<RoundChipProps> = ({ description, children }) => {
return ( return (
<span className="inline-flex items-center justify-center w-12 h-12 ml-4 mt-4 text-sm font-semibold rounded-full bg-gray-800 hover:bg-gray-700 text-gray-300"> <span className="inline-flex items-center justify-center w-8 h-8 ml-4 mt-4 text-sm font-semibold rounded-full bg-theme-secondary hover:bg-theme-secondary-hover text-white">
{children} {children}
<span className="sr-only">{description}</span> <span className="sr-only">{description}</span>
</span> </span>

View file

@ -1,9 +1,13 @@
import { FC } from "react"; import { FC } from "react";
const HeadsetIcon: FC = () => { type HeadsetIconProps = {
enabled: boolean;
};
const HeadsetIcon: FC<HeadsetIconProps> = ({ enabled }) => {
return ( return (
<svg <svg
className="w-6 h-6 text-white" className="w-4 h-4 pt-0.5 text-white"
aria-hidden="true" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="currentColor" fill="currentColor"
@ -11,7 +15,7 @@ const HeadsetIcon: FC = () => {
> >
<path d="M 7.824 5.937 a 1 1 0 0 0 0.726 -0.312 a 2.042 2.042 0 0 1 2.835 -0.065 a 1 1 0 0 0 1.388 -1.441 a 3.994 3.994 0 0 0 -5.674 0.13 a 1 1 0 0 0 0.725 1.688 Z" /> <path d="M 7.824 5.937 a 1 1 0 0 0 0.726 -0.312 a 2.042 2.042 0 0 1 2.835 -0.065 a 1 1 0 0 0 1.388 -1.441 a 3.994 3.994 0 0 0 -5.674 0.13 a 1 1 0 0 0 0.725 1.688 Z" />
<path d="M 17 7 A 7 7 0 1 0 3 7 a 3 3 0 0 0 -3 3 v 2 a 3 3 0 0 0 3 3 h 1 a 1 1 0 0 0 1 -1 V 7 a 5 5 0 1 1 10 0 v 7.083 A 2.92 2.92 0 0 1 12.083 17 H 12 a 2 2 0 0 0 -2 -2 H 9 a 2 2 0 0 0 -2 2 v 1 a 2 2 0 0 0 2 2 h 1 a 1.993 1.993 0 0 0 1.722 -1 h 0.361 a 4.92 4.92 0 0 0 4.824 -4 H 17 a 3 3 0 0 0 3 -3 v -2 a 3 3 0 0 0 -3 -3 Z" /> <path d="M 17 7 A 7 7 0 1 0 3 7 a 3 3 0 0 0 -3 3 v 2 a 3 3 0 0 0 3 3 h 1 a 1 1 0 0 0 1 -1 V 7 a 5 5 0 1 1 10 0 v 7.083 A 2.92 2.92 0 0 1 12.083 17 H 12 a 2 2 0 0 0 -2 -2 H 9 a 2 2 0 0 0 -2 2 v 1 a 2 2 0 0 0 2 2 h 1 a 1.993 1.993 0 0 0 1.722 -1 h 0.361 a 4.92 4.92 0 0 0 4.824 -4 H 17 a 3 3 0 0 0 3 -3 v -2 a 3 3 0 0 0 -3 -3 Z" />
<path d="M 1 19 L 19 0 L 19 1 L 1 20 L 1 19" /> {!enabled && <path d="M 1 19 L 19 0 L 19 1 L 1 20 L 1 19" />}
</svg> </svg>
); );
}; };

View file

@ -1,10 +1,13 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
import colors from "tailwindcss/colors";
import color from "tailwindcss/colors";
export default { export default {
content: [ content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}", "./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}", "./app/**/*.{js,ts,jsx,tsx,mdx}",
"./src/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/flowbite-react/**/*.js", "./node_modules/flowbite-react/**/*.js",
], ],
theme: { theme: {
@ -18,18 +21,21 @@ export default {
95: "0.95", 95: "0.95",
}, },
colors: { colors: {
transparent: "transparent", "theme-bg": color.teal[400],
current: "currentColor", "theme-primary": color.green[800],
black: colors.black, "theme-primary-hover": color.green[700],
white: colors.white, "theme-secondary": color.red[400],
emerald: colors.emerald, "theme-secondary-hover": color.red[300],
indigo: colors.indigo, "theme-tertiary": color.teal[200],
yellow: colors.yellow, "theme-font": color.gray[800],
stone: colors.stone, "theme-accent": color.yellow[500],
sky: colors.sky, },
neutral: colors.neutral, fontFamily: {
gray: colors.gray, theme: ["Alegreya", "serif"],
slate: colors.slate, },
dropShadow: {
white: "2px 2px 2px rgba(255, 255, 255, 1)",
black: "2px 2px 2px rgba(0, 0, 0, 1)",
}, },
}, },
}, },