new theme
This commit is contained in:
parent
11754f060e
commit
0ed91dc36d
13 changed files with 130 additions and 51 deletions
|
|
@ -1,10 +1,11 @@
|
|||
# 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:
|
||||
|
||||
- Typescript
|
||||
- React
|
||||
- Vite
|
||||
|
|
@ -21,8 +22,6 @@ When two players have joined the same session they can start the game:
|
|||
Game play:
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
Backend: https://github.com/pb-coding/skyjo-be
|
||||
|
|
|
|||
|
|
@ -7,7 +7,12 @@
|
|||
rel="stylesheet"
|
||||
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" />
|
||||
<title>Play Skylo!</title>
|
||||
</head>
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -32,7 +32,7 @@
|
|||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.30",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.30",
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
|
|
|
|||
43
src/components/CardFanAnimation.tsx
Normal file
43
src/components/CardFanAnimation.tsx
Normal 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;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { FC } from "react";
|
||||
|
||||
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 = () => (
|
||||
<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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { socket } from "../socket";
|
|||
|
||||
import { ConnectedIndicator, DisconnectedIndicator } from "./Indicators";
|
||||
import Button from "../global/Button";
|
||||
import CardFanAnimation from "./CardFanAnimation";
|
||||
|
||||
type SessionManagerProps = {
|
||||
isConnected: boolean;
|
||||
|
|
@ -48,12 +49,12 @@ export const SessionManager: FC<SessionManagerProps> = ({
|
|||
|
||||
return (
|
||||
<section>
|
||||
<div className="w-full h-full absolute top-0 left-0 z-0 bg-teal-500">
|
||||
<div className="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">
|
||||
Skylo
|
||||
<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="font-theme py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 z-10 relative">
|
||||
<h1 className="drop-shadow-black mb-4 text-7xl font-extrabold tracking-tight leading-none md:text-8xl lg:text-8xl text-white">
|
||||
SKYLO
|
||||
</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!
|
||||
</p>
|
||||
<div className="p-4">
|
||||
|
|
@ -61,13 +62,13 @@ export const SessionManager: FC<SessionManagerProps> = ({
|
|||
<form onSubmit={joinSession}>
|
||||
<label
|
||||
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
|
||||
</label>
|
||||
<div className="flex space-x-1 items-center">
|
||||
<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"
|
||||
required
|
||||
onChange={(e) => setSessionField(e.target.value)}
|
||||
|
|
@ -78,23 +79,44 @@ export const SessionManager: FC<SessionManagerProps> = ({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-gray-200 text-3xl my-2">Session: {session}</p>
|
||||
<p className="text-gray-200 text-3xl my-4">
|
||||
Players: {clientsInRoom}
|
||||
</p>
|
||||
<br />
|
||||
{showStartGameButton && (
|
||||
<Button onClick={startGame}>Start Game</Button>
|
||||
)}
|
||||
{isActiveSession && (
|
||||
<Button variant="secondary" onClick={() => leaveSession(session)}>
|
||||
Leave Session
|
||||
</Button>
|
||||
)}
|
||||
<div className="mt-8 flex justify-center items-center">
|
||||
<span className="text-gray-200">Game Server: </span>
|
||||
<div className="mx-4 py-4 bg-teal-200 border border-black rounded-lg">
|
||||
{isActiveSession && (
|
||||
<p className="text-theme-font text-3xl drop-shadow-white my-2">
|
||||
Session: {session}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-theme-font text-3xl drop-shadow-white mt-4">
|
||||
Players: {clientsInRoom}
|
||||
</p>
|
||||
<br />
|
||||
{showStartGameButton && (
|
||||
<Button onClick={startGame}>Start Game</Button>
|
||||
)}
|
||||
{isActiveSession && (
|
||||
<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 />}
|
||||
</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>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ const VoiceChat: FC<VoiceChatProps> = ({ session }) => {
|
|||
{/*<audio ref={localAudioRef} autoPlay muted></audio>*/}
|
||||
<audio ref={remoteAudioRef} autoPlay></audio>
|
||||
<button onClick={toggleAudio}>
|
||||
<HeadsetIcon />
|
||||
<HeadsetIcon enabled={isAudioEnabled} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const Button: FC<ButtonProps> = ({ variant, children, ...rest }) => {
|
|||
if (variant === "secondary") {
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
{children}
|
||||
|
|
@ -18,7 +18,7 @@ const Button: FC<ButtonProps> = ({ variant, children, ...rest }) => {
|
|||
}
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ type RoundChipProps = {
|
|||
|
||||
const RoundChip: FC<RoundChipProps> = ({ description, children }) => {
|
||||
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}
|
||||
<span className="sr-only">{description}</span>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { FC } from "react";
|
||||
|
||||
const HeadsetIcon: FC = () => {
|
||||
type HeadsetIconProps = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
const HeadsetIcon: FC<HeadsetIconProps> = ({ enabled }) => {
|
||||
return (
|
||||
<svg
|
||||
className="w-6 h-6 text-white"
|
||||
className="w-4 h-4 pt-0.5 text-white"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
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 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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
import colors from "tailwindcss/colors";
|
||||
|
||||
import color from "tailwindcss/colors";
|
||||
|
||||
export default {
|
||||
content: [
|
||||
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./node_modules/flowbite-react/**/*.js",
|
||||
],
|
||||
theme: {
|
||||
|
|
@ -18,18 +21,21 @@ export default {
|
|||
95: "0.95",
|
||||
},
|
||||
colors: {
|
||||
transparent: "transparent",
|
||||
current: "currentColor",
|
||||
black: colors.black,
|
||||
white: colors.white,
|
||||
emerald: colors.emerald,
|
||||
indigo: colors.indigo,
|
||||
yellow: colors.yellow,
|
||||
stone: colors.stone,
|
||||
sky: colors.sky,
|
||||
neutral: colors.neutral,
|
||||
gray: colors.gray,
|
||||
slate: colors.slate,
|
||||
"theme-bg": color.teal[400],
|
||||
"theme-primary": color.green[800],
|
||||
"theme-primary-hover": color.green[700],
|
||||
"theme-secondary": color.red[400],
|
||||
"theme-secondary-hover": color.red[300],
|
||||
"theme-tertiary": color.teal[200],
|
||||
"theme-font": color.gray[800],
|
||||
"theme-accent": color.yellow[500],
|
||||
},
|
||||
fontFamily: {
|
||||
theme: ["Alegreya", "serif"],
|
||||
},
|
||||
dropShadow: {
|
||||
white: "2px 2px 2px rgba(255, 255, 255, 1)",
|
||||
black: "2px 2px 2px rgba(0, 0, 0, 1)",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue