Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

OpenMC est un projet communautaire open-source dédié à la création d'un serveur Minecraft innovant et collaboratif.

**Ceci est le code source de son site web.**

<a href="https://github.com/ServerOpenMC/Website/graphs/contributors">
<img src="https://contrib.rocks/image?repo=ServerOpenMC/Website" alt="Contributeurs" />
</a>
Expand All @@ -17,9 +19,8 @@ OpenMC est un projet communautaire open-source dédié à la création d'un serv
```
Si des erreurs ou des warnings apparaissent, corrigez-les.
5. Une fois une fonctionnalité implémentée et fonctionnelle, créez une pull request.
6. Après approbation, la fonctionnalité sera disponible sur le serveur de développement pour des tests.
6. Après approbation, la fonctionnalité sera disponible sur le site [openmc.fr](openmc.fr).

- **IP du serveur** : `play.openmc.fr` (Minecraft Java Edition 26.2)
- **Site web** : [openmc.fr](https://openmc.fr)

## 🚧 Démarrage
Expand Down
11 changes: 6 additions & 5 deletions app/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ export default function ChangelogPage() {
Commits récents
</CardTitle>
<CardDescription className="text-muted-foreground">
Les dernières modifications du code source
Les dernières modifications du code source du plugin
</CardDescription>
</CardHeader>
<CardContent>
Expand Down Expand Up @@ -748,10 +748,10 @@ export default function ChangelogPage() {
transition={{ duration: 0.6, delay: 0.6 }}
className="text-center mt-12 pb-8"
>
<Card className="bg-card/30 border-border backdrop-blur-sm">
<CardContent className="pt-6">
<Card className="w-fit max-w-full mx-auto gap-0 p-0 bg-card/30 border-border backdrop-blur-sm">
<CardContent className="px-4 py-5 text-center">
<p className="text-muted-foreground mb-2">
Données récupérées depuis le repository{" "}
Le repository du plugin{" "}
<a
href="https://github.com/ServerOpenMC/PluginV2"
target="_blank"
Expand All @@ -762,11 +762,12 @@ export default function ChangelogPage() {
</a>
</p>
<p className="text-sm text-muted-foreground/70">
Mis à jour automatiquement • Développé avec Next.js et shadcn/ui
Mis à jour automatiquement via le dépôt Github
</p>
</CardContent>
</Card>
</motion.div>

</div>
</div>
);
Expand Down
113 changes: 113 additions & 0 deletions app/dance/backgroundaudio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { Pause, Play, Volume2, VolumeX } from "lucide-react";

type BackgroundAudioProps = {
onStateChange: (isPlaying: boolean) => void;
};

export default function BackgroundAudio({
onStateChange,
}: BackgroundAudioProps) {
const audioRef = useRef<HTMLAudioElement | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [volume, setVolume] = useState(0.4);

const audioSrc = `./songs/jonasblakewood-dance-pop.mp3`;

const setPlaying = (playing: boolean) => {
setIsPlaying(playing);
onStateChange(playing);
};

const play = async () => {
try {
await audioRef.current?.play();
setPlaying(true);
} catch {}
}

const togglePlay = () => {
const audio = audioRef.current;
if (!audio) return;

if (audio.paused) {
play();
} else {
audio.pause();
setPlaying(false);
}
}

const toggleMute = () => {
setVolume(volume === 0 ? 0.4 : 0);
}

useEffect(() => {
if (audioRef.current) {
audioRef.current.volume = volume;
}
}, [volume]);

useEffect(() => {
const handleInteraction = () => {
play();
window.removeEventListener("pointerdown", handleInteraction);
window.removeEventListener("keydown", handleInteraction);
};

play();

window.addEventListener("pointerdown", handleInteraction);
window.addEventListener("keydown", handleInteraction);

return () => {
window.removeEventListener("pointerdown", handleInteraction);
window.removeEventListener("keydown", handleInteraction);
};
}, []);

return (
<>
<audio ref={audioRef} src={audioSrc} loop preload="auto" />

<div className="fixed bottom-20 right-4 z-50 flex items-center gap-3 bg-background/80 backdrop-blur-md border border-border p-2 px-4 rounded-full shadow-lg text-foreground text-sm">

<button
onClick={togglePlay}
className="inline-flex items-center gap-2 hover:text-primary transition-colors focus:outline-none"
title={isPlaying ? "Mettre en pause" : "Lancer la musique"}
aria-label={isPlaying ? "Mettre en pause" : "Lancer la musique"}
>
{isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
<span>{isPlaying ? "Pause" : "Jouer"}</span>
</button>

<span className="text-border">|</span>

<div className="flex items-center gap-2">
<button
onClick={toggleMute}
className="hover:text-primary transition-colors focus:outline-none"
title={volume === 0 ? "Activer le son" : "Coupure du son"}
aria-label={volume === 0 ? "Activer le son" : "Coupure du son"}
>
{volume === 0 ? <VolumeX className="h-4 w-4" /> : <Volume2 className="h-4 w-4" />}
</button>

<input
type="range"
min={0}
max={1}
step={0.05}
value={volume}
onChange={(e) => setVolume(parseFloat(e.target.value))}
className="w-16 h-1 bg-muted rounded-lg appearance-none cursor-pointer accent-primary"
title="Ajuster le volume"
/>
</div>
</div>
</>
);
}
96 changes: 96 additions & 0 deletions app/dance/dance-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"use client";

import Image from "next/image";
import { useCallback, useEffect, useRef, useState } from "react";
import BackgroundAudio from "./backgroundaudio";

type GitHubUser = {
id: number;
login: string;
avatar_url: string;
html_url: string;
};


type DanceContentProps = {
contributors: GitHubUser[];
};

export default function DanceContent({ contributors }: DanceContentProps) {
const [isPlaying, setIsPlaying] = useState(false);
const [isStopping, setIsStopping] = useState(false);
const stopTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

useEffect(() => {
return () => {
if (stopTimeoutRef.current) {
clearTimeout(stopTimeoutRef.current);
}
};
}, []);

const handleAudioStateChange = useCallback((playing: boolean) => {
setIsPlaying(playing);

if (stopTimeoutRef.current) {
clearTimeout(stopTimeoutRef.current);
stopTimeoutRef.current = null;
}

if (playing) {
setIsStopping(false);
return;
}

setIsStopping(true);
stopTimeoutRef.current = setTimeout(() => {
setIsStopping(false);
stopTimeoutRef.current = null;
}, 1200);
}, []);

return (
<>
<BackgroundAudio onStateChange={handleAudioStateChange} />

<div className="flex flex-wrap justify-center gap-6 max-w-4xl">
{contributors.map((user, index) => {
const delay = `${(index % 5) * 0.25}s`;

return (
<a
key={user.id}
href={user.html_url}
target="_blank"
rel="noopener noreferrer"
className="flex flex-col items-center group cursor-pointer"
>
<div
className={`relative w-20 h-20 transition-transform ${
isPlaying
? "animate-dance"
: isStopping
? "animate-dance dance-stopping"
: ""
}`}
style={{ animationDelay: delay }}
>
<Image
src={user.avatar_url}
alt={user.login}
width={80}
height={80}
className="rounded-full border-4 border-primary object-cover"
unoptimized
/>
</div>
<span className="text-xs mt-2 font-mono text-muted-foreground group-hover:text-primary transition-colors">
{user.login}
</span>
</a>
);
})}
</div>
</>
);
}
86 changes: 86 additions & 0 deletions app/dance/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";

import { useEffect, useState } from "react";
import Image from "next/image";
import BackgroundAudio from "./backgroundaudio";
import { GitHubApi, type GitHubContributor } from "@/lib/github-cache";

export default function Dance() {
const [contributors, setContributors] = useState<GitHubContributor[]>([]);
const [isPlaying, setIsPlaying] = useState(false);
const [loading, setLoading] = useState(true);

useEffect(() => {
async function fetchAllContributors() {
try {
const repos = ["PluginV2", "Plugin", "Website"];
const requests = repos.map((repo) =>
GitHubApi.getContributors("ServerOpenMC", repo).catch(() => [])
);

const rawResults = await Promise.all(requests);
const allContributors = rawResults.flat();
const uniqueContributors = Array.from(
new Map(allContributors.map((user) => [user.id, user])).values()
);

setContributors(uniqueContributors);
} catch (error) {
console.error("Erreur lors de la récupération des contributeurs:", error);
} finally {
setLoading(false);
}
}

fetchAllContributors();
}, []);

return (
<main className="pt-28 pb-12 px-4 text-center min-h-screen bg-background text-foreground flex flex-col items-center justify-center overflow-hidden">
<h1 className="text-3xl font-bold text-primary mb-8">
Les contributeurs dansent (easter egg sympa xD) !
</h1>

<BackgroundAudio onStateChange={setIsPlaying} />

{loading ? (
<p className="text-muted-foreground animate-pulse">Chargement des contributeurs...</p>
) : (
<div className="flex flex-wrap justify-center gap-6 max-w-4xl">
{contributors.map((user, index) => {
const delay = `${(index % 5) * 0.15}s`;

return (
<a
key={user.id}
href={user.html_url}
target="_blank"
rel="noopener noreferrer"
className="flex flex-col items-center group cursor-pointer"
>
<div
className={`relative w-20 h-20 transition-transform ${
isPlaying ? "animate-dance" : ""
}`}
style={{ animationDelay: delay }}
>
<Image
src={user.avatar_url}
alt={user.login}
width={80}
height={80}
className="rounded-full border-4 border-primary object-cover"
unoptimized
/>
</div>
<span className="text-xs mt-2 font-mono text-muted-foreground group-hover:text-primary transition-colors">
{user.login}
</span>
</a>
);
})}
</div>
)}
</main>
);
}
22 changes: 22 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,25 @@
.animate-shimmer {
animation: shimmer 2s infinite;
}

@keyframes dance {
0%, 100% {
transform: translate3d(0, 0, 0) rotate(-8deg);
}
50% {
transform: translate3d(0, -16px, 0) rotate(8deg);
}
}

.animate-dance {
animation: dance 0.6s infinite ease-in-out;
will-change: transform;
backface-visibility: hidden;
}

.dance-stopping {
animation-duration: 1.2s;
animation-delay: 0s;
animation-iteration-count: 1;
animation-timing-function: ease-out;
}
Loading