feat: first commit
This commit is contained in:
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="KubernetesApiProvider">{}</component>
|
||||||
|
</project>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 854 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 213 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 737 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,57 @@
|
|||||||
|
import { minecraftServer } from "@/app/lib/server-config";
|
||||||
|
|
||||||
|
type McSrvStatPlayer = string | { name?: string; name_raw?: string };
|
||||||
|
|
||||||
|
type McSrvStatResponse = {
|
||||||
|
online?: boolean;
|
||||||
|
version?: string;
|
||||||
|
players?: {
|
||||||
|
online?: number;
|
||||||
|
max?: number;
|
||||||
|
list?: McSrvStatPlayer[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.mcsrvstat.us/3/${minecraftServer.address}`,
|
||||||
|
{
|
||||||
|
next: { revalidate: 60 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Minecraft status API returned ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json()) as McSrvStatResponse;
|
||||||
|
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
online: Boolean(data.online),
|
||||||
|
playersOnline: data.players?.online ?? null,
|
||||||
|
playersMax: data.players?.max ?? null,
|
||||||
|
version: data.version ?? null,
|
||||||
|
checkedAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Cache-Control": "s-maxage=45, stale-while-revalidate=120",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
online: false,
|
||||||
|
playersOnline: null,
|
||||||
|
playersMax: null,
|
||||||
|
version: null,
|
||||||
|
checkedAt: new Date().toISOString(),
|
||||||
|
error: "Le statut du serveur est indisponible.",
|
||||||
|
},
|
||||||
|
{ status: 200 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
type CopyAddressButtonProps = {
|
||||||
|
value: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CopyAddressButton({
|
||||||
|
value,
|
||||||
|
className = "",
|
||||||
|
}: CopyAddressButtonProps) {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
async function copyAddress() {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(value);
|
||||||
|
setCopied(true);
|
||||||
|
window.setTimeout(() => setCopied(false), 1800);
|
||||||
|
} catch {
|
||||||
|
setCopied(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`copy-address-button ${copied ? "is-copied" : ""} ${className}`}
|
||||||
|
onClick={copyAddress}
|
||||||
|
aria-live="polite"
|
||||||
|
>
|
||||||
|
<CopyIcon />
|
||||||
|
<span>{copied ? "Adresse copiée" : "Copier l'adresse"}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CopyIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M8 7.5A2.5 2.5 0 0 1 10.5 5h6A2.5 2.5 0 0 1 19 7.5v6A2.5 2.5 0 0 1 16.5 16h-6A2.5 2.5 0 0 1 8 13.5z" />
|
||||||
|
<path d="M5 10.5A2.5 2.5 0 0 1 7.5 8H8v5.5A2.5 2.5 0 0 0 10.5 16H16v.5a2.5 2.5 0 0 1-2.5 2.5h-6A2.5 2.5 0 0 1 5 16.5z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
type HomeElementProps = {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
href: string;
|
||||||
|
actionLabel: string;
|
||||||
|
variant: "jellyfin" | "minecraft";
|
||||||
|
external?: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function HomeElement({
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
href,
|
||||||
|
actionLabel,
|
||||||
|
variant,
|
||||||
|
external = false,
|
||||||
|
children,
|
||||||
|
}: HomeElementProps) {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<span className="service-status" aria-label="Service en ligne" />
|
||||||
|
<span className="service-icon-shell">{children}</span>
|
||||||
|
<span className="service-copy">
|
||||||
|
<strong>{title}</strong>
|
||||||
|
<span>{description}</span>
|
||||||
|
</span>
|
||||||
|
<span className="service-action">
|
||||||
|
{actionLabel}
|
||||||
|
<ArrowIcon />
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const className = `service-card service-card--${variant}`;
|
||||||
|
|
||||||
|
if (external) {
|
||||||
|
return (
|
||||||
|
<a href={href} className={className} target="_blank" rel="noopener noreferrer">
|
||||||
|
{content}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href={href} className={className}>
|
||||||
|
{content}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JellyfinIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 96 96" aria-hidden="true" focusable="false">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="jellyfin-gradient" x1="16" y1="84" x2="80" y2="12">
|
||||||
|
<stop stopColor="#7c3cff" />
|
||||||
|
<stop offset="0.52" stopColor="#00d8ff" />
|
||||||
|
<stop offset="1" stopColor="#c833ff" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
d="M48 11 88 80H8z"
|
||||||
|
fill="none"
|
||||||
|
stroke="url(#jellyfin-gradient)"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="9"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M48 35 66 67H30z"
|
||||||
|
fill="none"
|
||||||
|
stroke="url(#jellyfin-gradient)"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="8"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MinecraftIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 96 96" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M48 8 84 28 48 48 12 28z" fill="#7bd452" />
|
||||||
|
<path d="M48 48 84 28v40L48 88z" fill="#6b4a2c" />
|
||||||
|
<path d="M48 48 12 28v40l36 20z" fill="#8a6238" />
|
||||||
|
<path d="M24 34h10v10H24zm19 9h11v11H43zm18-9h11v10H61z" fill="#5aa543" />
|
||||||
|
<path d="M56 58h9v10h-9zm-25 0h10v11H31zm14 14h9v10h-9z" fill="#4b3525" opacity=".7" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ArrowIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M5 12h12" />
|
||||||
|
<path d="m13 6 6 6-6 6" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type ServerStatus = {
|
||||||
|
online: boolean;
|
||||||
|
playersOnline: number | null;
|
||||||
|
playersMax: number | null;
|
||||||
|
version: string | null;
|
||||||
|
checkedAt: string;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadingStatus: ServerStatus = {
|
||||||
|
online: false,
|
||||||
|
playersOnline: null,
|
||||||
|
playersMax: null,
|
||||||
|
version: null,
|
||||||
|
checkedAt: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function MinecraftStatus() {
|
||||||
|
const [status, setStatus] = useState<ServerStatus>(loadingStatus);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let alive = true;
|
||||||
|
|
||||||
|
async function refreshStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/minecraft/status", {
|
||||||
|
cache: "no-store",
|
||||||
|
});
|
||||||
|
const payload = (await response.json()) as ServerStatus;
|
||||||
|
|
||||||
|
if (alive) {
|
||||||
|
setStatus(payload);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (alive) {
|
||||||
|
setStatus({
|
||||||
|
...loadingStatus,
|
||||||
|
checkedAt: new Date().toISOString(),
|
||||||
|
error: "Impossible de récupérer le statut.",
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshStatus();
|
||||||
|
const interval = window.setInterval(refreshStatus, 60_000);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
alive = false;
|
||||||
|
window.clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const playerCount =
|
||||||
|
status.playersOnline === null
|
||||||
|
? "-"
|
||||||
|
: `${status.playersOnline}${status.playersMax ? ` / ${status.playersMax}` : ""}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="statut" className="minecraft-section">
|
||||||
|
<div className="section-heading pixel-heading">
|
||||||
|
<h2>Statut du serveur</h2>
|
||||||
|
<p>Mis à jour automatiquement toutes les 60 secondes.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="status-panel block-panel">
|
||||||
|
<StatusItem
|
||||||
|
label="État"
|
||||||
|
value={loading ? "Vérification..." : status.online ? "En ligne" : "Hors ligne"}
|
||||||
|
tone={loading ? undefined : status.online ? "online" : "offline"}
|
||||||
|
/>
|
||||||
|
<StatusItem label="Joueurs en ligne" value={playerCount} />
|
||||||
|
<StatusItem label="Version" value={status.version ?? "-"} />
|
||||||
|
<StatusItem
|
||||||
|
label="Dernier check"
|
||||||
|
value={formatCheckedAt(status.checkedAt)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{status.error ? (
|
||||||
|
<p className="status-note">{status.error}</p>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusItemProps = {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
tone?: "online" | "offline";
|
||||||
|
};
|
||||||
|
|
||||||
|
function StatusItem({ label, value, tone }: StatusItemProps) {
|
||||||
|
return (
|
||||||
|
<div className="status-item">
|
||||||
|
<span className="status-label">{label}</span>
|
||||||
|
<strong className={tone ? `status-value status-value--${tone}` : "status-value"}>
|
||||||
|
{tone ? <span className="status-light" aria-hidden="true" /> : null}
|
||||||
|
{value}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCheckedAt(value: string) {
|
||||||
|
if (!value) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Intl.DateTimeFormat("fr-FR", {
|
||||||
|
hour: "2-digit",
|
||||||
|
minute: "2-digit",
|
||||||
|
}).format(new Date(value));
|
||||||
|
}
|
||||||
+973
-7
@@ -1,8 +1,21 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: #ffffff;
|
--background: #090a0f;
|
||||||
--foreground: #171717;
|
--foreground: #f8fbff;
|
||||||
|
--muted: #b4c0d5;
|
||||||
|
--surface: #141832;
|
||||||
|
--surface-strong: #1f2244;
|
||||||
|
--cyan: #00e7ff;
|
||||||
|
--violet: #8b4dff;
|
||||||
|
--pink: #ff4dd2;
|
||||||
|
--green: #33e879;
|
||||||
|
--gold: #ffd54a;
|
||||||
|
--stone: #252932;
|
||||||
|
--stone-light: #343a45;
|
||||||
|
--dirt: #5a3b22;
|
||||||
|
--paper: #f2efe3;
|
||||||
|
--shadow-hard: 6px 6px 0 rgb(0 0 0 / 55%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@@ -12,15 +25,968 @@
|
|||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-geist-mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
* {
|
||||||
:root {
|
box-sizing: border-box;
|
||||||
--background: #0a0a0a;
|
|
||||||
--foreground: #ededed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: var(--cyan);
|
||||||
|
color: #06111a;
|
||||||
|
}
|
||||||
|
|
||||||
|
*:focus-visible {
|
||||||
|
outline: 3px solid var(--gold);
|
||||||
|
outline-offset: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stellar-page {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at bottom, #1b2735 0%, #0d1021 48%, #05060c 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stellar-page::before,
|
||||||
|
.stellar-page::after {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
content: "";
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stellar-page::before {
|
||||||
|
background-image:
|
||||||
|
radial-gradient(circle, rgb(255 255 255 / 90%) 1px, transparent 1.5px),
|
||||||
|
radial-gradient(circle, rgb(0 231 255 / 65%) 1px, transparent 1.5px),
|
||||||
|
radial-gradient(circle, rgb(255 255 255 / 55%) 1.5px, transparent 2px);
|
||||||
|
background-position:
|
||||||
|
0 0,
|
||||||
|
34px 74px,
|
||||||
|
88px 12px;
|
||||||
|
background-size:
|
||||||
|
86px 86px,
|
||||||
|
142px 142px,
|
||||||
|
230px 230px;
|
||||||
|
animation: star-drift 120s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stellar-page::after {
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, transparent 0%, rgb(0 231 255 / 6%) 58%, rgb(51 232 121 / 10%) 100%),
|
||||||
|
radial-gradient(ellipse at 30% 80%, rgb(139 77 255 / 18%), transparent 38%),
|
||||||
|
radial-gradient(ellipse at 80% 70%, rgb(0 231 255 / 13%), transparent 32%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-shell {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
width: min(1120px, calc(100% - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 32px 0 22px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-nav {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-lockup,
|
||||||
|
.minecraft-brand {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-lockup {
|
||||||
|
color: #a8f5ff;
|
||||||
|
font-family: var(--font-fredoka), var(--font-geist-sans), sans-serif;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-shadow: 0 0 18px rgb(0 231 255 / 45%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
display: grid;
|
||||||
|
width: 54px;
|
||||||
|
height: 54px;
|
||||||
|
place-items: center;
|
||||||
|
border: 2px solid rgb(168 245 255 / 85%);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgb(7 20 36 / 72%);
|
||||||
|
box-shadow: 0 0 22px rgb(0 231 255 / 35%), inset 0 0 14px rgb(0 231 255 / 20%);
|
||||||
|
color: #a8f5ff;
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero {
|
||||||
|
max-width: 920px;
|
||||||
|
margin-top: 54px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-fredoka), var(--font-geist-sans), sans-serif;
|
||||||
|
font-size: 4.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 0.98;
|
||||||
|
text-shadow: 0 10px 0 rgb(0 0 0 / 32%), 0 0 26px rgb(255 255 255 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero p {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: 22px auto 0;
|
||||||
|
color: #c7d0e6;
|
||||||
|
font-family: var(--font-fredoka), var(--font-geist-sans), sans-serif;
|
||||||
|
font-size: 1.45rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-grid {
|
||||||
|
display: grid;
|
||||||
|
width: min(860px, 100%);
|
||||||
|
margin-top: 48px;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card {
|
||||||
|
--service-accent: var(--cyan);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
min-height: 314px;
|
||||||
|
padding: 32px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 3px solid var(--cyan);
|
||||||
|
border-radius: 8px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgb(20 24 50 / 88%), rgb(8 10 22 / 92%)),
|
||||||
|
radial-gradient(circle at 50% 20%, rgb(255 255 255 / 10%), transparent 34%);
|
||||||
|
box-shadow: var(--shadow-hard), inset 0 0 0 1px rgb(255 255 255 / 9%);
|
||||||
|
color: var(--foreground);
|
||||||
|
text-align: center;
|
||||||
|
transition: transform 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card::before {
|
||||||
|
position: absolute;
|
||||||
|
inset: 20px;
|
||||||
|
content: "";
|
||||||
|
background-image:
|
||||||
|
radial-gradient(circle, currentColor 1.5px, transparent 2px),
|
||||||
|
radial-gradient(circle, currentColor 1px, transparent 1.5px);
|
||||||
|
background-position:
|
||||||
|
0 0,
|
||||||
|
26px 34px;
|
||||||
|
background-size:
|
||||||
|
52px 52px,
|
||||||
|
74px 74px;
|
||||||
|
opacity: 0.18;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card:hover {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card--jellyfin {
|
||||||
|
--service-accent: var(--violet);
|
||||||
|
|
||||||
|
border-color: var(--violet);
|
||||||
|
box-shadow: var(--shadow-hard), 0 0 28px rgb(139 77 255 / 34%), inset 0 0 0 1px rgb(255 255 255 / 9%);
|
||||||
|
color: #ae77ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card--minecraft {
|
||||||
|
--service-accent: var(--green);
|
||||||
|
|
||||||
|
border-color: var(--green);
|
||||||
|
box-shadow: var(--shadow-hard), 0 0 28px rgb(51 232 121 / 28%), inset 0 0 0 1px rgb(255 255 255 / 9%);
|
||||||
|
color: #46ee89;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-status {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border: 3px solid rgb(3 17 18 / 72%);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--green);
|
||||||
|
box-shadow: 0 0 0 4px rgb(51 232 121 / 18%), 0 0 18px rgb(51 232 121 / 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-icon-shell {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
width: 116px;
|
||||||
|
height: 116px;
|
||||||
|
place-items: center;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-icon-shell svg {
|
||||||
|
width: 94px;
|
||||||
|
height: 94px;
|
||||||
|
filter: drop-shadow(0 8px 14px rgb(0 0 0 / 40%));
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-copy {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-copy::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 18%;
|
||||||
|
right: 18%;
|
||||||
|
height: 3px;
|
||||||
|
background: currentColor;
|
||||||
|
box-shadow: 0 0 12px currentColor;
|
||||||
|
content: "";
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-copy strong {
|
||||||
|
color: #fff;
|
||||||
|
font-family: var(--font-fredoka), var(--font-geist-sans), sans-serif;
|
||||||
|
font-size: 2.05rem;
|
||||||
|
line-height: 1;
|
||||||
|
text-shadow: 0 4px 0 rgb(0 0 0 / 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-copy span {
|
||||||
|
min-height: 48px;
|
||||||
|
color: #bdc8dd;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-action {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: 56px;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 24px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
border: 2px solid rgb(255 255 255 / 20%);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--service-accent);
|
||||||
|
color: #06111a;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card--jellyfin .service-action {
|
||||||
|
background: linear-gradient(180deg, #a95cff, #7d3cff);
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 2px 0 rgb(0 0 0 / 24%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card--minecraft .service-action {
|
||||||
|
background: linear-gradient(180deg, #35f48a, #13ca69);
|
||||||
|
color: #041411;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-action svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 2.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-footer-hint {
|
||||||
|
display: flex;
|
||||||
|
width: min(560px, 100%);
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 42px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24px;
|
||||||
|
color: #a6b1cc;
|
||||||
|
font-family: var(--font-fredoka), var(--font-geist-sans), sans-serif;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-footer-hint span {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--cyan);
|
||||||
|
box-shadow: 0 0 14px var(--cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-footer-hint span:first-child {
|
||||||
|
background: var(--violet);
|
||||||
|
box-shadow: 0 0 14px var(--violet);
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, #0c1220 0%, #11161d 42%, #1e140e 100%);
|
||||||
|
color: var(--paper);
|
||||||
|
font-family: var(--font-retro), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
|
display: flex;
|
||||||
|
width: min(1120px, calc(100% - 24px));
|
||||||
|
margin: 12px auto 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
border: 4px solid #030303;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgb(255 255 255 / 5%) 25%, transparent 25%) 0 0 / 22px 22px,
|
||||||
|
linear-gradient(315deg, rgb(0 0 0 / 22%) 25%, transparent 25%) 0 0 / 22px 22px,
|
||||||
|
rgb(37 41 50 / 95%);
|
||||||
|
box-shadow: var(--shadow-hard);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-brand {
|
||||||
|
padding: 14px 18px;
|
||||||
|
color: var(--paper);
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-brand svg,
|
||||||
|
.minecraft-nav-links svg {
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
fill: var(--diamond, #5ad4e6);
|
||||||
|
stroke: #0a0c12;
|
||||||
|
stroke-width: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav-links {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav-links a {
|
||||||
|
display: flex;
|
||||||
|
min-height: 56px;
|
||||||
|
padding: 0 18px;
|
||||||
|
align-items: center;
|
||||||
|
border-left: 2px solid rgb(0 0 0 / 48%);
|
||||||
|
color: #f5f1e8;
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 0.66rem;
|
||||||
|
transition: background 160ms ease, color 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav-links a:hover {
|
||||||
|
background: rgb(255 213 74 / 12%);
|
||||||
|
color: var(--gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
min-height: 680px;
|
||||||
|
margin-top: -72px;
|
||||||
|
padding: 148px 24px 86px;
|
||||||
|
place-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero::after {
|
||||||
|
position: absolute;
|
||||||
|
inset: auto 0 0;
|
||||||
|
height: 210px;
|
||||||
|
background: linear-gradient(180deg, transparent, rgb(12 18 32 / 80%) 62%, #11161d 100%);
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero-image {
|
||||||
|
z-index: 0;
|
||||||
|
object-fit: cover;
|
||||||
|
filter: saturate(1.05) contrast(1.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
width: min(860px, 100%);
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 4px 4px 0 #030303;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero-content h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 4rem;
|
||||||
|
line-height: 1.18;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero-content p {
|
||||||
|
max-width: 660px;
|
||||||
|
margin: 22px 0 26px;
|
||||||
|
color: #f4f1e8;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-panel {
|
||||||
|
border: 4px solid #030303;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgb(255 255 255 / 5%) 25%, transparent 25%) 0 0 / 24px 24px,
|
||||||
|
linear-gradient(315deg, rgb(0 0 0 / 20%) 25%, transparent 25%) 0 0 / 24px 24px,
|
||||||
|
var(--stone);
|
||||||
|
box-shadow:
|
||||||
|
inset -4px -4px 0 rgb(0 0 0 / 45%),
|
||||||
|
inset 4px 4px 0 rgb(255 255 255 / 8%),
|
||||||
|
var(--shadow-hard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-address {
|
||||||
|
display: inline-flex;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 18px 24px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
color: var(--cyan);
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-address svg {
|
||||||
|
width: 34px;
|
||||||
|
min-width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
fill: #56e1ef;
|
||||||
|
stroke: #041314;
|
||||||
|
stroke-width: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-actions {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 22px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 18px;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-address-button,
|
||||||
|
.map-button,
|
||||||
|
.large-map-link,
|
||||||
|
.back-home-link {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: 58px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
border: 4px solid #030303;
|
||||||
|
box-shadow:
|
||||||
|
inset -3px -3px 0 rgb(0 0 0 / 32%),
|
||||||
|
inset 3px 3px 0 rgb(255 255 255 / 22%),
|
||||||
|
5px 5px 0 rgb(0 0 0 / 48%);
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
transition: transform 160ms ease, box-shadow 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-address-button:hover,
|
||||||
|
.map-button:hover,
|
||||||
|
.large-map-link:hover,
|
||||||
|
.back-home-link:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow:
|
||||||
|
inset -3px -3px 0 rgb(0 0 0 / 32%),
|
||||||
|
inset 3px 3px 0 rgb(255 255 255 / 22%),
|
||||||
|
8px 8px 0 rgb(0 0 0 / 56%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-address-button {
|
||||||
|
padding: 0 24px;
|
||||||
|
background: var(--gold);
|
||||||
|
color: #16120a;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-address-button.is-copied {
|
||||||
|
background: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-button,
|
||||||
|
.large-map-link {
|
||||||
|
padding: 0 24px;
|
||||||
|
background: #12c8c7;
|
||||||
|
color: #061115;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-address-button svg,
|
||||||
|
.map-button svg,
|
||||||
|
.large-map-link svg,
|
||||||
|
.back-home-link svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
fill: none;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-linejoin: round;
|
||||||
|
stroke-width: 2.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
width: min(1120px, calc(100% - 32px));
|
||||||
|
margin: -48px auto 0;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-section {
|
||||||
|
scroll-margin-top: 100px;
|
||||||
|
margin-top: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pixel-heading h2,
|
||||||
|
h2.pixel-heading {
|
||||||
|
margin: 0;
|
||||||
|
color: #fff;
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 1.45rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
text-shadow: 3px 3px 0 #030303;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading p,
|
||||||
|
.map-copy p {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
color: #b8c2cf;
|
||||||
|
font-size: 1.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item {
|
||||||
|
display: grid;
|
||||||
|
min-height: 92px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
place-items: center;
|
||||||
|
border-right: 2px solid rgb(255 255 255 / 12%);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-label {
|
||||||
|
color: #c7cbd1;
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-value {
|
||||||
|
display: inline-flex;
|
||||||
|
max-width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
color: #fff;
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-value--online {
|
||||||
|
color: #7aff5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-value--offline {
|
||||||
|
color: #ff5c5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-light {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: currentColor;
|
||||||
|
box-shadow: 0 0 14px currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-note {
|
||||||
|
margin: 18px 0 0;
|
||||||
|
color: #ffce86;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card {
|
||||||
|
position: relative;
|
||||||
|
min-height: 230px;
|
||||||
|
padding: 58px 22px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number {
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 16px;
|
||||||
|
display: grid;
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
place-items: center;
|
||||||
|
border: 3px solid #030303;
|
||||||
|
background: #080a0f;
|
||||||
|
color: var(--gold);
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card h3 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: var(--gold);
|
||||||
|
font-family: var(--font-pixel), var(--font-geist-mono), monospace;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card p {
|
||||||
|
margin: 0;
|
||||||
|
color: #d6d1c4;
|
||||||
|
font-size: 1.32rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-section {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.85fr 1fr;
|
||||||
|
gap: 28px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-preview {
|
||||||
|
min-height: 250px;
|
||||||
|
padding: 18px;
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgb(255 255 255 / 9%) 25%, transparent 25%) 0 0 / 20px 20px,
|
||||||
|
#24180f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-island {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 214px;
|
||||||
|
border: 4px solid #120b06;
|
||||||
|
background:
|
||||||
|
linear-gradient(45deg, transparent 46%, rgb(0 0 0 / 18%) 48%, rgb(0 0 0 / 18%) 52%, transparent 54%) 0 0 / 34px 34px,
|
||||||
|
radial-gradient(circle at 24% 42%, #7fbf4d 0 9%, transparent 10%),
|
||||||
|
radial-gradient(circle at 52% 35%, #8bc95a 0 12%, transparent 13%),
|
||||||
|
radial-gradient(circle at 71% 58%, #6ead43 0 10%, transparent 11%),
|
||||||
|
linear-gradient(135deg, #2d8ac7 0 36%, #75b756 36% 64%, #2d8ac7 64% 100%);
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pin {
|
||||||
|
position: absolute;
|
||||||
|
top: 22%;
|
||||||
|
right: 18%;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border: 4px solid #160302;
|
||||||
|
border-radius: 50% 50% 50% 0;
|
||||||
|
background: #ff493f;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
box-shadow: 4px 4px 0 rgb(0 0 0 / 38%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-copy {
|
||||||
|
display: flex;
|
||||||
|
padding: 34px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
border: 4px solid #030303;
|
||||||
|
background: rgb(37 41 50 / 92%);
|
||||||
|
box-shadow: var(--shadow-hard);
|
||||||
|
}
|
||||||
|
|
||||||
|
.large-map-link {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 24px;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-back {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 42px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-home-link {
|
||||||
|
padding: 0 24px;
|
||||||
|
background: var(--stone-light);
|
||||||
|
color: var(--paper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes star-drift {
|
||||||
|
from {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(-220px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.home-hero h1 {
|
||||||
|
font-size: 3.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-grid {
|
||||||
|
gap: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav {
|
||||||
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-brand {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav-links {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav-links a {
|
||||||
|
min-height: 48px;
|
||||||
|
justify-content: center;
|
||||||
|
border-top: 2px solid rgb(0 0 0 / 48%);
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero {
|
||||||
|
margin-top: -118px;
|
||||||
|
padding-top: 214px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel,
|
||||||
|
.steps-grid {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:nth-child(2) {
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:nth-child(-n + 2) {
|
||||||
|
border-bottom: 2px solid rgb(255 255 255 / 12%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-section {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.home-shell {
|
||||||
|
width: min(100% - 24px, 520px);
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-lockup {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
width: 46px;
|
||||||
|
height: 46px;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero {
|
||||||
|
margin-top: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero h1 {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-hero p {
|
||||||
|
font-size: 1.18rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
margin-top: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card {
|
||||||
|
min-height: 276px;
|
||||||
|
padding: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-footer-hint {
|
||||||
|
padding-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-page {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav-links {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-nav-links a {
|
||||||
|
font-size: 0.58rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero {
|
||||||
|
min-height: 720px;
|
||||||
|
padding-right: 16px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero-content h1 {
|
||||||
|
font-size: 2.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-hero-content p {
|
||||||
|
font-size: 1.42rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-address {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.minecraft-actions,
|
||||||
|
.copy-address-button,
|
||||||
|
.map-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel,
|
||||||
|
.steps-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item,
|
||||||
|
.status-item:nth-child(2),
|
||||||
|
.status-item:nth-child(-n + 2) {
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 2px solid rgb(255 255 255 / 12%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pixel-heading h2,
|
||||||
|
h2.pixel-heading {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading p,
|
||||||
|
.map-copy p {
|
||||||
|
font-size: 1.24rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.large-map-link {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
scroll-behavior: auto !important;
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-5
@@ -1,5 +1,5 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Fredoka, Geist, Geist_Mono, Press_Start_2P, VT323 } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
@@ -12,9 +12,26 @@ const geistMono = Geist_Mono({
|
|||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fredoka = Fredoka({
|
||||||
|
variable: "--font-fredoka",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const pressStart = Press_Start_2P({
|
||||||
|
variable: "--font-pixel",
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: "400",
|
||||||
|
});
|
||||||
|
|
||||||
|
const vt323 = VT323({
|
||||||
|
variable: "--font-retro",
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: "400",
|
||||||
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create Next App",
|
title: "Le serveur de Leon",
|
||||||
description: "Generated by create next app",
|
description: "Streaming, Minecraft et services privés de Leon.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -24,8 +41,9 @@ export default function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html
|
<html
|
||||||
lang="en"
|
lang="fr"
|
||||||
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
data-scroll-behavior="smooth"
|
||||||
|
className={`${geistSans.variable} ${geistMono.variable} ${fredoka.variable} ${pressStart.variable} ${vt323.variable} h-full antialiased`}
|
||||||
>
|
>
|
||||||
<body className="min-h-full flex flex-col">{children}</body>
|
<body className="min-h-full flex flex-col">{children}</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const siteConfig = {
|
||||||
|
name: "leonmorival.xyz",
|
||||||
|
title: "Le serveur de Leon",
|
||||||
|
description: "Streaming, jeux et services privés au même endroit.",
|
||||||
|
jellyfinUrl: "https://momostreaming.com",
|
||||||
|
minecraftPath: "/minecraft",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const minecraftServer = {
|
||||||
|
name: "Minecraft Survie",
|
||||||
|
address: "minecraft.leonmorival.xyz",
|
||||||
|
mapUrl: "https://map.leonmorival.xyz",
|
||||||
|
versionHint: "Java",
|
||||||
|
access: "Whitelist sur demande",
|
||||||
|
};
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import CopyAddressButton from "../components/CopyAddressButton";
|
||||||
|
import MinecraftStatus from "../components/MinecraftStatus";
|
||||||
|
import { minecraftServer, siteConfig } from "../lib/server-config";
|
||||||
|
|
||||||
|
const joinSteps = [
|
||||||
|
{
|
||||||
|
number: "1",
|
||||||
|
title: "Lance Minecraft Java",
|
||||||
|
text: "Utilise ton launcher habituel avec la version Java recommandée par le serveur.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "2",
|
||||||
|
title: "Ajoute le serveur",
|
||||||
|
text: `Renseigne ${minecraftServer.address} dans la liste multijoueur.`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "3",
|
||||||
|
title: "Demande l'accès",
|
||||||
|
text: "Le serveur fonctionne avec une whitelist pour garder un espace privé.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "4",
|
||||||
|
title: "Explore la map",
|
||||||
|
text: "Une fois connecté, retrouve les constructions sur la carte live.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function MinecraftPage() {
|
||||||
|
return (
|
||||||
|
<main className="minecraft-page">
|
||||||
|
<nav className="minecraft-nav" aria-label="Navigation Minecraft">
|
||||||
|
<Link href="/" className="minecraft-brand">
|
||||||
|
<SwordIcon />
|
||||||
|
<span>{siteConfig.name}</span>
|
||||||
|
</Link>
|
||||||
|
<div className="minecraft-nav-links">
|
||||||
|
<Link href="/">Accueil</Link>
|
||||||
|
<a href="#statut">Statut</a>
|
||||||
|
<a href="#rejoindre">Rejoindre</a>
|
||||||
|
<a href={minecraftServer.mapUrl} target="_blank" rel="noopener noreferrer">
|
||||||
|
Carte
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section className="minecraft-hero" aria-labelledby="minecraft-title">
|
||||||
|
<Image
|
||||||
|
src="/assets/minecraft-night.png"
|
||||||
|
alt=""
|
||||||
|
fill
|
||||||
|
sizes="100vw"
|
||||||
|
priority
|
||||||
|
className="minecraft-hero-image"
|
||||||
|
/>
|
||||||
|
<div className="minecraft-hero-content">
|
||||||
|
<h1 id="minecraft-title">Minecraft Survie</h1>
|
||||||
|
<p>Un monde privé pour construire, explorer et jouer entre amis.</p>
|
||||||
|
|
||||||
|
<div className="server-address block-panel">
|
||||||
|
<DiamondIcon />
|
||||||
|
<span>{minecraftServer.address}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="minecraft-actions">
|
||||||
|
<CopyAddressButton value={minecraftServer.address} />
|
||||||
|
<a
|
||||||
|
className="map-button"
|
||||||
|
href={minecraftServer.mapUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<MapIcon />
|
||||||
|
<span>Ouvrir la map</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="minecraft-content">
|
||||||
|
<MinecraftStatus />
|
||||||
|
|
||||||
|
<section id="rejoindre" className="minecraft-section">
|
||||||
|
<div className="section-heading pixel-heading">
|
||||||
|
<h2>Comment rejoindre</h2>
|
||||||
|
<p>Quatre étapes pour entrer dans le monde.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="steps-grid">
|
||||||
|
{joinSteps.map((step) => (
|
||||||
|
<article className="step-card block-panel" key={step.number}>
|
||||||
|
<span className="step-number">{step.number}</span>
|
||||||
|
<h3>{step.title}</h3>
|
||||||
|
<p>{step.text}</p>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="minecraft-section map-section" aria-labelledby="map-title">
|
||||||
|
<div className="map-preview block-panel" aria-hidden="true">
|
||||||
|
<div className="map-island">
|
||||||
|
<span className="map-pin" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="map-copy">
|
||||||
|
<h2 id="map-title" className="pixel-heading">
|
||||||
|
Carte live
|
||||||
|
</h2>
|
||||||
|
<p>Explore le monde en temps réel depuis ton navigateur.</p>
|
||||||
|
<a
|
||||||
|
className="large-map-link"
|
||||||
|
href={minecraftServer.mapUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<MapIcon />
|
||||||
|
<span>map.leonmorival.xyz</span>
|
||||||
|
<ArrowIcon />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="minecraft-back">
|
||||||
|
<Link href="/" className="back-home-link">
|
||||||
|
<ArrowLeftIcon />
|
||||||
|
<span>Retour accueil</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SwordIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M14.6 4h5.4v5.4L9 20.4 3.6 15z" />
|
||||||
|
<path d="M5.8 12.8 11.2 18" />
|
||||||
|
<path d="M4 20 8 16" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DiamondIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="m12 2 8 8-8 12-8-12z" />
|
||||||
|
<path d="M4 10h16" />
|
||||||
|
<path d="m8 10 4 12 4-12" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MapIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="m3 6 6-2 6 2 6-2v14l-6 2-6-2-6 2z" />
|
||||||
|
<path d="M9 4v14" />
|
||||||
|
<path d="M15 6v14" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ArrowIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M5 12h12" />
|
||||||
|
<path d="m13 6 6 6-6 6" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ArrowLeftIcon() {
|
||||||
|
return (
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||||
|
<path d="M19 12H7" />
|
||||||
|
<path d="m11 6-6 6 6 6" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
+45
-55
@@ -1,65 +1,55 @@
|
|||||||
import Image from "next/image";
|
import Link from "next/link";
|
||||||
|
import HomeElement, {
|
||||||
|
JellyfinIcon,
|
||||||
|
MinecraftIcon,
|
||||||
|
} from "./components/HomeElement";
|
||||||
|
import { siteConfig } from "./lib/server-config";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
|
<main className="stellar-page home-page">
|
||||||
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
|
<section className="home-shell" aria-labelledby="home-title">
|
||||||
<Image
|
<nav className="home-nav" aria-label="Navigation principale">
|
||||||
className="dark:invert"
|
<Link href="/" className="brand-lockup" aria-label="Accueil">
|
||||||
src="/next.svg"
|
<span className="brand-mark">L</span>
|
||||||
alt="Next.js logo"
|
<span>{siteConfig.name}</span>
|
||||||
width={100}
|
</Link>
|
||||||
height={20}
|
</nav>
|
||||||
priority
|
|
||||||
/>
|
<div className="home-hero">
|
||||||
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
<h1 id="home-title">{siteConfig.title}</h1>
|
||||||
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
<p>{siteConfig.description}</p>
|
||||||
To get started, edit the page.tsx file.
|
|
||||||
</h1>
|
|
||||||
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
|
|
||||||
Looking for a starting point or more instructions? Head over to{" "}
|
|
||||||
<a
|
|
||||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Templates
|
|
||||||
</a>{" "}
|
|
||||||
or the{" "}
|
|
||||||
<a
|
|
||||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
className="font-medium text-zinc-950 dark:text-zinc-50"
|
|
||||||
>
|
|
||||||
Learning
|
|
||||||
</a>{" "}
|
|
||||||
center.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
|
|
||||||
<a
|
<div className="service-grid" aria-label="Services disponibles">
|
||||||
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
|
<HomeElement
|
||||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
title="Jellyfin"
|
||||||
target="_blank"
|
description="Films, séries et bibliothèque privée."
|
||||||
rel="noopener noreferrer"
|
href={siteConfig.jellyfinUrl}
|
||||||
|
actionLabel="Ouvrir"
|
||||||
|
variant="jellyfin"
|
||||||
|
external
|
||||||
>
|
>
|
||||||
<Image
|
<JellyfinIcon />
|
||||||
className="dark:invert"
|
</HomeElement>
|
||||||
src="/vercel.svg"
|
|
||||||
alt="Vercel logomark"
|
<HomeElement
|
||||||
width={16}
|
title="Minecraft"
|
||||||
height={16}
|
description="Infos, statut et carte live du monde."
|
||||||
/>
|
href={siteConfig.minecraftPath}
|
||||||
Deploy Now
|
actionLabel="Voir le serveur"
|
||||||
</a>
|
variant="minecraft"
|
||||||
<a
|
|
||||||
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
|
|
||||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
>
|
||||||
Documentation
|
<MinecraftIcon />
|
||||||
</a>
|
</HomeElement>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="home-footer-hint" aria-hidden="true">
|
||||||
|
<span />
|
||||||
|
<strong>Services privés</strong>
|
||||||
|
<span />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
Reference in New Issue
Block a user