page-redesign #22

Merged
Chaoscaot merged 9 commits from page-redesign into master 2026-04-23 12:23:27 +02:00
41 changed files with 911 additions and 635 deletions

View File

@@ -24,4 +24,4 @@ export default defineAstroI18nConfig({
"passwort-setzen": "set-password", "passwort-setzen": "set-password",
}, },
}, },
}); });

View File

@@ -47,7 +47,6 @@ export default defineConfig({
configFile: "./tailwind.config.js", configFile: "./tailwind.config.js",
applyBaseStyles: false, applyBaseStyles: false,
}), }),
configureI18n(),
sitemap({ sitemap({
i18n: { i18n: {
defaultLocale: "en", defaultLocale: "en",

View File

@@ -3,7 +3,13 @@ import { Image } from "astro:assets";
import localBau from "@images/90.png"; import localBau from "@images/90.png";
--- ---
<Image src={localBau} alt="Bau" widths={[240, 540, 720, 1080, 1920, localBau.width]} <Image
src={localBau}
alt="Bau"
widths={[240, 540, 720, 1080, 1920, localBau.width]}
sizes={`(max-width: 240px) 240px, (max-width: 540px) 540px, (max-width: 720px) 720px, (max-width: 1080px) 1080px, (max-width: 1920px) 1920px, ${localBau.width}px`} sizes={`(max-width: 240px) 240px, (max-width: 540px) 540px, (max-width: 720px) 720px, (max-width: 1080px) 1080px, (max-width: 1920px) 1920px, ${localBau.width}px`}
class="w-full h-full object-cover rounded-b-2xl shadow-2xl" quality={100} class="w-full h-full object-cover"
draggable="false" loading="eager"/> quality={100}
draggable="false"
loading="eager"
/>

View File

@@ -52,7 +52,7 @@
} }
let { hoverEffect = true, extraClasses = "", children }: Props = $props(); let { hoverEffect = true, extraClasses = "", children }: Props = $props();
let classes = $derived(twMerge("w-72 border-2 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg bg-zinc-900 dark:border-gray-800 dark:text-gray-100", extraClasses)); let classes = $derived(twMerge("flex flex-col items-center p-8 m-4 bg-[#0c0c0c] border border-[rgba(255,255,255,0.06)] text-gray-100", extraClasses));
</script> </script>
<div class={classes} bind:this={cardElement} onmousemove={rotateElement} onmouseleave={resetElement} class:hoverEffect> <div class={classes} bind:this={cardElement} onmousemove={rotateElement} onmouseleave={resetElement} class:hoverEffect>
@@ -67,10 +67,13 @@
:global(h1) { :global(h1) {
@apply text-xl font-bold mt-4; @apply text-xl font-bold mt-4;
font-family: "Barlow Condensed", sans-serif;
letter-spacing: 0.06em;
} }
:global(svg) { :global(svg) {
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl; color: #f59e0b;
@apply transition-transform duration-300 ease-in-out hover:scale-110;
} }
} }

View File

@@ -44,7 +44,7 @@
} }
</script> </script>
<div class="p-3 bg-gray-200 dark:bg-neutral-800 rounded-2xl w-3/4 mx-auto"> <div class="p-3 bg-[#0c0c0c] border border-[rgba(255,255,255,0.06)] w-3/4 mx-auto">
<table> <table>
<thead> <thead>
<tr class="font-bold border-b"> <tr class="font-bold border-b">
@@ -57,7 +57,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{#each window( event.fights.filter((f) => (group === undefined ? true : f.group?.id === group)), rows ) as fights} {#each window( event.fights.filter((f) => (group === undefined ? true : f.group?.id === group)), rows, ) as fights}
<tr> <tr>
{#each fights as fight (fight.id)} {#each fights as fight (fight.id)}
<td <td

View File

@@ -40,11 +40,11 @@
team: event.teams.find((t) => t.id === Number(teamId))!!, team: event.teams.find((t) => t.id === Number(teamId))!!,
points: points, points: points,
})) }))
.sort((a, b) => b.points - a.points) .sort((a, b) => b.points - a.points),
); );
</script> </script>
<div class="p-3 bg-gray-200 dark:bg-neutral-800 rounded-2xl w-3/4 mx-auto"> <div class="p-3 bg-[#0c0c0c] border border-[rgba(255,255,255,0.06)] w-3/4 mx-auto">
<table class="w-full"> <table class="w-full">
<thead> <thead>
<tr class="font-bold border-b"> <tr class="font-bold border-b">

View File

@@ -1,8 +1,8 @@
--- ---
import {t} from "astro-i18n"; import { t } from "astro-i18n";
--- ---
<div class="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4" role="alert"> <div class="border-l-2 border-amber-500 bg-amber-500/5 p-4" role="alert">
<p class="font-bold">{t("warning.title")}</p> <p class="font-bold text-amber-400" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.1em; text-transform: uppercase;">{t("warning.title")}</p>
<p>{t("warning.text")}</p> <p class="text-gray-400 text-sm">{t("warning.text")}</p>
</div> </div>

View File

@@ -74,8 +74,8 @@
}); });
</script> </script>
<form class="bg-gray-100 dark:bg-neutral-900 p-12 rounded-2xl shadow-2xl border-2 border-gray-600 flex flex-col" onsubmit={preventDefault(login)}> <form class="sw-login-form" onsubmit={preventDefault(login)}>
<h1 class="text-4xl text-white text-center">{t("login.title")}</h1> <h1 class="text-4xl text-white text-center" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.08em;">{t("login.title")}</h1>
<div class="ml-2 flex flex-col"> <div class="ml-2 flex flex-col">
<label for="username">{t("login.label.username")}</label> <label for="username">{t("login.label.username")}</label>
<input type="text" id="username" name="username" placeholder={t("login.placeholder.username")} bind:value={username} /> <input type="text" id="username" name="username" placeholder={t("login.placeholder.username")} bind:value={username} />
@@ -83,24 +83,70 @@
<input type="password" id="password" name="password" placeholder={t("login.placeholder.password")} bind:value={pw} /> <input type="password" id="password" name="password" placeholder={t("login.placeholder.password")} bind:value={pw} />
</div> </div>
<p class="mt-2"> <p class="mt-2">
<a class="text-neutral-500 hover:underline" href={l("/set-password")}>{t("login.setPassword")}</a> <a class="sw-link" href={l("/set-password")}>{t("login.setPassword")}</a>
</p> </p>
{#if error} {#if error}
<p class="mt-2 text-red-500">{error}</p> <p class="mt-2 text-red-500">{error}</p>
{/if} {/if}
<button class="btn mt-4 !mx-0 justify-center" type="submit" onclick={preventDefault(login)}>{t("login.submit")}</button> <button class="btn mt-4 justify-center w-full" type="submit" onclick={preventDefault(login)}>{t("login.submit")}</button>
<a class="btn mt-4 !mx-0 justify-center" href="https://discord.com/oauth2/authorize?client_id=869606970099904562&response_type=token&redirect_uri=https%3A%2F%2Fsteamwar.de%2Flogin&scope=identify"> <a
class="btn mt-4 justify-center w-full"
href="https://discord.com/oauth2/authorize?client_id=869606970099904562&response_type=token&redirect_uri=https%3A%2F%2Fsteamwar.de%2Flogin&scope=identify"
>
{t("login.discord")} {t("login.discord")}
</a> </a>
</form> </form>
<style lang="postcss"> <style lang="postcss">
.sw-login-form {
background: rgba(12, 12, 12, 0.95);
border: 1px solid rgba(255, 255, 255, 0.06);
border-top: 2px solid #f59e0b;
backdrop-filter: blur(24px);
padding: 3rem;
display: flex;
flex-direction: column;
}
input { input {
@apply border-2 rounded-md p-2 shadow-2xl w-80 dark:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:border-transparent text-black; width: 20rem;
padding: 0.6rem 0.8rem;
margin-top: 0.25rem;
margin-bottom: 0.5rem;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
color: #f5f5f5;
font-size: 0.85rem;
outline: none;
transition: border-color 0.2s ease;
}
input:focus {
border-color: rgba(245, 158, 11, 0.5);
} }
label { label {
@apply text-neutral-300; font-family: "Barlow Condensed", sans-serif;
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: rgba(163, 163, 163, 0.7);
margin-top: 0.5rem;
}
.sw-link {
color: rgba(163, 163, 163, 0.5);
text-decoration: none;
font-size: 0.85rem;
border-bottom: 1px solid transparent;
transition:
color 0.2s,
border-color 0.2s;
}
.sw-link:hover {
color: #f59e0b;
border-bottom-color: #f59e0b;
} }
</style> </style>

View File

@@ -19,9 +19,8 @@
<script lang="ts"> <script lang="ts">
import "../styles/button.css"; import "../styles/button.css";
import { CaretDownOutline, SearchOutline } from "flowbite-svelte-icons"; import { CaretDownOutline, GlobeOutline } from "flowbite-svelte-icons";
import { t } from "astro-i18n"; import { t, l } from "astro-i18n";
import { l } from "../util/util";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { loggedIn } from "@repo/authv2.ts"; import { loggedIn } from "@repo/authv2.ts";
interface Props { interface Props {
@@ -49,29 +48,25 @@
function handleScroll() { function handleScroll() {
if (window.scrollY > 0) { if (window.scrollY > 0) {
navbar!.classList.add("before:scale-y-100"); navbar!.classList.add("sw-nav-scrolled");
} else { } else {
navbar!.classList.remove("before:scale-y-100"); navbar!.classList.remove("sw-nav-scrolled");
} }
} }
</script> </script>
<svelte:window onscroll={handleScroll} /> <svelte:window onscroll={handleScroll} />
<nav <nav data-pagefind-ignore class="sw-nav z-20 fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors flex justify-center" bind:this={navbar}>
data-pagefind-ignore
class="z-20 fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors flex justify-center before:backdrop-blur before:shadow-2xl before:absolute before:top-0 before:left-0 before:bottom-0 before:right-0 before:-z-10 before:scale-y-0 before:transition-transform before:origin-top"
bind:this={navbar}
>
<div class="flex flex-row items-center justify-evenly md:justify-between match"> <div class="flex flex-row items-center justify-evenly md:justify-between match">
<a class="flex items-center" href={l("/")}> <a class="flex items-center" href={l("/")}>
{@render logo?.()} {@render logo?.()}
<span class="text-2xl uppercase font-bold text-white hidden md:inline-block"> <span class="sw-nav-title hidden md:inline-block">
{t("navbar.title")} {t("navbar.title")}
<span class="before:scale-y-100" style="display: none" aria-hidden="true"></span> <span class="scrolled-trigger" style="display: none" aria-hidden="true"></span>
</span> </span>
</a> </a>
<div class="flex justify-center flex-wrap"> <div class="flex justify-center flex-wrap gap-2">
<div class="btn-dropdown"> <div class="btn-dropdown">
<button class="btn btn-gray"> <button class="btn btn-gray">
<a href={l("/")}> <a href={l("/")}>
@@ -104,8 +99,6 @@
<a href={l("/rules/megawargear")} class="btn btn-gray">{t("navbar.links.rules.megawg")}</a> <a href={l("/rules/megawargear")} class="btn btn-gray">{t("navbar.links.rules.megawg")}</a>
<a href={l("/rules/microwargear")} class="btn btn-gray">{t("navbar.links.rules.micro")}</a> <a href={l("/rules/microwargear")} class="btn btn-gray">{t("navbar.links.rules.micro")}</a>
<a href={l("/rules/streetfight")} class="btn btn-gray">{t("navbar.links.rules.sf")}</a> <a href={l("/rules/streetfight")} class="btn btn-gray">{t("navbar.links.rules.sf")}</a>
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.ranked")}</h2>
<a href={l("/rangliste/missilewars")} class="btn btn-gray">{t("navbar.links.ranked.mw")}</a>
</div> </div>
</div> </div>
<!-- TODO: Add help center <!-- TODO: Add help center
@@ -125,11 +118,16 @@
<a class="btn" href={l("/login")} bind:this={accountBtn}> <a class="btn" href={l("/login")} bind:this={accountBtn}>
<span class="btn__text">{t("navbar.links.account")}</span> <span class="btn__text">{t("navbar.links.account")}</span>
</a> </a>
<!-- <div class="btn-dropdown">
<button class="btn my-1" onclick={() => searchOpen = true}> <button class="btn btn-gray">
<SearchOutline ariaLabel="Site Search" class="inline-block h-6"/> <GlobeOutline />
</button> </button>
--> <div>
<a href={l("/")} onclick={() => cookieStore.delete("MANUAL_LANGUAGE")} class="btn btn-gray">Auto</a>
<a href={l("/", {}, { targetLocale: "de" })} onclick={() => cookieStore.set("MANUAL_LANGUAGE", "TRUE")} class="btn btn-gray">Deutsch</a>
<a href={l("/", {}, { targetLocale: "en" })} onclick={() => cookieStore.set("MANUAL_LANGUAGE", "TRUE")} class="btn btn-gray">English</a>
</div>
</div>
</div> </div>
</div> </div>
</nav> </nav>
@@ -144,4 +142,29 @@
.match { .match {
width: min(100vw, 70em); width: min(100vw, 70em);
} }
:global(.sw-nav) {
backdrop-filter: none;
background: transparent;
transition:
background 0.3s ease,
backdrop-filter 0.3s ease,
border-color 0.3s ease;
border-bottom: 1px solid transparent;
}
:global(.sw-nav-scrolled) {
background: rgba(8, 8, 8, 0.85) !important;
backdrop-filter: blur(16px) !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.04) !important;
}
.sw-nav-title {
font-family: "Barlow Condensed", sans-serif;
font-size: 1.3rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #f5f5f5;
}
</style> </style>

View File

@@ -22,26 +22,21 @@ const {
const postUrl = l(`/announcements/${post.slug.split("/").slice(1).join("/")}`); const postUrl = l(`/announcements/${post.slug.split("/").slice(1).join("/")}`);
--- ---
<Card extraClasses={`w-full items-start mx-0 ${slim ? "m-0 p-1 backdrop-blur-xl bg-transparent" : ""}`} hoverEffect={false}> <Card extraClasses={`w-full items-start mx-0 ${slim ? "m-0 p-2 backdrop-blur-xl bg-transparent border-0" : "border-t-2 border-t-amber-500/30"}`} hoverEffect={false}>
<div class={`flex flex-row ${slim ? "" : "p-4"}`}> <div class={`flex flex-row ${slim ? "" : "p-4"}`}>
{ {
post.data.image != null ? ( post.data.image != null ? (
<a href={postUrl}> <a href={postUrl}>
<div class="flex-shrink-0 pr-2"> <div class="flex-shrink-0 pr-2">
<Image <Image transition:name={post.data.title + "-image"} src={post.data.image} alt="Post Image" class="object-cover h-32 w-32 max-w-none transition-transform hover:scale-105" />
transition:name={post.data.title + "-image"}
src={post.data.image}
alt="Post Image"
class="rounded-2xl shadow-2xl object-cover h-32 w-32 max-w-none transition-transform hover:scale-105"
/>
</div> </div>
</a> </a>
) : null ) : null
} }
<div> <div>
<a href={postUrl} class="flex flex-col items-start"> <a href={postUrl} class="flex flex-col items-start">
<h2 class="text-2xl font-bold" transition:name={post.data.title + "-title"}>{post.data.title}</h2> <h2 class="text-2xl font-bold" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;" transition:name={post.data.title + "-title"}>{post.data.title}</h2>
<P class="text-gray-500" <P class="text-gray-500 text-sm"
>{ >{
Intl.DateTimeFormat(astroI18n.locale, { Intl.DateTimeFormat(astroI18n.locale, {
day: "numeric", day: "numeric",

View File

@@ -18,12 +18,11 @@
--> -->
<script lang="ts"> <script lang="ts">
import { slide, fade } from "svelte/transition";
import {slide, fade} from "svelte/transition"; import { onMount } from "svelte";
import {onMount} from "svelte"; import { importPagefind, type Pagefind, type PagefindDocument } from "@type/pagefind.js";
import {importPagefind, type Pagefind, type PagefindDocument} from "@type/pagefind.js";
import Card from "@components/Card.svelte"; import Card from "@components/Card.svelte";
import {l} from "@utils/util.ts"; import { l } from "@utils/util.ts";
let pagefind: Pagefind; let pagefind: Pagefind;
onMount(async () => { onMount(async () => {
@@ -36,27 +35,26 @@
async function search(e: KeyboardEvent) { async function search(e: KeyboardEvent) {
if (e.target instanceof HTMLInputElement) { if (e.target instanceof HTMLInputElement) {
let search: {results: any[]} = await pagefind.debouncedSearch(e.target.value); let search: { results: any[] } = await pagefind.debouncedSearch(e.target.value);
results = await Promise.all(search.results.slice(0, 10).map(value => value.data())) results = await Promise.all(search.results.slice(0, 10).map((value) => value.data()));
} }
} }
interface Props { interface Props {
open?: boolean; open?: boolean;
} }
let { open = $bindable(false) }: Props = $props(); let { open = $bindable(false) }: Props = $props();
</script> </script>
<button transition:fade class="fixed top-0 left-0 w-screen h-screen backdrop-blur z-20 cursor-default" onclick={() => open = false}> <button transition:fade class="fixed top-0 left-0 w-screen h-screen bg-black/60 backdrop-blur-sm z-20 cursor-default" onclick={() => (open = false)}> </button>
</button> <div transition:slide style="width: min(100%, 75em);" class="fixed top-0 left-1/2 -translate-x-1/2 h-2/3 z-30 p-4 text-white flex flex-col sw-search-panel">
<div transition:slide style="width: min(100%, 75em);" class="fixed top-0 left-1/2 -translate-x-1/2 h-2/3 dark:bg-zinc-900 rounded-b-2xl shadow-2xl z-30 p-4 text-white flex flex-col"> <input placeholder="Search..." onkeypress={search} />
<input placeholder="Search..." onkeypress={search}>
<div class="overflow-y-scroll flex-1 w-full mt-2 rounded-2xl"> <div class="overflow-y-scroll flex-1 w-full mt-2">
{#each results as result} {#each results as result}
<Card extraClasses="w-full m-0 my-2" hoverEffect={false}> <Card extraClasses="w-full m-0 my-2 border-t-2 border-t-amber-500/30" hoverEffect={false}>
<a class="grid grid-cols-3" href={l(result.url)}> <a class="grid grid-cols-3" href={l(result.url)}>
<h1>{result.meta.title}</h1> <h1>{result.meta.title}</h1>
{#each result.sub_results.slice(0, 2) as sub_result} {#each result.sub_results.slice(0, 2) as sub_result}
@@ -69,13 +67,28 @@
</div> </div>
<style lang="postcss"> <style lang="postcss">
.sw-search-panel {
background: rgba(8, 8, 8, 0.95);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
backdrop-filter: blur(24px);
}
input { input {
@apply border-2 rounded-md p-2 shadow-2xl w-full width: 100%;
dark:bg-neutral-800 padding: 0.7rem 1rem;
focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:border-transparent; background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
color: #f5f5f5;
font-size: 0.9rem;
outline: none;
transition: border-color 0.2s ease;
}
input:focus {
border-color: rgba(245, 158, 11, 0.5);
} }
label { label {
@apply text-neutral-300; color: rgba(163, 163, 163, 0.7);
} }
</style> </style>

View File

@@ -1,22 +1,31 @@
--- ---
import { l } from "../util/util";
import {l} from "../util/util"; import { capitalize } from "./admin/util";
import {capitalize} from "./admin/util";
interface Props { interface Props {
tag: string; tag: string;
noLink?: boolean; noLink?: boolean;
} }
const {tag, noLink} = Astro.props; const { tag, noLink } = Astro.props;
--- ---
{noLink {
? ( noLink ? (
<span class="inline-block bg-gray-800 rounded-full px-3 py-1 text-sm font-semibold text-white mr-2 shadow-2xl">{capitalize(tag)}</span> <span
) class="inline-block bg-transparent border border-amber-500/30 px-3 py-0.5 text-xs font-semibold text-amber-400 mr-2 uppercase tracking-wider"
: ( style="font-family: 'Barlow Condensed', sans-serif;"
>
{capitalize(tag)}
</span>
) : (
<a href={l(`/announcements/tags/${tag}`)}> <a href={l(`/announcements/tags/${tag}`)}>
<span class="inline-block bg-gray-800 rounded-full px-3 py-1 text-sm font-semibold text-white mr-2 shadow-2xl">{capitalize(tag)}</span> <span
class="inline-block bg-transparent border border-amber-500/30 px-3 py-0.5 text-xs font-semibold text-amber-400 mr-2 uppercase tracking-wider hover:border-amber-400 hover:text-amber-300 transition-colors"
style="font-family: 'Barlow Condensed', sans-serif;"
>
{capitalize(tag)}
</span>
</a> </a>
)} )
}

View File

@@ -52,7 +52,7 @@
/> />
</figure> </figure>
</Card> </Card>
<div class="flex flex-wrap"> <div class="flex flex-wrap gap-2">
<button class="btn mt-2" onclick={logout}>{t("dashboard.buttons.logout")}</button> <button class="btn mt-2" onclick={logout}>{t("dashboard.buttons.logout")}</button>
{#if user.perms.includes("MODERATION")} {#if user.perms.includes("MODERATION")}
<a class="btn w-fit mt-2" href="/admin/new" data-astro-reload>{t("dashboard.buttons.admin")}</a> <a class="btn w-fit mt-2" href="/admin/new" data-astro-reload>{t("dashboard.buttons.admin")}</a>

View File

@@ -13,7 +13,7 @@
</script> </script>
<div class="flex flex-col gap-1 {unsized ? '' : 'w-72 m-4'}"> <div class="flex flex-col gap-1 {unsized ? '' : 'w-72 m-4'}">
<div class="bg-gray-100 text-black font-bold px-2 rounded uppercase"> <div class="bg-amber-500 text-black font-bold px-2 uppercase text-xs tracking-wider" style="font-family: 'Barlow Condensed', sans-serif;">
{title} {title}
</div> </div>
<div class="border border-gray-600 rounded p-2 flex flex-col gap-2 bg-slate-900"> <div class="border border-gray-600 rounded p-2 flex flex-col gap-2 bg-slate-900">

View File

@@ -18,36 +18,21 @@
--> -->
<script lang="ts"> <script lang="ts">
import { preventDefault } from 'svelte/legacy'; import { preventDefault } from "svelte/legacy";
import { import { ChevronDoubleLeftOutline, ChevronDoubleRightOutline, ChevronLeftOutline, ChevronRightOutline } from "flowbite-svelte-icons";
ChevronDoubleLeftOutline,
ChevronDoubleRightOutline,
ChevronLeftOutline,
ChevronRightOutline,
} from "flowbite-svelte-icons";
interface Props {
page?: number;
maxPage: number;
firstUrl?: string;
lastUrl?: string;
previousUrl?: string;
nextUrl?: string;
pagesUrl?: (i: number) => string;
}
let { page = $bindable(0), maxPage, firstUrl = "#", lastUrl = "#", previousUrl = "#", nextUrl = "#", pagesUrl = () => "#" }: Props = $props();
interface Props {
page?: number;
maxPage: number;
firstUrl?: string;
lastUrl?: string;
previousUrl?: string;
nextUrl?: string;
pagesUrl?: (i: number) => string;
}
let {
page = $bindable(0),
maxPage,
firstUrl = "#",
lastUrl = "#",
previousUrl = "#",
nextUrl = "#",
pagesUrl = () => "#"
}: Props = $props();
const previous = () => { const previous = () => {
page = Math.max(page - 1, 0); page = Math.max(page - 1, 0);
@@ -56,20 +41,23 @@
const next = () => { const next = () => {
page = Math.min(page + 1, maxPage - 1); page = Math.min(page + 1, maxPage - 1);
}; };
let pages = $derived(new Array(maxPage).fill(0) let pages = $derived(
.map((_, i) => i + 1) new Array(maxPage)
//.slice(Math.max(page - 2, 0) - Math.abs(Math.max(page + 3 - maxPage, 0)), Math.min(page + 3, maxPage) + Math.abs(Math.min(page - 2, 0))) .fill(0)
.map(i => ({ .map((_, i) => i + 1)
name: i.toString(), //.slice(Math.max(page - 2, 0) - Math.abs(Math.max(page + 3 - maxPage, 0)), Math.min(page + 3, maxPage) + Math.abs(Math.min(page - 2, 0)))
active: i === page + 1, .map((i) => ({
i: i - 1 name: i.toString(),
}))); active: i === page + 1,
i: i - 1,
})),
);
</script> </script>
<div class="w-full flex justify-center mt-4"> <div class="w-full flex justify-center mt-4">
<ul class="inline-flex flex-wrap"> <ul class="inline-flex flex-wrap gap-1">
<li> <li>
<a href={firstUrl} onclick={preventDefault(() => page = 0)} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-r-none"> <a href={firstUrl} onclick={preventDefault(() => (page = 0))} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-r-none">
<span class="sr-only">Next</span> <span class="sr-only">Next</span>
<ChevronDoubleLeftOutline class="w-3 h-3" /> <ChevronDoubleLeftOutline class="w-3 h-3" />
</a> </a>
@@ -82,7 +70,7 @@
</li> </li>
{#each pages as p} {#each pages as p}
<li> <li>
<a href={pagesUrl(p.i)} onclick={preventDefault(() => page = p.i)} class="btn h-8 px-3 text-sm flex items-center !m-0 !rounded-none" class:btn-neutral={!p.active}> <a href={pagesUrl(p.i)} onclick={preventDefault(() => (page = p.i))} class="btn h-8 px-3 text-sm flex items-center !m-0 !rounded-none" class:btn-neutral={!p.active}>
<span>{p.name}</span> <span>{p.name}</span>
</a> </a>
</li> </li>
@@ -94,10 +82,10 @@
</a> </a>
</li> </li>
<li> <li>
<a href={lastUrl} onclick={preventDefault(() => page = maxPage - 1)} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-l-none"> <a href={lastUrl} onclick={preventDefault(() => (page = maxPage - 1))} class="btn btn-neutral h-8 px-3 text-sm flex items-center !m-0 !rounded-l-none">
<span class="sr-only">Next</span> <span class="sr-only">Next</span>
<ChevronDoubleRightOutline class="w-3 h-3" /> <ChevronDoubleRightOutline class="w-3 h-3" />
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -1,5 +1,5 @@
{ {
"translationKey": "as", "translationKey": "as",
"main": true, "main": true,
"ranked": true "ranked": false
} }

View File

@@ -1,5 +1,5 @@
{ {
"translationKey": "microwg", "translationKey": "microwg",
"main": false, "main": false,
"ranked": true "ranked": false
} }

View File

@@ -1,5 +1,5 @@
{ {
"translationKey": "mwg", "translationKey": "mwg",
"main": true, "main": true,
"ranked": true "ranked": false
} }

View File

@@ -1,5 +1,5 @@
{ {
"translationKey": "mw", "translationKey": "mw",
"main": false, "main": false,
"ranked": true "ranked": false
} }

View File

@@ -1,5 +1,5 @@
{ {
"translationKey": "wg", "translationKey": "wg",
"main": true, "main": true,
"ranked": true "ranked": false
} }

View File

@@ -5,6 +5,7 @@ import { SEO } from "astro-seo";
import { ClientRouter } from "astro:transitions"; import { ClientRouter } from "astro:transitions";
const { title, description, clientSideRouter = true, autoDarkMode = true } = Astro.props.frontmatter || Astro.props; const { title, description, clientSideRouter = true, autoDarkMode = true } = Astro.props.frontmatter || Astro.props;
import "../../public/fonts/roboto/roboto.css"; import "../../public/fonts/roboto/roboto.css";
import "../../public/fonts/barlow-condensed/barlow-condensed.css";
--- ---
<html lang={astroI18n.locale} class="dark"> <html lang={astroI18n.locale} class="dark">
@@ -44,7 +45,7 @@ import "../../public/fonts/roboto/roboto.css";
{clientSideRouter && <ClientRouter />} {clientSideRouter && <ClientRouter />}
</head> </head>
<body class="dark:bg-zinc-800"> <body>
<slot /> <slot />
</body> </body>
</html> </html>
@@ -54,5 +55,11 @@ import "../../public/fonts/roboto/roboto.css";
font-family: "Roboto", sans-serif; font-family: "Roboto", sans-serif;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
background-color: #080808;
color: #f5f5f5;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Barlow Condensed", sans-serif;
} }
</style> </style>

View File

@@ -12,6 +12,9 @@ import Navbar from "@components/Navbar.svelte";
import ServerStatus from "../components/ServerStatus.svelte"; import ServerStatus from "../components/ServerStatus.svelte";
const { title, description, transparentFooter = true } = Astro.props; const { title, description, transparentFooter = true } = Astro.props;
const footerCol = "flex flex-col min-w-40 [&_a]:text-neutral-400/85 [&_a]:text-[0.85rem] [&_a]:leading-[1.9] [&_a]:no-underline [&_a]:transition-colors [&_a]:duration-200 [&_a:hover]:text-amber-500";
const footerH1 = "font-display text-[0.7rem] font-bold tracking-[0.2em] uppercase text-amber-500 mb-3";
--- ---
<Basic title={title} description={description} autoDarkMode={false}> <Basic title={title} description={description} autoDarkMode={false}>
@@ -24,17 +27,15 @@ const { title, description, transparentFooter = true } = Astro.props;
<main class="flex-1" data-pagefind-body> <main class="flex-1" data-pagefind-body>
<slot /> <slot />
</main> </main>
<footer <footer class="w-full max-w-[75em] mx-auto mt-16 px-4 pb-4 bg-[rgba(8,8,8,0.85)]">
class={`min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col ${transparentFooter ? "backdrop-blur-3xl" : "bg-neutral-900"}`} <div class="h-px bg-gradient-to-r from-transparent via-amber-500/30 to-transparent mb-10"></div>
style="width: min(100%, 75em); margin-left: auto; margin-right: auto;" <div class="flex justify-evenly items-start flex-wrap gap-8">
> <div class={`${footerCol} [&_h2]:text-[0.85rem] [&_h2]:text-neutral-400/85 [&_h2]:leading-[1.9]`}>
<div class="flex-1 flex justify-evenly items-center md:items-start mt-4 md:flex-row flex-col gap-y-4"> <h1 class={footerH1}>Serverstatus</h1>
<div class="footer-card">
<h1>Serverstatus</h1>
<ServerStatus client:only="svelte" /> <ServerStatus client:only="svelte" />
</div> </div>
<div class="footer-card"> <div class={footerCol}>
<h1>Links</h1> <h1 class={footerH1}>Links</h1>
<a href={l("/")}>{t("navbar.links.home.title")}</a> <a href={l("/")}>{t("navbar.links.home.title")}</a>
<a href={l("/join")}>{t("footer.join")}</a> <a href={l("/join")}>{t("footer.join")}</a>
<a href={l("/announcements")}>{t("footer.announcements")}</a> <a href={l("/announcements")}>{t("footer.announcements")}</a>
@@ -44,8 +45,8 @@ const { title, description, transparentFooter = true } = Astro.props;
<a href={l("/privacy-policy")}>{t("footer.privacy")}</a> <a href={l("/privacy-policy")}>{t("footer.privacy")}</a>
<a href={l("/imprint")}>{t("footer.imprint")}</a> <a href={l("/imprint")}>{t("footer.imprint")}</a>
</div> </div>
<div class="footer-card"> <div class={footerCol}>
<h1>Social Media</h1> <h1 class={footerH1}>Social Media</h1>
<a class="flex" href="/youtube"> <a class="flex" href="/youtube">
<YoutubeSolid class="mr-2" /> <YoutubeSolid class="mr-2" />
YouTube</a YouTube</a
@@ -60,26 +61,8 @@ const { title, description, transparentFooter = true } = Astro.props;
> >
</div> </div>
</div> </div>
<span class="text-sm text-white text-center mt-1">© SteamWar.de - Made with ❤️ by Chaoscaot</span> <span class="block text-center text-[0.7rem] tracking-[0.15em] text-neutral-400/40 mt-10">© SteamWar.de</span>
</footer> </footer>
</div> </div>
</Fragment> </Fragment>
</Basic> </Basic>
<style>
.footer-card {
@apply w-40 text-gray-400 flex flex-col;
> h1 {
@apply text-xl font-bold text-gray-100;
}
> a {
@apply hover:underline;
}
}
.match {
width: min(100vw, 70em);
}
</style>

View File

@@ -10,11 +10,22 @@ const { title, description, wide = false } = Astro.props;
<div class="h-screen w-screen fixed -z-10"> <div class="h-screen w-screen fixed -z-10">
<BackgroundImage /> <BackgroundImage />
</div> </div>
<div <div class="sw-page-content" style={wide ? "width: clamp(80%, 75em, 100%);" : "width: min(100%, 75em);"}>
class="mx-auto p-8 rounded-b-md border-x-gray-100 shadow-md pt-14 relative
text-white backdrop-blur-3xl"
style={wide ? "width: clamp(80%, 75em, 100%);" : "width: min(100%, 75em);"}
>
<slot /> <slot />
</div> </div>
</NavbarLayout> </NavbarLayout>
<style>
.sw-page-content {
margin: 0 auto;
padding: 3.5rem 2rem 2rem;
position: relative;
color: #f5f5f5;
background: rgba(8, 8, 8, 0.88);
backdrop-filter: blur(24px);
border-left: 1px solid rgba(255, 255, 255, 0.04);
border-right: 1px solid rgba(255, 255, 255, 0.04);
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
min-height: 60vh;
}
</style>

View File

@@ -1,19 +1,17 @@
--- ---
import {type CollectionEntry, getCollection} from "astro:content"; import { type CollectionEntry, getCollection } from "astro:content";
import {astroI18n, createGetStaticPaths} from "astro-i18n"; import { astroI18n, createGetStaticPaths } from "astro-i18n";
import PageLayout from "../layouts/PageLayout.astro"; import PageLayout from "../layouts/PageLayout.astro";
import LanguageWarning from "../components/LanguageWarning.astro"; import LanguageWarning from "../components/LanguageWarning.astro";
import "@styles/table.css"; import "@styles/table.css";
export const getStaticPaths = createGetStaticPaths(async () => { export const getStaticPaths = createGetStaticPaths(async () => {
let posts = await getCollection("pages", value => value.id.split("/")[0] === astroI18n.locale); let posts = await getCollection("pages", (value) => value.id.split("/")[0] === astroI18n.locale);
const germanPosts = await getCollection("pages", entry => entry.id.split("/")[0] === astroI18n.fallbackLocale); const germanPosts = await getCollection("pages", (entry) => entry.id.split("/")[0] === astroI18n.fallbackLocale);
germanPosts.forEach(value => { germanPosts.forEach((value) => {
if (posts.find(post => post.id.split("/")[1] === value.id.split("/")[1])) { if (posts.find((post) => post.id.split("/")[1] !== value.id.split("/")[1])) {
return;
} else {
posts.push(value); posts.push(value);
} }
}); });
@@ -41,17 +39,15 @@ export const getStaticPaths = createGetStaticPaths(async () => {
})); }));
}); });
const {page, german} = Astro.props as { page: CollectionEntry<"pages">, german: boolean }; const { page, german } = Astro.props as { page: CollectionEntry<"pages">; german: boolean };
const {Content} = await page.render(); const { Content } = await page.render();
--- ---
<PageLayout title={page.data.title}> <PageLayout title={page.data.title}>
<article> <article>
{german && ( {german && <LanguageWarning />}
<LanguageWarning/> <h1 class="text-left" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;">{page.data.title}</h1>
)} <Content />
<h1 class="text-left">{page.data.title}</h1>
<Content/>
</article> </article>
</PageLayout> </PageLayout>
@@ -62,15 +58,28 @@ const {Content} = await page.render();
} }
code { code {
@apply dark:text-neutral-400 text-neutral-800; color: #fbbf24;
background: rgba(245, 158, 11, 0.08);
padding: 0.15em 0.4em;
} }
pre.astro-code { pre.astro-code {
@apply w-fit p-4 rounded-md border-2 border-gray-600 my-4; width: fit-content;
padding: 1rem;
margin: 1rem 0;
border: 1px solid rgba(255, 255, 255, 0.06);
background: #080808 !important;
} }
a { a {
@apply text-neutral-800 dark:text-neutral-400 hover:underline; color: #f59e0b;
text-decoration: none;
border-bottom: 1px solid transparent;
transition: border-color 0.2s ease;
}
a:hover {
border-bottom-color: #f59e0b;
} }
} }
</style> </style>

View File

@@ -73,13 +73,13 @@ const ogImage = await getImage({
{ {
post.data.image && ( post.data.image && (
<div class="absolute top-0 left-0 w-full aspect-video flex justify-center"> <div class="absolute top-0 left-0 w-full aspect-video flex justify-center">
<Image src={post.data.image} height="1080" alt="" transition:name={post.data.title + "-image"} class="rounded-2xl linear-fade object-contain h-full" /> <Image src={post.data.image} height="1080" alt="" transition:name={post.data.title + "-image"} class="linear-fade object-contain h-full" />
</div> </div>
) )
} }
<div class={post.data.image ? "absolute bottom-8 left-2" : "mb-4"}> <div class={post.data.image ? "absolute bottom-8 left-2" : "mb-4"}>
<h1 class="text-4xl mb-0" transition:name={post.data.title + "-title"}>{post.data.title}</h1> <h1 class="text-4xl mb-0" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;" transition:name={post.data.title + "-title"}>{post.data.title}</h1>
<div class="flex items-center mt-2 text-neutral-800 dark:text-neutral-300"> <div class="flex items-center mt-2 text-gray-400">
<TagSolid class="w-4 h-4 mr-2" /> <TagSolid class="w-4 h-4 mr-2" />
<div transition:name={post.data.title + "-tags"}> <div transition:name={post.data.title + "-tags"}>
{post.data.tags.map((tag) => <TagComponent tag={tag} />)} {post.data.tags.map((tag) => <TagComponent tag={tag} />)}
@@ -175,11 +175,17 @@ const ogImage = await getImage({
} }
code { code {
@apply dark:text-neutral-400 text-neutral-800; color: #fbbf24;
background: rgba(245, 158, 11, 0.08);
padding: 0.15em 0.4em;
} }
pre.astro-code { pre.astro-code {
@apply w-fit p-4 rounded-md border-2 border-gray-600 my-4; width: fit-content;
padding: 1rem;
margin: 1rem 0;
border: 1px solid rgba(255, 255, 255, 0.06);
background: #080808 !important;
} }
} }
</style> </style>

View File

@@ -1,31 +1,44 @@
--- ---
import PageLayout from "../layouts/PageLayout.astro"; import PageLayout from "../layouts/PageLayout.astro";
import {getCollection} from "astro:content"; import { getCollection } from "astro:content";
import {t} from "astro-i18n"; import { t } from "astro-i18n";
import {l} from "../util/util"; import { l } from "../util/util";
const downloads = await getCollection("downloads"); const downloads = await getCollection("downloads");
--- ---
<PageLayout title="Downloads"> <PageLayout title="Downloads">
{downloads.map(e => ( {
<div class="pt-4"> downloads.map((e) => (
<h1 class="font-bold text-2xl">{e.data.name}</h1> <div class="pt-6 pb-4" style="border-bottom: 1px solid rgba(255,255,255,0.04);">
<div class="py-4">{t(e.data.description)}</div> <h1 class="font-bold text-2xl" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.06em;">
{e.data.name}
</h1>
<div class="py-3 text-gray-400 text-sm">{t(e.data.description)}</div>
<div class="flex flex-col"> <div class="flex flex-col gap-1">
{typeof e.data.url === "object" ? {typeof e.data.url === "object" ? (
Object.entries(e.data.url).map(value => ( Object.entries(e.data.url).map((value) => (
<a href={value[1].startsWith("/") ? l(value[1]) : value[1]} <a
class="text-blue-500 hover:underline w-fit">{t(value[0])}</a> href={value[1].startsWith("/") ? l(value[1]) : value[1]}
)) class="text-amber-400 hover:text-amber-300 w-fit text-sm"
: style="border-bottom: 1px solid transparent; transition: border-color 0.2s;"
<a href={e.data.url} class="text-blue-500 hover:underline w-fit">{t("Download")}</a> >
} {t(value[0])}
{e.data.sourceUrl ? </a>
<a class="text-blue-500 hover:underline w-fit" href={e.data.sourceUrl}>Quelle</a> ))
: null} ) : (
<a href={e.data.url} class="text-amber-400 hover:text-amber-300 w-fit text-sm">
{t("Download")}
</a>
)}
{e.data.sourceUrl ? (
<a class="text-amber-400 hover:text-amber-300 w-fit text-sm" href={e.data.sourceUrl}>
Quelle
</a>
) : null}
</div>
</div> </div>
</div> ))
))} }
</PageLayout> </PageLayout>

View File

@@ -1,27 +1,29 @@
--- ---
import {getCollection} from "astro:content"; import { getCollection } from "astro:content";
import {astroI18n} from "astro-i18n"; import { astroI18n } from "astro-i18n";
import {l} from "../../util/util"; import { l } from "../../util/util";
import PageLayout from "../../layouts/PageLayout.astro"; import PageLayout from "../../layouts/PageLayout.astro";
let posts = await getCollection("help", entry => entry.id.split("/")[0] === astroI18n.locale); let posts = await getCollection("help", (entry) => entry.id.split("/")[0] === astroI18n.locale);
--- ---
<PageLayout title="Helpcenter"> <PageLayout title="Helpcenter">
<div> <div>
<h1>Helpcenter</h1> <h1 style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.06em;">Helpcenter</h1>
<h1 class="text-red-700 text-4xl">WIP!</h1> <h1 class="text-amber-500 text-2xl" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.2em; text-transform: uppercase; font-size: 0.8rem;">WIP</h1>
{posts.map(value => ( {
<a href={l("/help/" + value.slug)}> posts.map((value) => (
<h2>{value.data.title}</h2> <a href={l("/help/" + value.slug)} class="block py-2 text-gray-400 hover:text-amber-400 transition-colors">
</a> <h2>{value.data.title}</h2>
))} </a>
))
}
</div> </div>
</PageLayout> </PageLayout>
<style> <style>
div { div {
@apply mx-auto bg-gray-100 px-4 py-8 rounded-b-md shadow-md pt-40 sm:pt-28 md:pt-14 margin: 0 auto;
dark:text-white dark:bg-neutral-900; padding: 3.5rem 1rem 2rem;
} }
</style> </style>

View File

@@ -3,9 +3,7 @@ import dayjs from "dayjs";
import NavbarLayout from "@layouts/NavbarLayout.astro"; import NavbarLayout from "@layouts/NavbarLayout.astro";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import { astroI18n } from "astro-i18n"; import { astroI18n } from "astro-i18n";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import Card from "@components/Card.svelte";
import { CaretRight, Pause, Rocket, Crosshair1 } from "@astropub/icons"; import { CaretRight, Pause, Rocket, Crosshair1 } from "@astropub/icons";
import { t } from "astro-i18n"; import { t } from "astro-i18n";
import { l } from "@utils/util"; import { l } from "@utils/util";
@@ -31,194 +29,217 @@ germanPosts.forEach((value) => {
const latestPost = posts.sort((a, b) => dayjs(b.data.created).unix() - dayjs(a.data.created).unix()).at(0); const latestPost = posts.sort((a, b) => dayjs(b.data.created).unix() - dayjs(a.data.created).unix()).at(0);
const prefixColorMap: { const prefixColors: { [key: string]: string } = {
[key: string]: string; Admin: "#dc2626",
} = { Dev: "#0284c7",
Admin: "border-red-600 dark:border-red-800 shadow-red-600 dark:shadow-red-800", Mod: "#d97706",
Dev: "border-sky-600 dark:border-sky-800 shadow-sky-600 dark:shadow-sky-800", Sup: "#1d4ed8",
Mod: "border-amber-600 dark:border-amber-800 shadow-amber-600 dark:shadow-amber-800", Arch: "#22c55e",
Sup: "border-blue-700 dark:border-blue-900 shadow-blue-700 dark:shadow-blue-900",
Arch: "border-green-500 dark:border-green-700 shadow-green-500 dark:shadow-green-700",
}; };
const featClass =
"flex flex-col items-center text-center py-10 px-8 border-b border-white/[0.04] md:border-b-0 md:border-r md:last:border-r-0 transition-[background] duration-[400ms] hover:bg-amber-500/[0.025] [&_svg]:text-amber-500 [&_svg]:mb-5 [&_svg]:transition-transform [&_svg]:duration-300 [&:hover_svg]:scale-[1.15]";
const featNum = "font-display text-[3.5rem] font-extrabold leading-none text-amber-500/10 mb-5";
const featH3 = "font-display text-[1.15rem] font-bold text-white tracking-[0.08em] uppercase mb-3";
const featP = "text-neutral-400/85 text-[0.85rem] leading-[1.65] mt-1";
--- ---
<NavbarLayout title={t("home.page")} description="SteamWar.de Homepage" transparentFooter={false}> <NavbarLayout title={t("home.page")} description="SteamWar.de Homepage" transparentFooter={false}>
<div class="w-full h-screen relative mb-4 z-10"> <section class="relative w-full h-screen overflow-hidden">
<div style="height: calc(100vh + 1rem)"> <div class="absolute inset-0 h-[calc(100vh+1rem)]">
<BackgroundImage /> <BackgroundImage />
</div> </div>
<drop-in class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center"> <div class="absolute inset-0 z-[2] [background:linear-gradient(to_bottom,rgba(0,0,0,0.6)_0%,rgba(0,0,0,0.2)_35%,rgba(0,0,0,0.45)_65%,#080808_100%)]"></div>
<h1 <div class="absolute inset-0 z-[3] pointer-events-none [background:repeating-linear-gradient(0deg,transparent_0px,transparent_3px,rgba(0,0,0,0.06)_3px,rgba(0,0,0,0.06)_4px)]"></div>
class="text-4xl sm:text-6xl md:text-8xl font-extrabold text-white -translate-y-16 opacity-0 barlow tracking-wider"
style="transition: transform .7s ease-out, opacity .7s linear; filter: drop-shadow(2px 2px 5px black);" <div class="relative z-10 h-full flex flex-col items-center justify-center px-4">
> <h1 class="font-display text-[clamp(4.5rem,14vw,14rem)] font-black leading-[0.85] tracking-[-0.03em] select-none animate-[scaleIn_0.9s_0.25s_ease-out_both]">
<span class="bg-gradient-to-tr from-yellow-400 to-yellow-300 bg-clip-text text-transparent">{t("home.title.first")}</span><span class="text-neutral-600">{t("home.title.second")}</span> <span class="hero-text text-transparent">{t("home.title.first")}</span><span class="text-white [text-shadow:0_0_80px_rgba(255,255,255,0.06)]">{t("home.title.second")}</span>
</h1> </h1>
<text-carousel class="h-20 w-full relative select-none">
<h2 class="-translate-y-16">{t("home.subtitle.1")}</h2> <div class="w-0 h-0.5 bg-gradient-to-r from-transparent via-amber-500 to-transparent my-7 animate-[lineGrow_0.6s_0.7s_ease-out_both]"></div>
<text-carousel class="h-10 sm:h-12 w-full max-w-[28rem] sm:max-w-[32rem] relative pointer-events-none">
<h2>{t("home.subtitle.1")}</h2>
<h2> <h2>
{t("home.subtitle.2")} {t("home.subtitle.2")}
<PlayerCount client:idle /> <PlayerCount client:idle />
</h2> </h2>
<h2>{t("home.subtitle.3")}</h2> <h2>{t("home.subtitle.3")}</h2>
</text-carousel> </text-carousel>
<a href={l("join")} class="btn btn-ghost mt-32 px-8 flex" style="animation: normal flyIn forwards 1.2s ease-out"
>{t("home.join")} <a
<CaretRight width="24" height="24" /> href={l("join")}
class="inline-flex items-center gap-[0.6rem] mt-12 py-[0.8rem] px-[2.8rem] border border-amber-500/50 text-amber-400 font-display text-[0.85rem] tracking-[0.2em] uppercase bg-amber-500/[0.04] backdrop-blur-[12px] [clip-path:polygon(0_0,calc(100%-14px)_0,100%_14px,100%_100%,14px_100%,0_calc(100%-14px))] transition-all duration-[350ms] animate-[fadeUp_0.6s_1s_ease-out_both] hover:bg-amber-500/[0.12] hover:border-amber-500/85 hover:text-white hover:[box-shadow:0_0_40px_rgba(245,158,11,0.1),inset_0_0_40px_rgba(245,158,11,0.03)] hover:scale-[1.04] active:scale-[0.97]"
>
<span>{t("home.join")}</span>
<CaretRight width="18" height="18" />
</a> </a>
<style> </div>
@keyframes flyIn {
0% {
translate: 0 16px;
opacity: 0;
}
20% { <div class="absolute bottom-6 left-1/2 -translate-x-1/2 z-20 w-full max-w-[40rem] px-4">
translate: 0 16px;
opacity: 0;
}
100% {
translate: 0;
opacity: 1;
}
}
</style>
<script>
class TextCarousel extends HTMLElement {
current = 0;
connectedCallback() {
this._current.classList.add("!opacity-100");
for (let i = 0; i < this.children.length; i++) {
if (i !== this.current) {
this.children[i].classList.add("translate-y-8");
}
}
setInterval(() => {
this.next();
}, 5000);
}
get _current() {
return this.children[this.current];
}
next() {
this._current.classList.remove("!opacity-100");
this._current.classList.add("translate-y-8");
this._current.classList.remove("!delay-500");
this.current = (this.current + 1) % this.children.length;
this._current.classList.add("!opacity-100");
this._current.classList.remove("translate-y-8");
this._current.classList.add("!delay-500");
}
}
class DropIn extends HTMLElement {
connectedCallback() {
for (let child of this.children) {
if (child.classList.contains("opacity-0")) {
child.classList.remove("opacity-0");
child.classList.remove("-translate-y-16");
} else {
child.children[0].classList.remove("opacity-0");
child.children[0].classList.remove("-translate-y-16");
}
}
}
}
customElements.define("text-carousel", TextCarousel);
customElements.define("drop-in", DropIn);
</script>
</drop-in>
<div class="absolute left-1/2 bottom-6 -translate-x-1/2">
<PostComponent post={latestPost} slim={true} /> <PostComponent post={latestPost} slim={true} />
</div> </div>
</div> </section>
<section class="w-full flex flex-col items-center justify-center shadow-2xl rounded-b-2xl pb-8">
<div class="py-10 flex flex-col lg:flex-row"> <section class="relative bg-[#080808] overflow-hidden">
<Card client:idle> <div class="absolute -top-[100px] left-1/2 -translate-x-1/2 w-[500px] h-[250px] [background:radial-gradient(ellipse,rgba(245,158,11,0.06)_0%,transparent_70%)] pointer-events-none"></div>
<Crosshair1 height="64" width="64" /> <div class="relative z-[2] max-w-5xl mx-auto py-24 px-4">
<h1>{t("home.benefits.fights.title")}</h1> <div class="grid grid-cols-1 md:grid-cols-3">
<p class="mt-4">{t("home.benefits.fights.description.1")}</p> <article class={featClass}>
<p class="mt-4">{t("home.benefits.fights.description.2")}</p> <span class={featNum}>01</span>
</Card> <Crosshair1 height="36" width="36" />
<Card client:idle> <h3 class={featH3}>{t("home.benefits.fights.title")}</h3>
<Rocket height="64" width="64" /> <p class={featP}>{t("home.benefits.fights.description.1")}</p>
<h1>{t("home.benefits.bau.title")}</h1> <p class={featP}>{t("home.benefits.fights.description.2")}</p>
<p class="mt-4">{t("home.benefits.bau.description")}</p> </article>
</Card>
<Card client:idle> <article class={featClass}>
<Pause height="64" width="64" /> <span class={featNum}>02</span>
<h1>{t("home.benefits.minigames.title")}</h1> <Rocket height="36" width="36" />
<p class="mt-4">{t("home.benefits.minigames.description.1")}</p> <h3 class={featH3}>{t("home.benefits.bau.title")}</h3>
<p class="mt-4">{t("home.benefits.minigames.description.2")}</p> <p class={featP}>{t("home.benefits.bau.description")}</p>
</Card> </article>
<article class={featClass}>
<span class={featNum}>03</span>
<Pause height="36" width="36" />
<h3 class={featH3}>{t("home.benefits.minigames.title")}</h3>
<p class={featP}>{t("home.benefits.minigames.description.1")}</p>
<p class={featP}>{t("home.benefits.minigames.description.2")}</p>
</article>
</div>
</div> </div>
</section> </section>
<section class="w-full py-12 flex flex-wrap justify-center">
{ <section class="bg-[#0c0c0c] py-20">
Object.entries(teamMember).map(([prefix, players]) => ( <div class="max-w-5xl mx-auto px-4 flex flex-wrap gap-4">
<Fragment> {
{players.map((v, index) => ( Object.entries(teamMember).map(([prefix, players]) => (
<div class="inline-flex flex-col justify-end"> <Fragment>
{index == 0 ? <h2 class="dark:text-white text-4xl font-bold text-center md:text-left md:pl-4">{t("home.prefix." + prefix)}</h2> : null} {players.map((v, i) => (
<Card extraClasses={`pt-8 pb-10 px-8 w-fit shadow-md ${prefixColorMap[prefix]}`} client:idle> <div class="flex flex-col justify-end">
<figure class="flex flex-col items-center" style="width: 150px"> {i === 0 && (
<figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption> <h2 class="font-display text-[1.35rem] font-bold tracking-[0.12em] uppercase text-[var(--rc)] pb-1 inline-block" style={`--rc: ${prefixColors[prefix] || "#666"}`}>
{t("home.prefix." + prefix)}
</h2>
)}
<div
class="flex flex-col items-center py-5 px-6 bg-white/[0.025] border-t-2 border-[var(--rc)] transition-[transform,background,box-shadow] duration-300 hover:-translate-y-1.5 hover:bg-white/5 hover:shadow-[0_12px_32px_rgba(0,0,0,0.5)] [&:hover_img]:scale-[1.08]"
style={`--rc: ${prefixColors[prefix] || "#666"}`}
>
<Image <Image
src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${v.uuid}`} src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${v.uuid}`}
class="transition duration-300 ease-in-out hover:scale-110 hover:backdrop-blur-lg hover:drop-shadow-2xl" alt={v.name}
alt={v.name + "s bust"} width="120"
width="150" height="120"
height="150" class="w-[120px] h-[120px] transition-transform duration-300"
/> />
</figure> <span class="font-display mt-3 text-[0.9rem] tracking-[0.1em] text-neutral-300/90">{v.name}</span>
</Card> </div>
</div> </div>
))} ))}
</Fragment> </Fragment>
)) ))
} }
</div>
</section> </section>
<script>
class TextCarousel extends HTMLElement {
current = 0;
connectedCallback() {
this._current.classList.add("!opacity-100");
for (let i = 0; i < this.children.length; i++) {
if (i !== this.current) {
this.children[i].classList.add("translate-y-4");
}
}
setInterval(() => this.next(), 5000);
}
get _current() {
return this.children[this.current];
}
next() {
this._current.classList.remove("!opacity-100");
this._current.classList.add("translate-y-4");
this._current.classList.remove("!delay-500");
this.current = (this.current + 1) % this.children.length;
this._current.classList.add("!opacity-100");
this._current.classList.remove("translate-y-4");
this._current.classList.add("!delay-500");
}
}
customElements.define("text-carousel", TextCarousel);
</script>
</NavbarLayout> </NavbarLayout>
<style> <style>
text-carousel { .hero-text {
> * { background-image: linear-gradient(160deg, #fcd34d, #f59e0b);
@apply absolute top-0 left-0 w-full text-xl sm:text-4xl italic text-white text-center opacity-0; background-clip: text;
transition: }
transform 0.5s ease-out,
opacity 0.5s linear; text-carousel > * {
text-shadow: 2px 2px 5px black; position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: center;
opacity: 0;
font-family: "Barlow Condensed", sans-serif;
font-size: 0.95rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.5);
transform: translateY(8px);
transition:
transform 0.5s ease-out,
opacity 0.5s linear;
}
@media (min-width: 640px) {
text-carousel > * {
font-size: 1.15rem;
} }
} }
.barlow { @keyframes fadeUp {
font-family: from {
Barlow Condensed, opacity: 0;
sans-serif; transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
.card { @keyframes scaleIn {
@apply w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg transition-transform duration-300 ease-in-out from {
dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100 opacity: 0;
hover:scale-105; transform: scale(0.92);
> h1 {
@apply text-xl font-bold mt-4;
} }
to {
> p { opacity: 1;
@apply mt-4; transform: scale(1);
} }
}
> svg { @keyframes lineGrow {
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl; from {
width: 0;
opacity: 0;
}
to {
width: 6rem;
opacity: 1;
} }
} }
</style> </style>

View File

@@ -23,8 +23,7 @@ import BackgroundImage from "../components/BackgroundImage.astro";
<div class="h-screen w-screen fixed -z-10"> <div class="h-screen w-screen fixed -z-10">
<BackgroundImage /> <BackgroundImage />
</div> </div>
<div class="h-screen mx-auto p-8 rounded-b-md pt-40 sm:pt-28 md:pt-14 flex flex-col justify-center items-center <div class="h-screen mx-auto p-8 pt-40 sm:pt-28 md:pt-14 flex flex-col justify-center items-center" style="width: min(100vw, 75em);">
dark:text-white" style="width: min(100vw, 75em);">
<LoginComponent client:load /> <LoginComponent client:load />
</div> </div>
</NavbarLayout> </NavbarLayout>

View File

@@ -1,39 +1,12 @@
--- ---
import PageLayout from "../layouts/PageLayout.astro"; import PageLayout from "../layouts/PageLayout.astro";
import {t} from "astro-i18n"; import { t } from "astro-i18n";
--- ---
<PageLayout title={t("404.title")}> <PageLayout title={t("404.title")}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400"> <div class="flex flex-col items-center justify-center py-20">
<!-- 404 Text - heller für besseren Kontrast --> <span style="font-family: 'Barlow Condensed', sans-serif; font-size: clamp(6rem, 15vw, 12rem); font-weight: 900; line-height: 1; color: rgba(245, 158, 11, 0.1);">404</span>
<text x="400" y="150" font-family="Arial Black" font-size="120" fill="#ffffff" text-anchor="middle">404</text> <div style="width: 4rem; height: 2px; background: linear-gradient(90deg, transparent, #f59e0b, transparent); margin: 1.5rem 0;"></div>
<p style="font-family: 'Barlow Condensed', sans-serif; font-size: 1.1rem; letter-spacing: 0.15em; text-transform: uppercase; color: rgba(163, 163, 163, 0.7);">{t("404.description")}</p>
<!-- Trauriger Roboter - hellere Farben für besseren Kontrast --> </div>
<!-- Körper --> </PageLayout>
<rect x="350" y="200" width="100" height="120" rx="10" fill="#888888"/>
<!-- Kopf -->
<rect x="365" y="160" width="70" height="60" rx="5" fill="#888888"/>
<!-- Augen -->
<circle cx="385" cy="185" r="8" fill="#ff6b6b"/>
<circle cx="415" cy="185" r="8" fill="#ff6b6b"/>
<!-- Antenne -->
<line x1="400" y1="160" x2="400" y2="140" stroke="#888888" stroke-width="4"/>
<circle cx="400" cy="135" r="5" fill="#888888"/>
<!-- Arme -->
<rect x="320" y="220" width="30" height="10" rx="5" fill="#888888"/>
<rect x="450" y="220" width="30" height="10" rx="5" fill="#888888"/>
<!-- Text unter dem Roboter - heller für besseren Kontrast -->
<text x="400" y="360" font-family="Arial" font-size="24" fill="#ffffff" text-anchor="middle">{t("404.description")}</text>
<!-- Dekorative Elemente - heller für besseren Kontrast -->
<circle cx="250" cy="100" r="5" fill="#bbbbbb"/>
<circle cx="550" cy="150" r="5" fill="#bbbbbb"/>
<circle cx="200" cy="300" r="5" fill="#bbbbbb"/>
<circle cx="600" cy="250" r="5" fill="#bbbbbb"/>
</svg>
</PageLayout>

View File

@@ -1,5 +1,5 @@
--- ---
import {createGetStaticPaths} from "astro-i18n"; import { createGetStaticPaths } from "astro-i18n";
import { getCollection, CollectionEntry } from "astro:content"; import { getCollection, CollectionEntry } from "astro:content";
import PageLayout from "../../layouts/PageLayout.astro"; import PageLayout from "../../layouts/PageLayout.astro";
import PublicPreview from "@components/publics/PublicPreview.svelte"; import PublicPreview from "@components/publics/PublicPreview.svelte";
@@ -18,17 +18,17 @@ export const getStaticPaths = createGetStaticPaths(async () => {
})); }));
}); });
const { schem }: { schem: CollectionEntry<"publics">} = Astro.props; const { schem }: { schem: CollectionEntry<"publics"> } = Astro.props;
--- ---
<PageLayout title={schem.data.name}> <PageLayout title={schem.data.name}>
<h1 class="text-5xl font-bold w-fit" transition:name={schem.data.id + "-title"}>{schem.data.name}</h1> <h1 class="text-5xl font-bold w-fit" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;" transition:name={schem.data.id + "-title"}>{schem.data.name}</h1>
<PublicPreview client:idle pub={schem} imageHeight={schem.data.image.height}> <PublicPreview client:idle pub={schem} imageHeight={schem.data.image.height}>
<Image class="object-contain" transition:name={schem.data.id + "-img"} src={schem.data.alt || schem.data.image} alt={schem.data.name}></Image> <Image class="object-contain" transition:name={schem.data.id + "-img"} src={schem.data.alt || schem.data.image} alt={schem.data.name} />
</PublicPreview> </PublicPreview>
<!-- <!--
<p transition:name={schem.data.id + "-desc"}>{schem.data.description}</p> <p transition:name={schem.data.id + "-desc"}>{schem.data.description}</p>
<p> <p>
Erbauer: {schem.data.creator.join(", ")} Erbauer: {schem.data.creator.join(", ")}
</p>--> </p>-->
</PageLayout> </PageLayout>

View File

@@ -1,18 +1,18 @@
--- ---
import {createGetStaticPaths} from "astro-i18n"; import { createGetStaticPaths } from "astro-i18n";
import PageLayout from "../../layouts/PageLayout.astro"; import PageLayout from "../../layouts/PageLayout.astro";
import {getCollection} from "astro:content"; import { getCollection } from "astro:content";
import {l} from "../../util/util"; import { l } from "../../util/util";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import Card from "@components/Card.svelte"; import Card from "@components/Card.svelte";
import XRayPreview from "@components/publics/XRayPreview.svelte"; import XRayPreview from "@components/publics/XRayPreview.svelte";
import {t} from "astro-i18n"; import { t } from "astro-i18n";
const { mode } = Astro.props; const { mode } = Astro.props;
export const getStaticPaths = createGetStaticPaths(async () => { export const getStaticPaths = createGetStaticPaths(async () => {
const gamemodes = await getCollection("modes"); const gamemodes = await getCollection("modes");
return gamemodes.map((entry) => ({ return gamemodes.map((entry) => ({
props: { props: {
mode: entry, mode: entry,
@@ -23,24 +23,28 @@ export const getStaticPaths = createGetStaticPaths(async () => {
})); }));
}); });
const publics = await getCollection("publics", entry => entry.data.gamemode.id == mode.id); const publics = await getCollection("publics", (entry) => entry.data.gamemode.id == mode.id);
--- ---
<PageLayout title="Publics"> <PageLayout title="Publics">
<h1 class="text-4xl font-bold">{t(`${mode.data.translationKey}.title`)}</h1> <h1 class="text-4xl font-bold" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;">{t(`${mode.data.translationKey}.title`)}</h1>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2 mt-4">
{publics.map((pub) => ( {
<a href={l("/publics/" + pub.id)}> publics.map((pub) => (
<Card extraClasses="w-full m-0" hoverEffect={false}> <a href={l("/publics/" + pub.id)}>
<div class="flex justify-center"> <Card extraClasses="w-full m-0 border-t-2 border-t-amber-500/30" hoverEffect={false}>
<XRayPreview client:load> <div class="flex justify-center">
<Image style="width: 500px" slot="normal" src={pub.data.image} alt={pub.data.name} transition:name={pub.data.id + "-img"} /> <XRayPreview client:load>
{pub.data.xray && <Image slot="xray" class="bg-zinc-50 dark:bg-zinc-900" src={pub.data.xray} alt={pub.data.name} />} <Image style="width: 500px" slot="normal" src={pub.data.image} alt={pub.data.name} transition:name={pub.data.id + "-img"} />
</XRayPreview> {pub.data.xray && <Image slot="xray" src={pub.data.xray} alt={pub.data.name} />}
</div> </XRayPreview>
<h2 class="font-bold text-3xl" transition:name={pub.data.id + "-title"}>{pub.data.name}</h2> </div>
</Card> <h2 class="font-bold text-3xl" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;" transition:name={pub.data.id + "-title"}>
</a> {pub.data.name}
))} </h2>
</Card>
</a>
))
}
</div> </div>
</PageLayout> </PageLayout>

View File

@@ -1,13 +1,13 @@
--- ---
import {createGetStaticPaths, t} from "astro-i18n"; import { createGetStaticPaths, t } from "astro-i18n";
import {getCollection, type CollectionEntry} from "astro:content"; import { getCollection, type CollectionEntry } from "astro:content";
import PageLayout from "../../layouts/PageLayout.astro"; import PageLayout from "../../layouts/PageLayout.astro";
import EloTable from "../../components/EloTable.svelte"; import EloTable from "../../components/EloTable.svelte";
export const getStaticPaths = createGetStaticPaths(async () => { export const getStaticPaths = createGetStaticPaths(async () => {
const modes = await getCollection("modes", entry => entry.data.ranked); const modes = await getCollection("modes", (entry) => entry.data.ranked);
return modes.map(value => ({ return modes.map((value) => ({
props: { props: {
mode: value, mode: value,
}, },
@@ -21,10 +21,10 @@ interface Props {
mode: CollectionEntry<"modes">; mode: CollectionEntry<"modes">;
} }
const {mode} = Astro.props; const { mode } = Astro.props;
--- ---
<PageLayout title={t("ranking.heading", {mode: t(`${mode.data.translationKey}.title`)})}> <PageLayout title={t("ranking.heading", { mode: t(`${mode.data.translationKey}.title`) })}>
<h1 class="text-2xl mb-2">{t("ranking.heading", {mode: t(`${mode.data.translationKey}.title`)})}</h1> <h1 class="text-2xl mb-2" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.06em;">{t("ranking.heading", { mode: t(`${mode.data.translationKey}.title`) })}</h1>
<EloTable gamemode={mode.id} client:load/> <EloTable gamemode={mode.id} client:load />
</PageLayout> </PageLayout>

View File

@@ -1,19 +1,19 @@
--- ---
import {getCollection, type CollectionEntry} from "astro:content"; import { getCollection, type CollectionEntry } from "astro:content";
import {astroI18n, createGetStaticPaths, t} from "astro-i18n"; import { astroI18n, createGetStaticPaths, t } from "astro-i18n";
import PageLayout from "@layouts/PageLayout.astro"; import PageLayout from "@layouts/PageLayout.astro";
import LanguageWarning from "@components/LanguageWarning.astro"; import LanguageWarning from "@components/LanguageWarning.astro";
import EloTable from "@components/EloTable.svelte"; import EloTable from "@components/EloTable.svelte";
import {l} from "../../util/util"; import { l } from "../../util/util";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
export const getStaticPaths = createGetStaticPaths(async () => { export const getStaticPaths = createGetStaticPaths(async () => {
let posts = await getCollection("rules", value => value.id.split("/")[0] === astroI18n.locale); let posts = await getCollection("rules", (value) => value.id.split("/")[0] === astroI18n.locale);
const germanPosts = await getCollection("rules", entry => entry.id.split("/")[0] === astroI18n.fallbackLocale); const germanPosts = await getCollection("rules", (entry) => entry.id.split("/")[0] === astroI18n.fallbackLocale);
germanPosts.forEach(value => { germanPosts.forEach((value) => {
if (posts.find(post => post.id.split("/")[1] === value.id.split("/")[1])) { if (posts.find((post) => post.id.split("/")[1] === value.id.split("/")[1])) {
return; return;
} else { } else {
posts.push(value); posts.push(value);
@@ -27,53 +27,58 @@ export const getStaticPaths = createGetStaticPaths(async () => {
props: { props: {
page, page,
german: page.id.split("/")[0] != astroI18n.locale, german: page.id.split("/")[0] != astroI18n.locale,
mode: modes.find(value => value.id === page.id.split("/")[1].split(".")[0]), mode: modes.find((value) => value.id === page.id.split("/")[1].split(".")[0]),
publics: publics.filter(value => value.data.gamemode.id === page.id.split("/")[1].split(".")[0]), publics: publics.filter((value) => value.data.gamemode.id === page.id.split("/")[1].split(".")[0]),
}, },
params: { params: {
mode: page.slug.split("/")[1], mode: page.slug.split("/")[1],
}, },
}), }));
);
}); });
interface Props { interface Props {
page: CollectionEntry<"rules">, page: CollectionEntry<"rules">;
mode: CollectionEntry<"modes">, mode: CollectionEntry<"modes">;
publics: CollectionEntry<"publics">[], publics: CollectionEntry<"publics">[];
german: boolean german: boolean;
} }
const {page, german, mode, publics} = Astro.props; const { page, german, mode, publics } = Astro.props;
const {Content} = await page.render(); const { Content } = await page.render();
--- ---
<PageLayout title={t("rules.title", {mode: t(`${page.data.translationKey}.title`)})}> <PageLayout title={t("rules.title", { mode: t(`${page.data.translationKey}.title`) })}>
<h1 class="text-3xl font-bold">{t(`${page.data.translationKey}.title`)}</h1> <h1 class="text-3xl font-bold" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;">{t(`${page.data.translationKey}.title`)}</h1>
{mode && mode.data.ranked && ( {
<Fragment> mode && mode.data.ranked && (
<EloTable gamemode={page.id.split("/")[1].split(".")[0]} client:load topFive={true}/> <Fragment>
<a class="text-neutral-800 dark:text-neutral-400 hover:underline" href={l(`/rangliste/${page.id.split("/")[1].split(".")[0]}`)}>{t("rules.ranking")}</a> <EloTable gamemode={page.id.split("/")[1].split(".")[0]} client:load topFive={true} />
</Fragment> <a class="text-amber-400 hover:text-amber-300 transition-colors text-sm" href={l(`/rangliste/${page.id.split("/")[1].split(".")[0]}`)}>
)} {t("rules.ranking")}
{publics && publics.length != 0 && ( </a>
<Fragment> </Fragment>
<div class="flex overflow-x-scroll"> )
{publics.map(value => ( }
<a href={l(`/publics/${value.id}`)} style="display: contents"> {
<Image src={value.data.image} alt={value.data.name} height={300} width={300} class="drop-shadow"></Image> publics && publics.length != 0 && (
</a> <Fragment>
))} <div class="flex overflow-x-scroll gap-2 py-4">
</div> {publics.map((value) => (
<a class="text-neutral-800 dark:text-neutral-400 hover:underline" href={l(`/publics/${page.id.split("/")[1].split(".")[0]}`)}>{t("rules.publics")}</a> <a href={l(`/publics/${value.id}`)} style="display: contents">
</Fragment> <Image src={value.data.image} alt={value.data.name} height={300} width={300} />
)} </a>
))}
</div>
<a class="text-amber-400 hover:text-amber-300 transition-colors text-sm" href={l(`/publics/${page.id.split("/")[1].split(".")[0]}`)}>
{t("rules.publics")}
</a>
</Fragment>
)
}
<article> <article>
{german && ( {german && <LanguageWarning />}
<LanguageWarning/> <Content />
)}
<Content/>
</article> </article>
</PageLayout> </PageLayout>
@@ -84,15 +89,28 @@ const {Content} = await page.render();
} }
code { code {
@apply dark:text-neutral-400 text-neutral-800; color: #fbbf24;
background: rgba(245, 158, 11, 0.08);
padding: 0.15em 0.4em;
} }
pre.astro-code { pre.astro-code {
@apply w-fit p-4 rounded-md border-2 border-gray-600 my-4; width: fit-content;
padding: 1rem;
margin: 1rem 0;
border: 1px solid rgba(255, 255, 255, 0.06);
background: #080808 !important;
} }
a { a {
@apply text-neutral-800 dark:text-neutral-400 hover:underline; color: #f59e0b;
text-decoration: none;
border-bottom: 1px solid transparent;
transition: border-color 0.2s ease;
}
a:hover {
border-bottom-color: #f59e0b;
} }
} }

View File

@@ -1,48 +1,83 @@
--- ---
import {t} from "astro-i18n"; import { t } from "astro-i18n";
import {getCollection, type CollectionEntry} from "astro:content"; import { getCollection, type CollectionEntry } from "astro:content";
import PageLayout from "@layouts/PageLayout.astro"; import PageLayout from "@layouts/PageLayout.astro";
import {Image} from "astro:assets"; import { Image } from "astro:assets";
import {l} from "@utils/util"; import { l } from "@utils/util";
const imageMap = { const imageMap = {
"wg": await getRandomFromMode("wargear"), wg: await getRandomFromMode("wargear"),
"mwg": await getRandomFromMode("miniwargear"), mwg: await getRandomFromMode("miniwargear"),
"as": await getRandomFromMode("airship"), as: await getRandomFromMode("airship"),
"ws": await getRandomFromMode("warship"), ws: await getRandomFromMode("warship"),
"qg": await getRandomFromMode("quickgear"), qg: await getRandomFromMode("quickgear"),
}; };
async function getRandomFromMode(mode: "wargear" | "airship" | "megawargear" | "microwargear" | "miniwargear" | "quickgear" | "streetfight" | "warship"): Promise<CollectionEntry<"publics">> { async function getRandomFromMode(mode: "wargear" | "airship" | "megawargear" | "microwargear" | "miniwargear" | "quickgear" | "streetfight" | "warship"): Promise<CollectionEntry<"publics">> {
const publics = await getCollection("publics", entry => entry.data.gamemode.id === mode); const publics = await getCollection("publics", (entry) => entry.data.gamemode.id === mode);
return publics[Math.floor(Math.random() * publics.length)]; return publics[Math.floor(Math.random() * publics.length)];
} }
const modes = await getCollection("modes", entry => entry.data.main); const modes = await getCollection("modes", (entry) => entry.data.main);
--- ---
<PageLayout title={t("rules.page")}> <PageLayout title={t("rules.page")}>
{modes.map(value => ( {
<div class="dark:bg-neutral-800 rounded-md p-4 border border-neutral-400 shadow-md my-4 flex flex-col modes.map((value) => (
md:flex-row"> <div class="sw-mode-card">
<a href={l(`/publics/${imageMap[value.data.translationKey].id}`)}> <a href={l(`/publics/${imageMap[value.data.translationKey].id}`)}>
<Image height="200" width="200" src={imageMap[value.data.translationKey].data.image} <Image
alt={t("rules." + value.data.translationKey + ".title")} class="h-full aspect-square max-w-fit"></Image> height="200"
</a> width="200"
<div class="ml-4"> src={imageMap[value.data.translationKey].data.image}
<a href={l(`/rules/${value.id}`)}> alt={t("rules." + value.data.translationKey + ".title")}
<h1 class="text-2xl font-bold">{t(value.data.translationKey + ".title")}</h1> class="h-full aspect-square max-w-fit"
<div>{t("rules." + value.data.translationKey + ".description")}</div> />
</a> </a>
<div class="mt-2 flex flex-col"> <div class="ml-4">
<a href={l(`/publics/${value.id}`)} class="text-yellow-300 hover:underline w-fit">{t("rules.publics")}</a> <a href={l(`/rules/${value.id}`)}>
{value.data.ranked <h1 class="text-2xl font-bold" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;">
? <a href={l(`/ranked/${value.id}`)} {t(value.data.translationKey + ".title")}
class="text-yellow-300 hover:underline w-fit">{t("rules.ranking")}</a> </h1>
: null} <div class="text-gray-400 text-sm mt-1">{t("rules." + value.data.translationKey + ".description")}</div>
</a>
<div class="mt-2 flex flex-col gap-0.5">
<a href={l(`/publics/${value.id}`)} class="text-amber-400 hover:text-amber-300 w-fit text-sm transition-colors">
{t("rules.publics")}
</a>
{value.data.ranked ? (
<a href={l(`/ranked/${value.id}`)} class="text-amber-400 hover:text-amber-300 w-fit text-sm transition-colors">
{t("rules.ranking")}
</a>
) : null}
</div>
</div> </div>
</div> </div>
</div>))} ))
<a href={l("/rangliste/MissileWars")}>MissileWars Rangliste</a> }
</PageLayout> <a href={l("/rangliste/MissileWars")} class="text-amber-400 hover:text-amber-300 transition-colors">MissileWars Rangliste</a>
</PageLayout>
<style>
.sw-mode-card {
display: flex;
flex-direction: column;
padding: 1.25rem;
margin: 1rem 0;
border: 1px solid rgba(255, 255, 255, 0.06);
border-top: 2px solid rgba(245, 158, 11, 0.3);
background: rgba(255, 255, 255, 0.02);
transition: background 0.3s ease;
}
.sw-mode-card:hover {
background: rgba(245, 158, 11, 0.03);
}
@media (min-width: 768px) {
.sw-mode-card {
flex-direction: row;
}
}
</style>

View File

@@ -1,10 +1,10 @@
--- ---
import PageLayout from "../../layouts/PageLayout.astro"; import PageLayout from "../../layouts/PageLayout.astro";
import FightStatistics from "../../components/FightStatistics.svelte"; import FightStatistics from "../../components/FightStatistics.svelte";
import {t} from "astro-i18n"; import { t } from "astro-i18n";
--- ---
<PageLayout title={t("stats.title")}> <PageLayout title={t("stats.title")}>
<h1>{t("stats.title")}</h1> <h1 style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.06em;">{t("stats.title")}</h1>
<FightStatistics client:only="svelte"/> <FightStatistics client:only="svelte" />
</PageLayout> </PageLayout>

View File

@@ -23,67 +23,77 @@
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 3.1%;
--foreground: 240 10% 3.9%; --foreground: 0 0% 96%;
--muted: 240 4.8% 95.9%; --muted: 0 0% 10%;
--muted-foreground: 240 3.8% 46.1%; --muted-foreground: 0 0% 55%;
--popover: 0 0% 100%; --popover: 0 0% 5%;
--popover-foreground: 240 10% 3.9%; --popover-foreground: 0 0% 96%;
--card: 0 0% 100%; --card: 0 0% 5%;
--card-foreground: 240 10% 3.9%; --card-foreground: 0 0% 96%;
--border: 240 5.9% 90%; --border: 0 0% 12%;
--input: 240 5.9% 90%; --input: 0 0% 12%;
--primary: 240 5.9% 10%; --primary: 38 92% 50%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 4%;
--secondary: 240 4.8% 95.9%; --secondary: 0 0% 10%;
--secondary-foreground: 240 5.9% 10%; --secondary-foreground: 0 0% 96%;
--accent: 240 4.8% 95.9%; --accent: 38 92% 50%;
--accent-foreground: 240 5.9% 10%; --accent-foreground: 0 0% 4%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
--ring: 240 5% 64.9%; --ring: 38 92% 50%;
--radius: 0.5rem; --radius: 0;
--sw-bg: #080808;
--sw-bg-raised: #0c0c0c;
--sw-bg-surface: #111111;
--sw-amber: #f59e0b;
--sw-amber-light: #fbbf24;
--sw-amber-dim: rgba(245, 158, 11, 0.15);
--sw-text: #f5f5f5;
--sw-text-muted: #a3a3a3;
--sw-border: rgba(255, 255, 255, 0.06);
} }
.dark { .dark {
--background: 240 10% 3.9%; --background: 0 0% 3.1%;
--foreground: 0 0% 98%; --foreground: 0 0% 96%;
--muted: 240 3.7% 15.9%; --muted: 0 0% 10%;
--muted-foreground: 240 5% 64.9%; --muted-foreground: 0 0% 55%;
--popover: 240 10% 3.9%; --popover: 0 0% 5%;
--popover-foreground: 0 0% 98%; --popover-foreground: 0 0% 96%;
--card: 240 10% 3.9%; --card: 0 0% 5%;
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 96%;
--border: 240 3.7% 15.9%; --border: 0 0% 12%;
--input: 240 3.7% 15.9%; --input: 0 0% 12%;
--primary: 0 0% 98%; --primary: 38 92% 50%;
--primary-foreground: 240 5.9% 10%; --primary-foreground: 0 0% 4%;
--secondary: 240 3.7% 15.9%; --secondary: 0 0% 10%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 0 0% 96%;
--accent: 240 3.7% 15.9%; --accent: 38 92% 50%;
--accent-foreground: 0 0% 98%; --accent-foreground: 0 0% 4%;
--destructive: 0 62.8% 30.6%; --destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%; --destructive-foreground: 0 85.7% 97.3%;
--ring: 240 3.7% 15.9%; --ring: 38 92% 50%;
} }
} }
@@ -93,6 +103,13 @@
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
background-color: var(--sw-bg);
color: var(--sw-text);
}
::selection {
background: rgba(245, 158, 11, 0.3);
color: #fff;
} }
article { article {
@@ -101,15 +118,26 @@
} }
code { code {
@apply dark:text-neutral-400 text-neutral-800; color: var(--sw-amber-light);
background: rgba(245, 158, 11, 0.08);
padding: 0.15em 0.4em;
} }
pre.astro-code { pre.astro-code {
@apply w-fit p-4 rounded-md border-2 border-gray-600 my-4; @apply w-fit p-4 my-4;
border: 1px solid var(--sw-border);
background: var(--sw-bg) !important;
} }
a { a {
@apply text-neutral-800 dark:text-neutral-400 hover:underline; color: var(--sw-amber);
text-decoration: none;
border-bottom: 1px solid transparent;
transition: border-color 0.2s ease;
}
a:hover {
border-bottom-color: var(--sw-amber);
} }
} }
} }

View File

@@ -18,56 +18,137 @@
*/ */
.btn { .btn {
@apply bg-yellow-400 font-bold py-2 px-4 rounded cursor-pointer select-none mx-0.5 md:mx-2 text-black flex flex-row; display: inline-flex;
@apply hover:bg-yellow-300 hover:text-black hover:shadow-2xl hover:scale-105; align-items: center;
transition: all 0.5s cubic-bezier(.2, 3, .67, .6), gap: 0.4rem;
background-color .1s ease-in-out, font-family: "Barlow Condensed", sans-serif;
outline-width .1s ease-in-out, font-weight: 700;
outline-color .1s ease-in-out; font-size: 0.8rem;
@apply active:scale-90; letter-spacing: 0.12em;
text-transform: uppercase;
padding: 0.5rem 1.2rem;
color: #f5f5f5;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
cursor: pointer;
user-select: none;
transition: all 0.25s ease;
clip-path: polygon(
0 0,
calc(100% - 8px) 0,
100% 8px,
100% 100%,
8px 100%,
0 calc(100% - 8px)
);
.btn__text { .btn__text {
@apply inline-block; display: inline-block;
} }
} }
.btn:hover {
background: rgba(245, 158, 11, 0.08);
border-color: rgba(245, 158, 11, 0.4);
color: #fbbf24;
}
.btn:active {
transform: scale(0.96);
}
.btn-dropdown { .btn-dropdown {
@apply relative mx-0.5 md:mx-2; position: relative;
> :nth-child(1) { > :nth-child(1) {
@apply !mx-0;
} }
> :nth-child(2) { > :nth-child(2) {
@apply hidden absolute top-full left-1/2 -translate-x-1/2 bg-gray-800 list-none text-white rounded py-2 flex-col text-sm z-20; display: none;
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
background: #111111;
border: 1px solid rgba(255, 255, 255, 0.06);
list-style: none;
padding: 0.5rem 0;
flex-direction: column;
font-size: 0.8rem;
z-index: 20;
min-width: 10rem;
}
> :nth-child(2) .btn {
clip-path: none;
width: 100%;
justify-content: flex-start;
border: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
} }
&:hover, &:focus-within { &:hover, &:focus-within {
> :nth-child(2) { > :nth-child(2) {
@apply flex; display: flex;
} }
} }
} }
.btn-ghost { .btn-ghost {
@apply px-6 text-xl; padding: 0.8rem 2.8rem;
@apply bg-transparent border-2 border-yellow-400 shadow-2xl shadow-yellow-400 backdrop-blur text-neutral-800 font-bold bg-gradient-to-br from-yellow-400 to-yellow-200 rounded-xl; font-size: 0.85rem;
@apply hover:border-yellow-300 hover:shadow-yellow-800 hover:scale-110; letter-spacing: 0.2em;
color: #fbbf24;
background: rgba(245, 158, 11, 0.04);
border: 1px solid rgba(245, 158, 11, 0.5);
backdrop-filter: blur(12px);
clip-path: polygon(
0 0,
calc(100% - 14px) 0,
100% 14px,
100% 100%,
14px 100%,
0 calc(100% - 14px)
);
}
.btn-ghost:hover {
background: rgba(245, 158, 11, 0.12);
border-color: rgba(245, 158, 11, 0.85);
color: #fff;
box-shadow: 0 0 40px rgba(245, 158, 11, 0.1);
transform: scale(1.04);
} }
.btn-gray { .btn-gray {
@apply bg-gray-800 text-white; background: transparent;
border-color: rgba(255, 255, 255, 0.06);
color: rgba(212, 212, 212, 0.9);
}
.btn-gray:hover {
background: rgba(245, 158, 11, 0.06);
border-color: rgba(245, 158, 11, 0.3);
color: #fbbf24;
} }
.btn-neutral { .btn-neutral {
@apply bg-neutral-800 text-white; background: rgba(255, 255, 255, 0.04);
border-color: rgba(255, 255, 255, 0.08);
color: #f5f5f5;
} }
.btn-text { .btn-text {
@apply bg-transparent underline text-white; background: transparent;
@apply hover:bg-transparent hover:outline hover:outline-1; border: none;
clip-path: none;
.btn__text { color: #f59e0b;
@apply underline; text-decoration: none;
} border-bottom: 1px solid transparent;
}
.btn-text:hover {
background: transparent;
border-bottom-color: #f59e0b;
color: #fbbf24;
} }

View File

@@ -18,20 +18,42 @@
*/ */
table { table {
@apply w-full overflow-clip; width: 100%;
overflow: clip;
:not(:has([data-no-head])) { border-collapse: collapse;
}
thead { thead {
border-bottom: 1px solid white; border-bottom: 2px solid rgba(245, 158, 11, 0.3);
font-family: "Barlow Condensed", sans-serif;
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: #f59e0b;
}
thead th {
padding: 0.6rem 0.8rem;
text-align: left;
} }
tbody { tbody {
text-align: center; text-align: center;
}
tr:nth-child(odd) { tbody td {
@apply backdrop-brightness-125; padding: 0.5rem 0.8rem;
} border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}
tbody tr {
transition: background 0.2s ease;
}
tbody tr:nth-child(odd) {
background: rgba(255, 255, 255, 0.02);
}
tbody tr:hover {
background: rgba(245, 158, 11, 0.04);
} }
} }

View File

@@ -17,25 +17,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {l as proxyL} from "astro-i18n"; import { l as proxyL } from "astro-i18n";
const locales = [ export const l = proxyL;
"en",
"de",
];
export const l = (route: string) => {
const transPath = proxyL(route);
if (import.meta.env.DEV) {
return transPath;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, locale, ...rest] = transPath.split("/");
if (locales.includes(locale)) {
return "/" + rest.join("/");
}
return transPath;
};

View File

@@ -74,7 +74,8 @@ const config = {
sm: "calc(var(--radius) - 4px)", sm: "calc(var(--radius) - 4px)",
}, },
fontFamily: { fontFamily: {
sans: [...fontFamily.sans], sans: ["Roboto", ...fontFamily.sans],
display: ["Barlow Condensed", ...fontFamily.sans],
}, },
}, },
}, },