feat(sprint1-step6): polish, landing page, responsive, deploy config
- Landing.jsx : écran d'accueil "Entrer dans le Marais" sur / - Home.jsx : jeu sur /jeu, click animation float-up, sidebar responsive - formatNumber.ts : util partagé k/M/B/T (remplace 4 copies locales) - home.scss : rewrite classes (game-cover, click-zone, tadpole-sprite, game-sidebar) - Responsive : sidebar fixe desktop, drawer bottom mobile (<768px) - navbar : wildCoin → resource-counter, auth-nav stylé, dead code supprimé - GameSync.tsx : bridge useSaveSync ↔ Zustand (câblé dans App) - tadpole.svg : asset renommé (SantaClause-bag → tadpole) - deploy/ : Apache vhost SPA+proxy, deploy.sh, ecosystem.config.cjs PM2 - NavBarData : Jeu → /jeu - Cleanup : dead imports, commentaires legacy
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import BoutiqueCard from "../components/BoutiqueCard";
|
||||
// import { useWildCoin } from "..components/WildCoin/WildCoinContext";
|
||||
import "../scss/shop.scss";
|
||||
import shop from "../data/shop";
|
||||
|
||||
|
||||
@@ -1,231 +1,131 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useOutletContext } from "react-router-dom";
|
||||
import PropTypes from "prop-types";
|
||||
import "../scss/home.scss";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useCallback } from "react";
|
||||
|
||||
import { useWildCoin } from "../components/WildCoin/WildCoinContext";
|
||||
import { useGameStore } from "../store/useGameStore";
|
||||
import { formatNumber } from "../utils/formatNumber";
|
||||
import { GeneratorShop } from "../components/GeneratorShop";
|
||||
import { PrestigePanel } from "../components/PrestigePanel";
|
||||
import { EvolutionTree } from "../components/EvolutionTree";
|
||||
import { MilestoneBar } from "../components/MilestoneBar";
|
||||
import "../scss/home.scss";
|
||||
|
||||
export default function Home() {
|
||||
const [toggleSnow, setToggleSnow] = useOutletContext();
|
||||
Home.propTypes = {
|
||||
setToggleSnow: PropTypes.function,
|
||||
toggleSnow: PropTypes.bool,
|
||||
}.isRequired;
|
||||
|
||||
const { biere, setBiere, santaDrunk, setSantaDrunk } = useWildCoin();
|
||||
|
||||
var snow = {
|
||||
wind: 0,
|
||||
maxXrange: 40,
|
||||
minXrange: 20,
|
||||
maxSpeed: 1,
|
||||
minSpeed: 3,
|
||||
color: "#fff",
|
||||
char: "*",
|
||||
maxSize: 32,
|
||||
minSize: 10,
|
||||
|
||||
flakes: [],
|
||||
WIDTH: -10,
|
||||
HEIGHT: 0,
|
||||
|
||||
init: function (nb) {
|
||||
var o = this,
|
||||
frag = document.createDocumentFragment();
|
||||
o.getSize();
|
||||
|
||||
for (var i = 0; i < nb; i++) {
|
||||
var flake = {
|
||||
x: o.random(o.WIDTH),
|
||||
y: -o.maxSize,
|
||||
xrange: o.minXrange + o.random(o.maxXrange - o.minXrange),
|
||||
yspeed: o.minSpeed + o.random(o.maxSpeed - o.minSpeed, 100),
|
||||
life: 0,
|
||||
size: o.minSize + o.random(o.maxSize - o.minSize),
|
||||
html: document.createElement("span"),
|
||||
};
|
||||
|
||||
flake.html.style.position = "absolute";
|
||||
flake.html.style.top = flake.y + "px";
|
||||
flake.html.style.left = flake.x + "px";
|
||||
flake.html.style.fontSize = flake.size + "px";
|
||||
flake.html.style.color = o.color;
|
||||
flake.html.appendChild(document.createTextNode(o.char));
|
||||
frag.appendChild(flake.html);
|
||||
flake.html.style.userSelect = "none";
|
||||
flake.html.style.overflow = "hidden";
|
||||
o.flakes.push(flake);
|
||||
}
|
||||
|
||||
document.body.appendChild(frag);
|
||||
o.animate();
|
||||
|
||||
window.onresize = function () {
|
||||
o.getSize();
|
||||
};
|
||||
},
|
||||
|
||||
animate: function () {
|
||||
var o = this;
|
||||
for (var i = 0, c = o.flakes.length; i < c; i++) {
|
||||
var flake = o.flakes[i],
|
||||
top = flake.y + flake.yspeed,
|
||||
left = flake.x + Math.sin(flake.life) * flake.xrange + o.wind;
|
||||
if (
|
||||
top < o.HEIGHT - flake.size - 10 &&
|
||||
left < o.WIDTH - flake.size &&
|
||||
left > 0
|
||||
) {
|
||||
flake.html.style.top = top + "px";
|
||||
flake.html.style.left = left + "px";
|
||||
flake.y = top;
|
||||
flake.x += o.wind;
|
||||
flake.life += 0.01;
|
||||
} else {
|
||||
flake.html.style.top = -o.maxSize + "px";
|
||||
flake.x = o.random(o.WIDTH);
|
||||
flake.y = -o.maxSize;
|
||||
flake.html.style.left = flake.x + "px";
|
||||
flake.life = 0;
|
||||
}
|
||||
}
|
||||
setTimeout(function () {
|
||||
o.animate();
|
||||
}, 20);
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
for (var i = 0, c = this.flakes.length; i < c; i++) {
|
||||
document.body.removeChild(this.flakes[i].html);
|
||||
}
|
||||
this.flakes = [];
|
||||
},
|
||||
|
||||
random: function (range, num) {
|
||||
num = num ? num : 1;
|
||||
return Math.floor(Math.random() * (range + 1) * num) / num;
|
||||
},
|
||||
|
||||
getSize: function () {
|
||||
this.WIDTH = document.body.clientWidth || window.innerWidth;
|
||||
this.HEIGHT = document.body.clientHeight || window.innerHeight;
|
||||
},
|
||||
};
|
||||
|
||||
const { incrementClick, incrementWildCoin } = useWildCoin();
|
||||
|
||||
const createParticle = (x, y) => {
|
||||
const cookieClicks = document.querySelector(".pieces");
|
||||
|
||||
const particle = document.createElement("a");
|
||||
particle.style.backgroundImage = "url('/png/w-coin.png')";
|
||||
particle.setAttribute("class", "pieces-particle");
|
||||
particle.style.left = x + "%";
|
||||
particle.style.bottom = y + "px";
|
||||
|
||||
cookieClicks.appendChild(particle);
|
||||
const [toggleRain] = useOutletContext();
|
||||
const click = useGameStore((s) => s.click);
|
||||
const resources = useGameStore((s) => s.state.resources);
|
||||
const clickMultiplier = useGameStore((s) => s.state.clickMultiplier);
|
||||
|
||||
const createParticle = useCallback((clientX, clientY) => {
|
||||
const particle = document.createElement("span");
|
||||
particle.className = "click-particle";
|
||||
particle.textContent = `+${formatNumber(clickMultiplier)}`;
|
||||
particle.style.left = `${clientX}px`;
|
||||
particle.style.top = `${clientY}px`;
|
||||
document.body.appendChild(particle);
|
||||
setTimeout(() => {
|
||||
cookieClicks.removeChild(particle);
|
||||
}, 1500);
|
||||
};
|
||||
if (particle.parentNode) particle.parentNode.removeChild(particle);
|
||||
}, 800);
|
||||
}, [clickMultiplier]);
|
||||
|
||||
const handleIncrement = () => {
|
||||
incrementWildCoin(incrementClick);
|
||||
createParticle(50, 300);
|
||||
};
|
||||
const handleIncrement = useCallback((e) => {
|
||||
click();
|
||||
createParticle(e.clientX, e.clientY);
|
||||
}, [click, createParticle]);
|
||||
|
||||
// Rain effect (ambiance)
|
||||
useEffect(() => {
|
||||
if (toggleSnow) {
|
||||
snow.init(10);
|
||||
} else {
|
||||
snow.stop();
|
||||
}
|
||||
|
||||
return () => {
|
||||
snow.stop();
|
||||
};
|
||||
}, [toggleSnow]);
|
||||
|
||||
useEffect(() => {
|
||||
const main = document.querySelector(".bghomecover");
|
||||
const santa = document.querySelector(".santaclaus");
|
||||
if (main !== undefined) {
|
||||
if (biere[1] >= 1) {
|
||||
santa.style.background = `url("/svg/SantaClause-drink.svg")`;
|
||||
if (santaDrunk === true) {
|
||||
main.style.filter = `blur(${biere[1]}px)`;
|
||||
|
||||
setTimeout(() => {
|
||||
console.count("setTimeOut");
|
||||
main.style.filter = `blur(0px)`;
|
||||
setSantaDrunk(false);
|
||||
}, biere[1] * 5000);
|
||||
const rain = {
|
||||
wind: 0, maxXrange: 40, minXrange: 20, maxSpeed: 1, minSpeed: 3,
|
||||
color: "#8ecae6", char: "~", maxSize: 32, minSize: 10,
|
||||
flakes: [], WIDTH: -10, HEIGHT: 0, running: false,
|
||||
init(nb) {
|
||||
const frag = document.createDocumentFragment();
|
||||
this.getSize();
|
||||
this.running = true;
|
||||
for (let i = 0; i < nb; i++) {
|
||||
const flake = {
|
||||
x: this.random(this.WIDTH), y: -this.maxSize,
|
||||
xrange: this.minXrange + this.random(this.maxXrange - this.minXrange),
|
||||
yspeed: this.minSpeed + this.random(this.maxSpeed - this.minSpeed, 100),
|
||||
life: 0, size: this.minSize + this.random(this.maxSize - this.minSize),
|
||||
html: document.createElement("span"),
|
||||
};
|
||||
Object.assign(flake.html.style, {
|
||||
position: "absolute", top: `${flake.y}px`, left: `${flake.x}px`,
|
||||
fontSize: `${flake.size}px`, color: this.color, userSelect: "none", overflow: "hidden",
|
||||
});
|
||||
flake.html.appendChild(document.createTextNode(this.char));
|
||||
frag.appendChild(flake.html);
|
||||
this.flakes.push(flake);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [biere, setBiere]);
|
||||
document.body.appendChild(frag);
|
||||
this.animate();
|
||||
window.onresize = () => this.getSize();
|
||||
},
|
||||
animate() {
|
||||
if (!this.running) return;
|
||||
for (const flake of this.flakes) {
|
||||
const top = flake.y + flake.yspeed;
|
||||
const left = flake.x + Math.sin(flake.life) * flake.xrange + this.wind;
|
||||
if (top < this.HEIGHT - flake.size - 10 && left < this.WIDTH - flake.size && left > 0) {
|
||||
flake.html.style.top = `${top}px`;
|
||||
flake.html.style.left = `${left}px`;
|
||||
flake.y = top;
|
||||
flake.x += this.wind;
|
||||
flake.life += 0.01;
|
||||
} else {
|
||||
flake.html.style.top = `${-this.maxSize}px`;
|
||||
flake.x = this.random(this.WIDTH);
|
||||
flake.y = -this.maxSize;
|
||||
flake.html.style.left = `${flake.x}px`;
|
||||
flake.life = 0;
|
||||
}
|
||||
}
|
||||
setTimeout(() => this.animate(), 20);
|
||||
},
|
||||
stop() {
|
||||
this.running = false;
|
||||
for (const flake of this.flakes) {
|
||||
if (flake.html.parentNode) flake.html.parentNode.removeChild(flake.html);
|
||||
}
|
||||
this.flakes = [];
|
||||
},
|
||||
random(range, num = 1) {
|
||||
return Math.floor(Math.random() * (range + 1) * num) / num;
|
||||
},
|
||||
getSize() {
|
||||
this.WIDTH = document.body.clientWidth || window.innerWidth;
|
||||
this.HEIGHT = document.body.clientHeight || window.innerHeight;
|
||||
},
|
||||
};
|
||||
|
||||
if (toggleRain) rain.init(10);
|
||||
return () => rain.stop();
|
||||
}, [toggleRain]);
|
||||
|
||||
return (
|
||||
<main className="bghomecover">
|
||||
<main className="game-cover">
|
||||
<Helmet>
|
||||
<meta
|
||||
name="description"
|
||||
content="Xmass Click votre nouveau Clicker préféré !"
|
||||
/>
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta
|
||||
name="googlebot"
|
||||
content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
|
||||
/>
|
||||
<meta
|
||||
name="bingbot"
|
||||
content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
|
||||
/>
|
||||
<link rel="canonical" href="https://xmass.click/" />
|
||||
<meta property="og:url" content="https://xmass.click/" />
|
||||
<meta property="og:site_name" content="Xmass Click" />
|
||||
<meta property="og:locale" content="fr_FR" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="mywebsite | title" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Xmass Click votre nouveau Clicker préféré !"
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://xmass.click/webp/share-cover.webp"
|
||||
/>
|
||||
<meta
|
||||
property="og:image:secure_url"
|
||||
content="https://xmass.click/webp/share-cover.webp"
|
||||
/>
|
||||
<meta property="og:image:width" content="584" />
|
||||
<meta property="og:image:height" content="384" />
|
||||
<meta property="fb:pages" content="" />
|
||||
<meta property="fb:admins" content="" />
|
||||
<meta property="fb:app_id" content="" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:site" content="" />
|
||||
<meta name="twitter:creator" content="" />
|
||||
<meta name="twitter:title" content="Xmass Click" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Xmass Click votre nouveau Clicker préféré !"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://xmass.click/webp/share-cover.webp"
|
||||
/>
|
||||
|
||||
<title>Xmass Click</title>
|
||||
<meta name="description" content="Clickerz — Clicker idle dans le Tetard Universe." />
|
||||
<title>Clickerz — Tetard Universe</title>
|
||||
</Helmet>
|
||||
<div className="santaposition">
|
||||
<div className="pieces" />
|
||||
<div className="santaclaus" onClick={handleIncrement} />
|
||||
|
||||
{/* Clicker area — centre */}
|
||||
<div className="click-zone" onClick={handleIncrement}>
|
||||
<div className="tadpole-sprite" />
|
||||
<div className="text-center text-3xl md:text-4xl font-bold text-white drop-shadow-lg font-[var(--font)] select-none pointer-events-none">
|
||||
{formatNumber(resources)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="boostList"></div>
|
||||
|
||||
{/* Game panels — sidebar (right desktop, bottom mobile) */}
|
||||
<aside className="game-sidebar">
|
||||
<MilestoneBar />
|
||||
<GeneratorShop />
|
||||
<PrestigePanel />
|
||||
<EvolutionTree />
|
||||
</aside>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
37
Frontend/src/pages/Landing.jsx
Normal file
37
Frontend/src/pages/Landing.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export default function Landing() {
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Clickerz — Tetard Universe</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Clickerz — Clicker idle dans le Tetard Universe. Fais éclore des têtards, évolue et domine le marais !"
|
||||
/>
|
||||
</Helmet>
|
||||
<main className="min-h-[92vh] mt-20 flex flex-col items-center justify-center gap-8 bg-[var(--bg-color)]">
|
||||
<div className="flex flex-col items-center gap-4 text-center px-4">
|
||||
<h1 className="text-4xl md:text-6xl font-bold text-gray-800 font-[var(--font)]">
|
||||
Clickerz
|
||||
</h1>
|
||||
<p className="text-lg md:text-xl text-gray-600 font-[var(--font)] max-w-md">
|
||||
Fais éclore des têtards, construis ton empire et domine le marais.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
to="/jeu"
|
||||
className="px-8 py-4 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-lg font-semibold font-[var(--font)] transition-all hover:scale-105 shadow-lg shadow-emerald-600/30"
|
||||
>
|
||||
Entrer dans le Marais
|
||||
</Link>
|
||||
|
||||
<p className="text-sm text-gray-500 font-[var(--font)]">
|
||||
Pas de compte requis — joue en mode invité
|
||||
</p>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user