feat: suppression boutique legacy + refonte achievements milestones Clickerz
- Suppression route /boutique + Boutique.jsx, BoutiqueCard.jsx, shop.json, scss associés (le GeneratorShop sidebar fait déjà le job) - Refonte complète achievements : 27 milestones basés sur le GameState réel (paliers ressources, générateurs, prestige, évolution, easter eggs humour) - Suppression ancien système JSON statique + AchievementsCard legacy - Page achievements : unlocked/locked state-aware, compteur progression
This commit is contained in:
@@ -1,28 +0,0 @@
|
||||
import "../scss/components/achievementscard.scss";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
function AchievementsCard({ name, description, image, key }) {
|
||||
AchievementsCard.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
key: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="achievCardcontainer">
|
||||
<img
|
||||
className="achievecardpicture"
|
||||
key={key}
|
||||
src={image}
|
||||
alt="cartes speciales"
|
||||
/>
|
||||
<div className="achievetitle">
|
||||
<p className="achievname">{name}</p>
|
||||
<p className="achievdescription">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AchievementsCard;
|
||||
@@ -1,65 +0,0 @@
|
||||
// BoutiqueCard.jsx — Legacy shop card (shop.json boosters)
|
||||
// TODO: Migrate to economy.ts generator system in a future step
|
||||
import "../scss/components/boutiquecard.scss";
|
||||
import "../scss/components/buttons.scss";
|
||||
import PropTypes from "prop-types";
|
||||
import { useGameStore } from "../store/useGameStore";
|
||||
|
||||
export default function BoutiqueCard({
|
||||
name,
|
||||
price,
|
||||
incrementValue,
|
||||
description,
|
||||
image,
|
||||
type,
|
||||
}) {
|
||||
const resources = useGameStore((s) => s.state.resources);
|
||||
|
||||
// Legacy shop — disabled for now, generators are in GeneratorShop
|
||||
const canAfford = resources >= price;
|
||||
|
||||
return (
|
||||
<div className="shopcardcontainer">
|
||||
<div className="shopcontainer">
|
||||
<div
|
||||
className="cardpicture"
|
||||
style={{ backgroundImage: `url(${image})` }}
|
||||
alt={`image de ${name}`}
|
||||
/>
|
||||
<div>
|
||||
<div className="titlesection">
|
||||
<p className="itemname">{name}</p>
|
||||
<div className="price">
|
||||
<p className="itemprice">{price}</p>
|
||||
<div className="priceicon" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="description">
|
||||
<p className="itemdesc">
|
||||
<em>
|
||||
{type} + {incrementValue}
|
||||
</em>
|
||||
</p>
|
||||
<p className="itemdesc">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
disabled={!canAfford}
|
||||
className="primary-button"
|
||||
style={{ opacity: canAfford ? 1 : 0.5 }}
|
||||
>
|
||||
Bientôt
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
BoutiqueCard.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
price: PropTypes.number.isRequired,
|
||||
incrementValue: PropTypes.number.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
image: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -1,308 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Véritable cactus en peluche",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/n0tuti.jpg"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Brosse à dents sans poil",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/hd42tk.jpg"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Un jour, Dark Vador s’est attaqué à Chuck Norris. Depuis, il fait de l’asthme."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Photo d'Ayoub",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/fpanvh.jpg"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Pourquoi on met une selle sur un cheval ? Parce qu'en dessous, elle tomberait."
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Parapluie invisible",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/b8ms4o.jpg"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Qu'est-ce qui est plus merveilleux que de faire tourner un enfant sur un tourniquet ? L'arrêter avec une pelle."
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "Savon qui gratte",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/oeefev.jpg"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Pourquoi les moutons aiment le chewing-gum ? Car c'est bon pour la laine."
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Moule à glaçons géant",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/2sdm53.png"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Quand Chuck Norris fait un programme, il installe les modules, code et vend le programme... ensuite il demande à quoi il doit servir."
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "Casquette avec ventilateur intégré",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/wjhe5i.jpg"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Chuck Norris a invité Albert Einstein...à son dîner de cons."
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "Game boy color de tonton",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/w82iwu.jpg"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Vous savez pourquoi les pets puent ? Pour que les sourds en profitent !"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"name": "Mug moustache",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/csu9z1.jpg"
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Qu'est ce qui est jaune et qui n'attend pas ? Un citron pressé"
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Peau de grenouille rare",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/uwjwn1.jpg"
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Que met un développeur sur sa voiture en hiver ? Une bash."
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"name": "Stickers Gnia gnia gnia 5 minutes la présentation",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/8jids3.png"
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Comment une blonde fait-elle pour faire un double de ses clefs ? Elle les photocopie."
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"name": "Photo de ta mamie...",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/htqzwa.jpg"
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Qu'est-ce qu'un cochon volant ? Un aéroport."
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"name": "Livre de names de Tonton",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/avr4b4.png"
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Mon grand-père avait prédit que le Titanic coulerait, il l'avait répété maintes fois...Mais on a préféré le virer de la salle de cinéma"
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"name": "Oreiller qui ronfle",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/4ydby9.png"
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Un jour, les Power Rangers ont combattu Chuck Norris. Depuis, on les appelle les Teletubbies."
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"name": "Haut-parleur de douche non étanche",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/ugqwdj.jpg"
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Qu'est-ce qui a deux pattes et qui saigne ? Un demi-chien..."
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"name": "Kit de survie du marais au wasabi et moutarde forte",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/dakyj9.png"
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Sur quel site internet peut-on trouver un lave-vaisselle pas cher ? Meetic."
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"name": "Lunettes de soleil pour joueur de valorant",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/dxjicl.png"
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Chuck Norris mine de la crypto-monnaie...avec la calculette de sa montre Casio"
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"name": "Parfum au PHP: aucune odeur",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/o2435t.png"
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Quand Google ne trouve pas quelque chose, Il demande à Chuck Norris."
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"name": "Couronne de nénuphars lumineuse",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/d4su7e.png"
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Quelles sont les choses les plus lourdes de l'univers ? Soleil, Étoiles, Trou noir...et node_modules..."
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"name": "Kit de survie pour la fin du monde",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/ltcik6.png"
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Quelle est la fée la plus paresseuse ? La fée Néante"
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"name": "Bougie parfumée à l'essence de pizza",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/a3hv8n.jpg"
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Que mettre dans un kit de survie pour la fin du monde ? Du chocolat, des cookies et un DVD de !"
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"name": "Le livre mein... craft",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/fuy8kq.png"
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Un jour Hulk s’est battu contre Chuck Norris. Depuis, il fait de la pub pour du maïs."
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Pourquoi les chaussettes ont-elles des orteils séparés ? Parce que même les pieds ont besoin d'intimité !"
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "J'ai dit non ! Mon gynécologue m'a dit pas de sexe pendant 3 semaines. Et que t'as dit ton dentiste ?"
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"name": "Chaussettes avec orteils séparés",
|
||||
"founded": false,
|
||||
"image": "https://i.goopics.net/vn3xht.png"
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"name": "Blague:",
|
||||
"founded": false,
|
||||
"image": "https://images.pexels.com/photos/1115680/pexels-photo-1115680.jpeg",
|
||||
"description": "Quel est le jeu préféré des Portugais ?Call of d'outils"
|
||||
}
|
||||
]
|
||||
216
Frontend/src/data/achievements.ts
Normal file
216
Frontend/src/data/achievements.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
// achievements.ts — Milestones Clickerz basés sur le GameState réel
|
||||
import { GameState } from "../core/economy";
|
||||
|
||||
export interface Achievement {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
check: (state: GameState) => boolean;
|
||||
}
|
||||
|
||||
function totalGeneratorsOwned(state: GameState): number {
|
||||
return state.generators.reduce((sum, g) => sum + g.owned, 0);
|
||||
}
|
||||
|
||||
function hasGenerator(state: GameState, genId: string): boolean {
|
||||
return state.generators.some((g) => g.id === genId && g.owned > 0);
|
||||
}
|
||||
|
||||
function hasEvolutionNode(state: GameState, nodeId: string): boolean {
|
||||
return state.evolutionTree.some((n) => n.id === nodeId && n.unlocked);
|
||||
}
|
||||
|
||||
export const ACHIEVEMENTS: Achievement[] = [
|
||||
// --- Paliers de ressources ---
|
||||
{
|
||||
id: "first_tadpole",
|
||||
name: "Premier Têtard",
|
||||
description: "Faire éclore son tout premier têtard.",
|
||||
icon: "🥚",
|
||||
check: (s) => s.resources >= 1 || s.lifetimeTadpoles >= 1,
|
||||
},
|
||||
{
|
||||
id: "colony",
|
||||
name: "Colonie",
|
||||
description: "Atteindre 100 têtards.",
|
||||
icon: "🌱",
|
||||
check: (s) => s.resources >= 100 || s.lifetimeTadpoles >= 100,
|
||||
},
|
||||
{
|
||||
id: "village",
|
||||
name: "Village",
|
||||
description: "Atteindre 1 000 têtards.",
|
||||
icon: "🏘️",
|
||||
check: (s) => s.resources >= 1_000 || s.lifetimeTadpoles >= 1_000,
|
||||
},
|
||||
{
|
||||
id: "city",
|
||||
name: "Cité",
|
||||
description: "Atteindre 10 000 têtards.",
|
||||
icon: "🏙️",
|
||||
check: (s) => s.resources >= 10_000 || s.lifetimeTadpoles >= 10_000,
|
||||
},
|
||||
{
|
||||
id: "metropolis",
|
||||
name: "Métropole",
|
||||
description: "Atteindre 100 000 têtards.",
|
||||
icon: "🌆",
|
||||
check: (s) => s.resources >= 100_000 || s.lifetimeTadpoles >= 100_000,
|
||||
},
|
||||
{
|
||||
id: "empire",
|
||||
name: "Empire du Marais",
|
||||
description: "Atteindre 1 000 000 de têtards.",
|
||||
icon: "👑",
|
||||
check: (s) => s.resources >= 1_000_000 || s.lifetimeTadpoles >= 1_000_000,
|
||||
},
|
||||
|
||||
// --- Générateurs ---
|
||||
{
|
||||
id: "first_nid",
|
||||
name: "Nidificateur",
|
||||
description: "Construire son premier Nid.",
|
||||
icon: "🪹",
|
||||
check: (s) => hasGenerator(s, "nid"),
|
||||
},
|
||||
{
|
||||
id: "first_mare",
|
||||
name: "Batracien",
|
||||
description: "Aménager sa première Mare.",
|
||||
icon: "💧",
|
||||
check: (s) => hasGenerator(s, "mare"),
|
||||
},
|
||||
{
|
||||
id: "first_marecage",
|
||||
name: "Marécageux",
|
||||
description: "S'enfoncer dans son premier Marécage.",
|
||||
icon: "🌿",
|
||||
check: (s) => hasGenerator(s, "marecage"),
|
||||
},
|
||||
{
|
||||
id: "first_etang",
|
||||
name: "Gardien de l'Étang",
|
||||
description: "Découvrir un Étang Ancien.",
|
||||
icon: "🏛️",
|
||||
check: (s) => hasGenerator(s, "etang"),
|
||||
},
|
||||
{
|
||||
id: "first_lac",
|
||||
name: "Seigneur du Lac",
|
||||
description: "Accéder au Lac Mystique.",
|
||||
icon: "🔮",
|
||||
check: (s) => hasGenerator(s, "lac"),
|
||||
},
|
||||
{
|
||||
id: "industriel",
|
||||
name: "Industriel",
|
||||
description: "Posséder 10 générateurs au total.",
|
||||
icon: "🏭",
|
||||
check: (s) => totalGeneratorsOwned(s) >= 10,
|
||||
},
|
||||
{
|
||||
id: "magnate",
|
||||
name: "Magnate",
|
||||
description: "Posséder 50 générateurs au total.",
|
||||
icon: "💎",
|
||||
check: (s) => totalGeneratorsOwned(s) >= 50,
|
||||
},
|
||||
{
|
||||
id: "tycoon",
|
||||
name: "Tycoon du Marais",
|
||||
description: "Posséder 100 générateurs au total.",
|
||||
icon: "🐸",
|
||||
check: (s) => totalGeneratorsOwned(s) >= 100,
|
||||
},
|
||||
|
||||
// --- Prestige ---
|
||||
{
|
||||
id: "first_prestige",
|
||||
name: "Nouvelle Génération",
|
||||
description: "Effectuer son premier prestige.",
|
||||
icon: "🧬",
|
||||
check: (s) => s.prestigeCount >= 1,
|
||||
},
|
||||
{
|
||||
id: "veteran",
|
||||
name: "Vétéran",
|
||||
description: "Atteindre 5 prestiges.",
|
||||
icon: "⭐",
|
||||
check: (s) => s.prestigeCount >= 5,
|
||||
},
|
||||
{
|
||||
id: "legend",
|
||||
name: "Légende du Marais",
|
||||
description: "Atteindre 10 prestiges.",
|
||||
icon: "🏆",
|
||||
check: (s) => s.prestigeCount >= 10,
|
||||
},
|
||||
|
||||
// --- ADN & Évolution ---
|
||||
{
|
||||
id: "first_dna",
|
||||
name: "ADN Ancestral",
|
||||
description: "Accumuler son premier ADN.",
|
||||
icon: "🧪",
|
||||
check: (s) => s.ancestralDna >= 1,
|
||||
},
|
||||
{
|
||||
id: "first_evolution",
|
||||
name: "Première Mutation",
|
||||
description: "Débloquer la Ponte Améliorée.",
|
||||
icon: "🦎",
|
||||
check: (s) => hasEvolutionNode(s, "ponte_amelioree"),
|
||||
},
|
||||
{
|
||||
id: "full_tree",
|
||||
name: "Évolution Complète",
|
||||
description: "Débloquer tous les noeuds de l'arbre.",
|
||||
icon: "🌳",
|
||||
check: (s) => s.evolutionTree.every((n) => n.unlocked),
|
||||
},
|
||||
|
||||
// --- Easter eggs & humour ---
|
||||
{
|
||||
id: "chuck_norris",
|
||||
name: "Blague",
|
||||
description: "Quand Chuck Norris fait un programme, il installe les modules, code et vend le programme... ensuite il demande à quoi il doit servir.",
|
||||
icon: "🤜",
|
||||
check: (s) => s.lifetimeTadpoles >= 42,
|
||||
},
|
||||
{
|
||||
id: "patience",
|
||||
name: "Patience de Têtard",
|
||||
description: "Un têtard ne devient pas grenouille en un jour. Toi non plus visiblement.",
|
||||
icon: "🐌",
|
||||
check: (s) => hasGenerator(s, "nid") && s.resources < 50,
|
||||
},
|
||||
{
|
||||
id: "brain_powered",
|
||||
name: "Powered by Brain",
|
||||
description: "Le Brain a codé ce succès avant de savoir pourquoi. Classique.",
|
||||
icon: "🧠",
|
||||
check: (s) => s.prestigeCount >= 1 && totalGeneratorsOwned(s) >= 10,
|
||||
},
|
||||
{
|
||||
id: "marecage_addict",
|
||||
name: "Addict au Marécage",
|
||||
description: "T'as 10 marécages. Tu sens un peu la vase mais on respecte l'engagement.",
|
||||
icon: "🫠",
|
||||
check: (s) => s.generators.find((g) => g.id === "marecage")?.owned >= 10,
|
||||
},
|
||||
{
|
||||
id: "overkill",
|
||||
name: "Overkill",
|
||||
description: "25 Nids. T'aurais pu investir dans un Lac, mais non.",
|
||||
icon: "😅",
|
||||
check: (s) => s.generators.find((g) => g.id === "nid")?.owned >= 25,
|
||||
},
|
||||
{
|
||||
id: "symbiose_joke",
|
||||
name: "Le Cercle de la Vie",
|
||||
description: "Symbiose activée. Même Mufasa serait fier.",
|
||||
icon: "🦁",
|
||||
check: (s) => hasEvolutionNode(s, "symbiose"),
|
||||
},
|
||||
];
|
||||
@@ -1,92 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "Griffes de Grenouille",
|
||||
"price": 15,
|
||||
"incrementValue": 1,
|
||||
"description": "Des griffes acérées pour une ponte plus efficace. +1 par clic.",
|
||||
"link": "/",
|
||||
"image": "./svg/Hand.svg",
|
||||
"buyed": false,
|
||||
"type": "actif"
|
||||
},
|
||||
{
|
||||
"name": "Algues Nutritives",
|
||||
"price": 15,
|
||||
"incrementValue": 1,
|
||||
"description": "Les algues nourrissent le marais en continu. +1 têtard/s.",
|
||||
"link": "/",
|
||||
"image": "./svg/Tasse.svg",
|
||||
"buyed": false,
|
||||
"type": "passif"
|
||||
},
|
||||
{
|
||||
"name": "Crapaud Gardien",
|
||||
"price": 150,
|
||||
"incrementValue": 10,
|
||||
"description": "Un ancien du marais qui veille sur les pontes. +10 par clic.",
|
||||
"link": "/",
|
||||
"image": "./svg/Bonhome.svg",
|
||||
"buyed": false,
|
||||
"type": "actif"
|
||||
},
|
||||
{
|
||||
"name": "Nénuphar Géant",
|
||||
"price": 150,
|
||||
"incrementValue": 10,
|
||||
"description": "Un nénuphar massif qui attire les têtards. +10 têtards/s.",
|
||||
"link": "/",
|
||||
"image": "./svg/Bonnet.svg",
|
||||
"buyed": false,
|
||||
"type": "passif"
|
||||
},
|
||||
{
|
||||
"name": "Oeuf Doré",
|
||||
"price": 1500,
|
||||
"incrementValue": 100,
|
||||
"description": "Un oeuf rare qui éclot en masse. +100 par clic.",
|
||||
"link": "/",
|
||||
"image": "./svg/Cookie.svg",
|
||||
"buyed": false,
|
||||
"type": "actif"
|
||||
},
|
||||
{
|
||||
"name": "Mousse Lumineuse",
|
||||
"price": 1500,
|
||||
"incrementValue": 100,
|
||||
"description": "La mousse phosphorescente accélère la croissance. +100 têtards/s.",
|
||||
"link": "/",
|
||||
"image": "./svg/Canne.svg",
|
||||
"buyed": false,
|
||||
"type": "passif"
|
||||
},
|
||||
{
|
||||
"name": "Couronne de Roseaux",
|
||||
"price": 15000,
|
||||
"incrementValue": 1000,
|
||||
"description": "Le symbole du Gardien suprême du Marais. +1000 par clic.",
|
||||
"link": "/",
|
||||
"image": "./svg/Courone.svg",
|
||||
"buyed": false,
|
||||
"type": "actif"
|
||||
},
|
||||
{
|
||||
"name": "Esprit du Marais",
|
||||
"price": 15000,
|
||||
"incrementValue": 1000,
|
||||
"description": "L'esprit ancestral bénit les eaux. +1000 têtards/s.",
|
||||
"link": "/",
|
||||
"image": "./svg/PainDep.svg",
|
||||
"buyed": false,
|
||||
"type": "passif"
|
||||
},
|
||||
{
|
||||
"name": "Nectar de Lotus",
|
||||
"price": 8000,
|
||||
"incrementValue": 1000,
|
||||
"description": "Un nectar enivrant qui trouble les eaux... mais booste la ponte. Attention aux effets secondaires.",
|
||||
"link": "/",
|
||||
"image": "./svg/Beer.svg",
|
||||
"buyed": false,
|
||||
"type": "actif"
|
||||
}
|
||||
]
|
||||
@@ -7,7 +7,6 @@ import ErrorPage from "./pages/404";
|
||||
import Login from "./pages/Login";
|
||||
import AuthCallback from "./pages/AuthCallback";
|
||||
import { AuthProvider } from "./context/AuthContext";
|
||||
import Boutique from "./pages/Boutique";
|
||||
import Achievements from "./pages/Achievements";
|
||||
import Legal from "./pages/Legal";
|
||||
import Cookie from "./pages/Cookie";
|
||||
@@ -25,11 +24,7 @@ const router = createBrowserRouter([
|
||||
path: "/jeu",
|
||||
element: <Home />,
|
||||
},
|
||||
{
|
||||
path: "/boutique",
|
||||
element: <Boutique />,
|
||||
},
|
||||
{
|
||||
{
|
||||
path: "/achievements",
|
||||
element: <Achievements />,
|
||||
},
|
||||
|
||||
@@ -1,33 +1,41 @@
|
||||
import { useState } from "react";
|
||||
import AchievementsCard from "../components/AchievementsCard";
|
||||
import "../scss/achievements.scss";
|
||||
import { useGameStore } from "../store/useGameStore";
|
||||
import achievements from "../data/Achievements.json";
|
||||
import { ACHIEVEMENTS } from "../data/achievements";
|
||||
import "../scss/achievements.scss";
|
||||
|
||||
function Achievements() {
|
||||
const resources = useGameStore((s) => s.state.resources);
|
||||
let score = 1;
|
||||
if (resources >= 25) {
|
||||
score = Math.floor((resources - 25) / 400) + 1;
|
||||
} else {
|
||||
score = 0;
|
||||
}
|
||||
const state = useGameStore((s) => s.state);
|
||||
|
||||
const unlocked = ACHIEVEMENTS.filter((a) => a.check(state));
|
||||
const locked = ACHIEVEMENTS.filter((a) => !a.check(state));
|
||||
|
||||
return (
|
||||
<div className="fullachieve">
|
||||
<h1>Succès</h1>
|
||||
<p className="achieve-counter">
|
||||
{unlocked.length} / {ACHIEVEMENTS.length}
|
||||
</p>
|
||||
|
||||
<div className="achievementscontainer">
|
||||
<div className="achievementscardcontainer">
|
||||
{achievements &&
|
||||
achievements.slice(0, score).map((a) => {
|
||||
return (
|
||||
<AchievementsCard
|
||||
key={a.id}
|
||||
name={a.name}
|
||||
description={a.description}
|
||||
image={a.image}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{unlocked.map((a) => (
|
||||
<div key={a.id} className="achieve-card achieve-unlocked">
|
||||
<span className="achieve-icon">{a.icon}</span>
|
||||
<div className="achieve-info">
|
||||
<p className="achieve-name">{a.name}</p>
|
||||
<p className="achieve-desc">{a.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{locked.map((a) => (
|
||||
<div key={a.id} className="achieve-card achieve-locked">
|
||||
<span className="achieve-icon">🔒</span>
|
||||
<div className="achieve-info">
|
||||
<p className="achieve-name">{a.name}</p>
|
||||
<p className="achieve-desc">???</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import BoutiqueCard from "../components/BoutiqueCard";
|
||||
import "../scss/shop.scss";
|
||||
import shop from "../data/shop";
|
||||
|
||||
export default function Boutique() {
|
||||
|
||||
|
||||
return (
|
||||
<main>
|
||||
<div className="shoppagecontainer">
|
||||
<h1>Boutique</h1>
|
||||
<div className="cardcontainer">
|
||||
{shop.map((item) => {
|
||||
return (
|
||||
<BoutiqueCard
|
||||
key={item.name}
|
||||
name={item.name}
|
||||
price={item.price}
|
||||
incrementValue={item.incrementValue}
|
||||
description={item.description}
|
||||
image={item.image}
|
||||
link={item.link}
|
||||
type={item.type}
|
||||
buyed={item.buyed}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +1,98 @@
|
||||
.fullachieve {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 10rem;
|
||||
padding-top: 6rem;
|
||||
padding-bottom: 3rem;
|
||||
background-color: var(--color-blue-light);
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
min-height: 80vh;
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-family: var(--font);
|
||||
font-size: 2.5rem;
|
||||
color: var(--color-grey);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.achieve-counter {
|
||||
text-align: center;
|
||||
font-family: var(--font);
|
||||
font-size: 1.1rem;
|
||||
color: var(--color-grey);
|
||||
opacity: 0.7;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.achievementscontainer {
|
||||
margin: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-family: var(--font);
|
||||
font-size: 3rem;
|
||||
color: var(--color-grey);
|
||||
margin-bottom: 3rem;
|
||||
width: 100%;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.achievementscardcontainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
min-height: 300px;
|
||||
gap: 3rem;
|
||||
min-height: 200px;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.achieve-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.2rem;
|
||||
border-radius: 0.75rem;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
transition: transform 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
.achieve-unlocked {
|
||||
background: rgba(16, 185, 129, 0.12);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.achieve-locked {
|
||||
background: rgba(107, 114, 128, 0.08);
|
||||
border: 1px solid rgba(107, 114, 128, 0.15);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.achieve-icon {
|
||||
font-size: 2rem;
|
||||
flex-shrink: 0;
|
||||
width: 3rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.achieve-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.achieve-name {
|
||||
font-family: var(--font);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-grey);
|
||||
}
|
||||
|
||||
.achieve-desc {
|
||||
font-family: var(--font);
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-grey);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
.achievCardcontainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border: solid 0.05rem;
|
||||
max-width: 250px;
|
||||
border-radius: 1rem;
|
||||
background-color: rgb(255, 255, 255);
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 1px 1px 10px 2px var(--color-grey);
|
||||
|
||||
}
|
||||
|
||||
.achievecardpicture {
|
||||
width: 100%;
|
||||
border-radius: 1rem 1rem 0 0;
|
||||
}
|
||||
|
||||
.achievname {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0.2rem;
|
||||
font-family: var(--font);
|
||||
text-align: center;
|
||||
color:rgb(29, 30, 30);
|
||||
}
|
||||
.achievdescription {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-grey);
|
||||
font-family: var(--font);
|
||||
color:rgb(25, 25, 26);
|
||||
padding: 1rem;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
.shopcardcontainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
min-height: 520px;
|
||||
padding: 1rem;
|
||||
border-radius: 1rem;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--color-white);
|
||||
|
||||
font-family: var(--font);
|
||||
|
||||
.shopcontainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
|
||||
.cardpicture {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
padding: 3rem;
|
||||
background-size: 50%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 10px;
|
||||
background-color: var(--color-purple-light);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.titlesection {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-top: 1rem;
|
||||
|
||||
.itemname {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-grey);
|
||||
}
|
||||
|
||||
.price {
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-grey);
|
||||
.itemprice {
|
||||
font-weight: 600;
|
||||
color: var(--color-red-light);
|
||||
}
|
||||
.priceicon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background-image: url("/svg/tadpole.svg");
|
||||
background-size: contain;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-grey);
|
||||
}
|
||||
}
|
||||
.buttoncard {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
|
||||
.shoppagecontainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
padding: 12rem 0 4rem;
|
||||
|
||||
h1 {
|
||||
font-family: var(--font);
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-grey);
|
||||
}
|
||||
.cardcontainer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
max-width: 1280px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user