5 Commits

Author SHA1 Message Date
2204bb8663 fixes
Some checks failed
SteamWarCI Build failed
2026-04-21 21:52:02 +02:00
43f42c03c0 Add event announcements for Oster-Event 2026 and WarGear Season 2026 2026-04-21 21:33:31 +02:00
1426536c88 Enhance layout and styling for improved UI consistency across components 2026-03-28 21:53:31 +01:00
5d365bc744 Refactor components and pages for improved readability and consistency
- Updated BackgroundImage.astro to format props for better readability.
- Adjusted FightTable.svelte to remove unnecessary trailing commas.
- Modified GroupTable.svelte to fix sorting syntax.
- Cleaned up LanguageWarning.astro by standardizing import statements.
- Enhanced Login.svelte for better formatting and readability.
- Simplified Navbar.svelte by merging multi-line attributes into single lines.
- Streamlined PostComponent.astro by condensing Image component props.
- Improved SearchComponent.svelte for consistent spacing and formatting.
- Refined TagComponent.astro for better readability and structure.
- Updated PageLayout.astro to simplify div structure.
- Enhanced downloads.astro for improved readability and consistency.
- Cleaned up index.astro in help directory for better formatting.
- Refactored index.astro in main pages for improved readability.
- Standardized login.astro for better formatting.
- Cleaned up not-found.astro for consistent import formatting.
- Enhanced [...schem].astro for better readability.
- Refactored [mode].astro for consistent import formatting.
- Improved [...gamemode].astro for better readability.
- Cleaned up [mode].astro in regeln directory for consistent formatting.
- Refactored index.astro in regeln directory for improved readability.
- Enhanced fight.astro for consistent import formatting.
2026-03-28 15:56:56 +01:00
d2ee422d6d Refactor styles and components for improved UI consistency
- Updated login page layout by removing unnecessary classes.
- Redesigned 404 not found page with new layout and styles.
- Enhanced public pages with consistent font styling and hover effects.
- Improved ranking page header styling for better readability.
- Updated rules page with consistent font and link styles.
- Enhanced statistics page header styling.
- Refined global CSS variables for better theme consistency.
- Updated button styles for improved interaction feedback.
- Enhanced table styles for better readability and interaction.
- Updated Tailwind configuration to include new font families.
2026-03-28 15:56:32 +01:00
42 changed files with 997 additions and 612 deletions

View File

@@ -3,7 +3,13 @@ import { Image } from "astro:assets";
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`}
class="w-full h-full object-cover rounded-b-2xl shadow-2xl" quality={100}
draggable="false" loading="eager"/>
class="w-full h-full object-cover"
quality={100}
draggable="false"
loading="eager"
/>

View File

@@ -52,7 +52,7 @@
}
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>
<div class={classes} bind:this={cardElement} onmousemove={rotateElement} onmouseleave={resetElement} class:hoverEffect>
@@ -67,10 +67,13 @@
:global(h1) {
@apply text-xl font-bold mt-4;
font-family: "Barlow Condensed", sans-serif;
letter-spacing: 0.06em;
}
: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>
<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>
<thead>
<tr class="font-bold border-b">
@@ -57,7 +57,7 @@
</tr>
</thead>
<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>
{#each fights as fight (fight.id)}
<td

View File

@@ -40,11 +40,11 @@
team: event.teams.find((t) => t.id === Number(teamId))!!,
points: points,
}))
.sort((a, b) => b.points - a.points)
.sort((a, b) => b.points - a.points),
);
</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">
<thead>
<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">
<p class="font-bold">{t("warning.title")}</p>
<p>{t("warning.text")}</p>
<div class="border-l-2 border-amber-500 bg-amber-500/5 p-4" role="alert">
<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 class="text-gray-400 text-sm">{t("warning.text")}</p>
</div>

View File

@@ -74,8 +74,8 @@
});
</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)}>
<h1 class="text-4xl text-white text-center">{t("login.title")}</h1>
<form class="sw-login-form" onsubmit={preventDefault(login)}>
<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">
<label for="username">{t("login.label.username")}</label>
<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} />
</div>
<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>
{#if error}
<p class="mt-2 text-red-500">{error}</p>
{/if}
<button class="btn mt-4 !mx-0 justify-center" 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">
<button class="btn mt-4 justify-center w-full" type="submit" onclick={preventDefault(login)}>{t("login.submit")}</button>
<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")}
</a>
</form>
<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 {
@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 {
@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>

View File

@@ -49,29 +49,25 @@
function handleScroll() {
if (window.scrollY > 0) {
navbar!.classList.add("before:scale-y-100");
navbar!.classList.add("sw-nav-scrolled");
} else {
navbar!.classList.remove("before:scale-y-100");
navbar!.classList.remove("sw-nav-scrolled");
}
}
</script>
<svelte:window onscroll={handleScroll} />
<nav
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}
>
<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}>
<div class="flex flex-row items-center justify-evenly md:justify-between match">
<a class="flex items-center" href={l("/")}>
{@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")}
<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>
</a>
<div class="flex justify-center flex-wrap">
<div class="flex justify-center flex-wrap gap-2">
<div class="btn-dropdown">
<button class="btn btn-gray">
<a href={l("/")}>
@@ -104,8 +100,6 @@
<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/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>
<!-- TODO: Add help center
@@ -144,4 +138,29 @@
.match {
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>

View File

@@ -22,26 +22,21 @@ const {
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"}`}>
{
post.data.image != null ? (
<a href={postUrl}>
<div class="flex-shrink-0 pr-2">
<Image
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"
/>
<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" />
</div>
</a>
) : null
}
<div>
<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>
<P class="text-gray-500"
<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 text-sm"
>{
Intl.DateTimeFormat(astroI18n.locale, {
day: "numeric",

View File

@@ -18,12 +18,11 @@
-->
<script lang="ts">
import {slide, fade} from "svelte/transition";
import {onMount} from "svelte";
import {importPagefind, type Pagefind, type PagefindDocument} from "@type/pagefind.js";
import { slide, fade } from "svelte/transition";
import { onMount } from "svelte";
import { importPagefind, type Pagefind, type PagefindDocument } from "@type/pagefind.js";
import Card from "@components/Card.svelte";
import {l} from "@utils/util.ts";
import { l } from "@utils/util.ts";
let pagefind: Pagefind;
onMount(async () => {
@@ -36,27 +35,26 @@
async function search(e: KeyboardEvent) {
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 {
open?: boolean;
}
interface Props {
open?: boolean;
}
let { open = $bindable(false) }: Props = $props();
let { open = $bindable(false) }: Props = $props();
</script>
<button transition:fade class="fixed top-0 left-0 w-screen h-screen backdrop-blur z-20 cursor-default" onclick={() => open = false}>
</button>
<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}>
<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>
<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">
<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}
<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)}>
<h1>{result.meta.title}</h1>
{#each result.sub_results.slice(0, 2) as sub_result}
@@ -69,13 +67,28 @@
</div>
<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 {
@apply border-2 rounded-md p-2 shadow-2xl w-full
dark:bg-neutral-800
focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:border-transparent;
width: 100%;
padding: 0.7rem 1rem;
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 {
@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 {capitalize} from "./admin/util";
import { l } from "../util/util";
import { capitalize } from "./admin/util";
interface Props {
tag: string;
noLink?: boolean;
}
const {tag, noLink} = Astro.props;
const { tag, noLink } = Astro.props;
---
{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>
)
: (
{
noLink ? (
<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}`)}>
<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>
)}
)
}

View File

@@ -52,7 +52,7 @@
/>
</figure>
</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>
{#if user.perms.includes("MODERATION")}
<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>
<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}
</div>
<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">
import { preventDefault } from 'svelte/legacy';
import { preventDefault } from "svelte/legacy";
import {
ChevronDoubleLeftOutline,
ChevronDoubleRightOutline,
ChevronLeftOutline,
ChevronRightOutline,
} from "flowbite-svelte-icons";
import { 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;
}
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();
let { page = $bindable(0), maxPage, firstUrl = "#", lastUrl = "#", previousUrl = "#", nextUrl = "#", pagesUrl = () => "#" }: Props = $props();
const previous = () => {
page = Math.max(page - 1, 0);
@@ -56,20 +41,23 @@
const next = () => {
page = Math.min(page + 1, maxPage - 1);
};
let pages = $derived(new Array(maxPage).fill(0)
.map((_, i) => i + 1)
//.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)))
.map(i => ({
name: i.toString(),
active: i === page + 1,
i: i - 1
})));
let pages = $derived(
new Array(maxPage)
.fill(0)
.map((_, i) => i + 1)
//.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)))
.map((i) => ({
name: i.toString(),
active: i === page + 1,
i: i - 1,
})),
);
</script>
<div class="w-full flex justify-center mt-4">
<ul class="inline-flex flex-wrap">
<ul class="inline-flex flex-wrap gap-1">
<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>
<ChevronDoubleLeftOutline class="w-3 h-3" />
</a>
@@ -82,7 +70,7 @@
</li>
{#each pages as p}
<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>
</a>
</li>
@@ -94,10 +82,10 @@
</a>
</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>
<ChevronDoubleRightOutline class="w-3 h-3" />
</a>
</li>
</ul>
</div>
</div>

View File

@@ -4,10 +4,14 @@ key: 2026-easter_event
description: Das Oster Event 2026 steht an. Wir freuen uns auf Eure Teilnahme!
created: 2026-03-10T00:00:00.000Z
tags:
- event
- warship
- event
- warship
---
## Diese Version ist nicht die Aktuelle!
Bite schaue [hier](/events/2026-OsterEvent)!
Ahoi Matrosen,
der Winter zieht sich langsam zurück, die ersten Blumen kämpfen sich durch den Schnee und auch auf den Werften von SteamWar beginnt wieder geschäftiges Treiben.

View File

@@ -4,40 +4,48 @@ key: 2026-wgs
description: Die WarGear Season 2026 steht an. Wir freuen uns auf Eure Teilnahme!
created: 2026-02-18T00:00:00.000Z
tags:
- event
- wargear
- event
- wargear
---
# Diese Version ist nicht die Aktuelle!
Bite schaue [hier](/events/2026-wgs)!
# WarGear Season 2026
Die WarGear Season 2026 steht bevor!
Bereitet eure Teams vor und stellt euch einer kompetitiven Season mit klaren Regeln und fairen Bedingungen.
## Allgemeine Infos
Version: 1.21.6
WarGear Schem Type: ???
Anzahl Teams: ∞
Anmeldeschluss: 30.04.2026 (Anzahl Teams wird am 25.04.2026 entschieden)
Einsendeschluss: 07.05.2026 (Ab dann nur noch Public Schematic wählbar)
Erster Spieltag: 02.05.2026
Erster Spieltag: 02.05.2026
Das Turnierformat wird zwischen dem 25.04.2026 und dem 30.04.2026 entschieden.
## Teams
Fighter pro Team: 6
Spectate System: ja
Gebannte Spieler: bleiben gebannt
Gebannte Spieler: bleiben gebannt
## Schematic
Hotfixes erlaubt: ja (bis max. 4 Stunden vor dem ersten Fight des Tages)
Schematic wird während der WGS an den User SteamWar gebunden.
Regelwerk: WG21 ohne SFA, Windcharges zum komprimieren erlaubt (Regelwerk folgt in kürze)
Regelwerk: WG21 ohne SFA, Windcharges zum komprimieren erlaubt (Regelwerk folgt in kürze)
## Challenge System
Am 25.04.2026 wird die Anzahl Teams auf die wir die WGS auslegen festgelegt. Alle dann angemeldeten
Teams kommen auf jeden Fall rein. Alle Teams die sich darüber hinaus bis zum 30.04.2026 anmelden rücken
bei Abmeldung nach oder können pro Spieltag/Wochenende das/die Team(s) auf dem letzten Platz herausfordern
und bei erfolgreicher Herausforderung in die WGS eintreten.
und bei erfolgreicher Herausforderung in die WGS eintreten.
Wenn ein Team **UNANGEKUNDIGT** an einem Spieltag fehlt, wird es durch ein Team welches nicht mehr in die WGS
gekommen ist ersetzt. Das rausgeflogene Team kann sich nicht mehr durch eine Herausforderung in die WGS

View File

@@ -0,0 +1,37 @@
---
eventId: 78
mode: "warship"
verwantwortlicher: "JajaKings"
---
Ahoi Matrosen,
der Winter zieht sich langsam zurück, die ersten Blumen kämpfen sich durch den Schnee und auch auf den Werften von SteamWar beginnt wieder geschäftiges Treiben.
Zu Ostern laden wir euch zu einem besonderen Warship-Event ein!
Packt eure Werkzeuge aus, bringt eure Kanonen auf Hochglanz und bereitet eure Schiffe für den Kampf vor.
Am 25. und 26. April treffen sich die Kapitäne der Flotte, um ihre neuesten Kreationen auf dem Schlachtfeld zu testen. Egal ob altgedienter Konstrukteur oder neuer Schiffsbauer jeder ist willkommen, seine Ideen zu Wasser zu lassen.
Ein Osterdesign ist gerne gesehen, aber keine Pflicht. Wer also sein Schiff mit Eiern, Frühlingsfarben oder kleinen Überraschungen schmücken möchte, darf seiner Kreativität freien Lauf lassen.
Termin
26. April
Version
Gespielt wird auf dem aktuellen 1.21 RW
Was euch erwartet
spannende Warship-Gefechte
neue Konstruktionen und kreative Designs
gemeinsames Event-Wochenende mit der Community
jede Menge Explosionen und epische Schlachten
Also: Werft die Maschinen an, hisst die Flaggen und macht eure Schiffe bereit.
Wir freuen uns auf ein spannendes Osterwochenende mit euch!

View File

@@ -0,0 +1,44 @@
---
eventId: 77
mode: "wargear"
verwantwortlicher: "JajaKings"
---
# WarGear Season 2026
Die WarGear Season 2026 steht bevor!
Bereitet eure Teams vor und stellt euch einer kompetitiven Season mit klaren Regeln und fairen Bedingungen.
## Allgemeine Infos
Version: 1.21.6
WarGear Schem Type: ???
Anzahl Teams: ∞
Anmeldeschluss: 30.04.2026 (Anzahl Teams wird am 25.04.2026 entschieden)
Einsendeschluss: 07.05.2026 (Ab dann nur noch Public Schematic wählbar)
Erster Spieltag: 02.05.2026
Das Turnierformat wird zwischen dem 25.04.2026 und dem 30.04.2026 entschieden.
## Teams
Fighter pro Team: 6
Spectate System: ja
Gebannte Spieler: bleiben gebannt
## Schematic
Hotfixes erlaubt: ja (bis max. 4 Stunden vor dem ersten Fight des Tages)
Schematic wird während der WGS an den User SteamWar gebunden.
Regelwerk: WG21 ohne SFA, Windcharges zum komprimieren erlaubt (Regelwerk folgt in kürze)
## Challenge System
Am 25.04.2026 wird die Anzahl Teams auf die wir die WGS auslegen festgelegt. Alle dann angemeldeten
Teams kommen auf jeden Fall rein. Alle Teams die sich darüber hinaus bis zum 30.04.2026 anmelden rücken
bei Abmeldung nach oder können pro Spieltag/Wochenende das/die Team(s) auf dem letzten Platz herausfordern
und bei erfolgreicher Herausforderung in die WGS eintreten.
Wenn ein Team **UNANGEKUNDIGT** an einem Spieltag fehlt, wird es durch ein Team welches nicht mehr in die WGS
gekommen ist ersetzt. Das rausgeflogene Team kann sich nicht mehr durch eine Herausforderung in die WGS
reinkämpfen.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,9 @@ import Navbar from "@components/Navbar.svelte";
import ServerStatus from "../components/ServerStatus.svelte";
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}>
@@ -24,17 +27,15 @@ const { title, description, transparentFooter = true } = Astro.props;
<main class="flex-1" data-pagefind-body>
<slot />
</main>
<footer
class={`min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col ${transparentFooter ? "backdrop-blur-3xl" : "bg-neutral-900"}`}
style="width: min(100%, 75em); margin-left: auto; margin-right: auto;"
>
<div class="flex-1 flex justify-evenly items-center md:items-start mt-4 md:flex-row flex-col gap-y-4">
<div class="footer-card">
<h1>Serverstatus</h1>
<footer class="w-full max-w-[75em] mx-auto mt-16 px-4 pb-4 bg-[rgba(8,8,8,0.85)]">
<div class="h-px bg-gradient-to-r from-transparent via-amber-500/30 to-transparent mb-10"></div>
<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]`}>
<h1 class={footerH1}>Serverstatus</h1>
<ServerStatus client:only="svelte" />
</div>
<div class="footer-card">
<h1>Links</h1>
<div class={footerCol}>
<h1 class={footerH1}>Links</h1>
<a href={l("/")}>{t("navbar.links.home.title")}</a>
<a href={l("/join")}>{t("footer.join")}</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("/imprint")}>{t("footer.imprint")}</a>
</div>
<div class="footer-card">
<h1>Social Media</h1>
<div class={footerCol}>
<h1 class={footerH1}>Social Media</h1>
<a class="flex" href="/youtube">
<YoutubeSolid class="mr-2" />
YouTube</a
@@ -60,26 +61,8 @@ const { title, description, transparentFooter = true } = Astro.props;
>
</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>
</div>
</Fragment>
</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">
<BackgroundImage />
</div>
<div
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);"}
>
<div class="sw-page-content" style={wide ? "width: clamp(80%, 75em, 100%);" : "width: min(100%, 75em);"}>
<slot />
</div>
</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 {astroI18n, createGetStaticPaths} from "astro-i18n";
import { type CollectionEntry, getCollection } from "astro:content";
import { astroI18n, createGetStaticPaths } from "astro-i18n";
import PageLayout from "../layouts/PageLayout.astro";
import LanguageWarning from "../components/LanguageWarning.astro";
import "@styles/table.css";
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 => {
if (posts.find(post => post.id.split("/")[1] === value.id.split("/")[1])) {
return;
} else {
germanPosts.forEach((value) => {
if (posts.find((post) => post.id.split("/")[1] !== value.id.split("/")[1])) {
posts.push(value);
}
});
@@ -41,17 +39,15 @@ export const getStaticPaths = createGetStaticPaths(async () => {
}));
});
const {page, german} = Astro.props as { page: CollectionEntry<"pages">, german: boolean };
const {Content} = await page.render();
const { page, german } = Astro.props as { page: CollectionEntry<"pages">; german: boolean };
const { Content } = await page.render();
---
<PageLayout title={page.data.title}>
<article>
{german && (
<LanguageWarning/>
)}
<h1 class="text-left">{page.data.title}</h1>
<Content/>
{german && <LanguageWarning />}
<h1 class="text-left" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;">{page.data.title}</h1>
<Content />
</article>
</PageLayout>
@@ -62,15 +58,28 @@ const {Content} = await page.render();
}
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 {
@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 {
@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>

View File

@@ -73,13 +73,13 @@ const ogImage = await getImage({
{
post.data.image && (
<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 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>
<div class="flex items-center mt-2 text-neutral-800 dark:text-neutral-300">
<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-gray-400">
<TagSolid class="w-4 h-4 mr-2" />
<div transition:name={post.data.title + "-tags"}>
{post.data.tags.map((tag) => <TagComponent tag={tag} />)}
@@ -175,11 +175,17 @@ const ogImage = await getImage({
}
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 {
@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>

View File

@@ -1,31 +1,44 @@
---
import PageLayout from "../layouts/PageLayout.astro";
import {getCollection} from "astro:content";
import {t} from "astro-i18n";
import {l} from "../util/util";
import { getCollection } from "astro:content";
import { t } from "astro-i18n";
import { l } from "../util/util";
const downloads = await getCollection("downloads");
---
<PageLayout title="Downloads">
{downloads.map(e => (
<div class="pt-4">
<h1 class="font-bold text-2xl">{e.data.name}</h1>
<div class="py-4">{t(e.data.description)}</div>
{
downloads.map((e) => (
<div class="pt-6 pb-4" style="border-bottom: 1px solid rgba(255,255,255,0.04);">
<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">
{typeof e.data.url === "object" ?
Object.entries(e.data.url).map(value => (
<a href={value[1].startsWith("/") ? l(value[1]) : value[1]}
class="text-blue-500 hover:underline w-fit">{t(value[0])}</a>
))
:
<a href={e.data.url} class="text-blue-500 hover:underline w-fit">{t("Download")}</a>
}
{e.data.sourceUrl ?
<a class="text-blue-500 hover:underline w-fit" href={e.data.sourceUrl}>Quelle</a>
: null}
<div class="flex flex-col gap-1">
{typeof e.data.url === "object" ? (
Object.entries(e.data.url).map((value) => (
<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;"
>
{t(value[0])}
</a>
))
) : (
<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>
))}
</PageLayout>
))
}
</PageLayout>

View File

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

View File

@@ -3,9 +3,7 @@ import dayjs from "dayjs";
import NavbarLayout from "@layouts/NavbarLayout.astro";
import { getCollection } from "astro:content";
import { astroI18n } from "astro-i18n";
import { Image } from "astro:assets";
import Card from "@components/Card.svelte";
import { CaretRight, Pause, Rocket, Crosshair1 } from "@astropub/icons";
import { t } from "astro-i18n";
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 prefixColorMap: {
[key: string]: string;
} = {
Admin: "border-red-600 dark:border-red-800 shadow-red-600 dark:shadow-red-800",
Dev: "border-sky-600 dark:border-sky-800 shadow-sky-600 dark:shadow-sky-800",
Mod: "border-amber-600 dark:border-amber-800 shadow-amber-600 dark:shadow-amber-800",
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 prefixColors: { [key: string]: string } = {
Admin: "#dc2626",
Dev: "#0284c7",
Mod: "#d97706",
Sup: "#1d4ed8",
Arch: "#22c55e",
};
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}>
<div class="w-full h-screen relative mb-4 z-10">
<div style="height: calc(100vh + 1rem)">
<section class="relative w-full h-screen overflow-hidden">
<div class="absolute inset-0 h-[calc(100vh+1rem)]">
<BackgroundImage />
</div>
<drop-in class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center">
<h1
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);"
>
<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>
<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>
<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>
<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="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>
<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>
{t("home.subtitle.2")}
<PlayerCount client:idle />
</h2>
<h2>{t("home.subtitle.3")}</h2>
</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")}
<CaretRight width="24" height="24" />
<a
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>
<style>
@keyframes flyIn {
0% {
translate: 0 16px;
opacity: 0;
}
</div>
20% {
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">
<div class="absolute bottom-6 left-1/2 -translate-x-1/2 z-20 w-full max-w-[40rem] px-4">
<PostComponent post={latestPost} slim={true} />
</div>
</div>
<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">
<Card client:idle>
<Crosshair1 height="64" width="64" />
<h1>{t("home.benefits.fights.title")}</h1>
<p class="mt-4">{t("home.benefits.fights.description.1")}</p>
<p class="mt-4">{t("home.benefits.fights.description.2")}</p>
</Card>
<Card client:idle>
<Rocket height="64" width="64" />
<h1>{t("home.benefits.bau.title")}</h1>
<p class="mt-4">{t("home.benefits.bau.description")}</p>
</Card>
<Card client:idle>
<Pause height="64" width="64" />
<h1>{t("home.benefits.minigames.title")}</h1>
<p class="mt-4">{t("home.benefits.minigames.description.1")}</p>
<p class="mt-4">{t("home.benefits.minigames.description.2")}</p>
</Card>
</section>
<section class="relative bg-[#080808] overflow-hidden">
<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>
<div class="relative z-[2] max-w-5xl mx-auto py-24 px-4">
<div class="grid grid-cols-1 md:grid-cols-3">
<article class={featClass}>
<span class={featNum}>01</span>
<Crosshair1 height="36" width="36" />
<h3 class={featH3}>{t("home.benefits.fights.title")}</h3>
<p class={featP}>{t("home.benefits.fights.description.1")}</p>
<p class={featP}>{t("home.benefits.fights.description.2")}</p>
</article>
<article class={featClass}>
<span class={featNum}>02</span>
<Rocket height="36" width="36" />
<h3 class={featH3}>{t("home.benefits.bau.title")}</h3>
<p class={featP}>{t("home.benefits.bau.description")}</p>
</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>
</section>
<section class="w-full py-12 flex flex-wrap justify-center">
{
Object.entries(teamMember).map(([prefix, players]) => (
<Fragment>
{players.map((v, index) => (
<div class="inline-flex flex-col justify-end">
{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}
<Card extraClasses={`pt-8 pb-10 px-8 w-fit shadow-md ${prefixColorMap[prefix]}`} client:idle>
<figure class="flex flex-col items-center" style="width: 150px">
<figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption>
<section class="bg-[#0c0c0c] py-20">
<div class="max-w-5xl mx-auto px-4 flex flex-wrap gap-4">
{
Object.entries(teamMember).map(([prefix, players]) => (
<Fragment>
{players.map((v, i) => (
<div class="flex flex-col justify-end">
{i === 0 && (
<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
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 + "s bust"}
width="150"
height="150"
alt={v.name}
width="120"
height="120"
class="w-[120px] h-[120px] transition-transform duration-300"
/>
</figure>
</Card>
</div>
))}
</Fragment>
))
}
<span class="font-display mt-3 text-[0.9rem] tracking-[0.1em] text-neutral-300/90">{v.name}</span>
</div>
</div>
))}
</Fragment>
))
}
</div>
</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>
<style>
text-carousel {
> * {
@apply absolute top-0 left-0 w-full text-xl sm:text-4xl italic text-white text-center opacity-0;
transition:
transform 0.5s ease-out,
opacity 0.5s linear;
text-shadow: 2px 2px 5px black;
.hero-text {
background-image: linear-gradient(160deg, #fcd34d, #f59e0b);
background-clip: text;
}
text-carousel > * {
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 {
font-family:
Barlow Condensed,
sans-serif;
@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
@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
dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100
hover:scale-105;
> h1 {
@apply text-xl font-bold mt-4;
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.92);
}
> p {
@apply mt-4;
to {
opacity: 1;
transform: scale(1);
}
}
> svg {
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl;
@keyframes lineGrow {
from {
width: 0;
opacity: 0;
}
to {
width: 6rem;
opacity: 1;
}
}
</style>

View File

@@ -23,8 +23,7 @@ import BackgroundImage from "../components/BackgroundImage.astro";
<div class="h-screen w-screen fixed -z-10">
<BackgroundImage />
</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
dark:text-white" style="width: min(100vw, 75em);">
<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);">
<LoginComponent client:load />
</div>
</NavbarLayout>

View File

@@ -1,39 +1,12 @@
---
import PageLayout from "../layouts/PageLayout.astro";
import {t} from "astro-i18n";
import { t } from "astro-i18n";
---
<PageLayout title={t("404.title")}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 400">
<!-- 404 Text - heller für besseren Kontrast -->
<text x="400" y="150" font-family="Arial Black" font-size="120" fill="#ffffff" text-anchor="middle">404</text>
<!-- Trauriger Roboter - hellere Farben für besseren Kontrast -->
<!-- Körper -->
<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>
<div class="flex flex-col items-center justify-center py-20">
<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>
<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>
</div>
</PageLayout>

View File

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

View File

@@ -1,13 +1,13 @@
---
import {createGetStaticPaths, t} from "astro-i18n";
import {getCollection, type CollectionEntry} from "astro:content";
import { createGetStaticPaths, t } from "astro-i18n";
import { getCollection, type CollectionEntry } from "astro:content";
import PageLayout from "../../layouts/PageLayout.astro";
import EloTable from "../../components/EloTable.svelte";
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: {
mode: value,
},
@@ -21,10 +21,10 @@ interface Props {
mode: CollectionEntry<"modes">;
}
const {mode} = Astro.props;
const { mode } = Astro.props;
---
<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>
<EloTable gamemode={mode.id} client:load/>
</PageLayout>
<PageLayout title={t("ranking.heading", { mode: t(`${mode.data.translationKey}.title`) })}>
<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 />
</PageLayout>

View File

@@ -1,19 +1,19 @@
---
import {getCollection, type CollectionEntry} from "astro:content";
import {astroI18n, createGetStaticPaths, t} from "astro-i18n";
import { getCollection, type CollectionEntry } from "astro:content";
import { astroI18n, createGetStaticPaths, t } from "astro-i18n";
import PageLayout from "@layouts/PageLayout.astro";
import LanguageWarning from "@components/LanguageWarning.astro";
import EloTable from "@components/EloTable.svelte";
import {l} from "../../util/util";
import { l } from "../../util/util";
import { Image } from "astro:assets";
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 => {
if (posts.find(post => post.id.split("/")[1] === value.id.split("/")[1])) {
germanPosts.forEach((value) => {
if (posts.find((post) => post.id.split("/")[1] === value.id.split("/")[1])) {
return;
} else {
posts.push(value);
@@ -27,53 +27,58 @@ export const getStaticPaths = createGetStaticPaths(async () => {
props: {
page,
german: page.id.split("/")[0] != astroI18n.locale,
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]),
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]),
},
params: {
mode: page.slug.split("/")[1],
},
}),
);
}));
});
interface Props {
page: CollectionEntry<"rules">,
mode: CollectionEntry<"modes">,
publics: CollectionEntry<"publics">[],
german: boolean
page: CollectionEntry<"rules">;
mode: CollectionEntry<"modes">;
publics: CollectionEntry<"publics">[];
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`)})}>
<h1 class="text-3xl font-bold">{t(`${page.data.translationKey}.title`)}</h1>
{mode && mode.data.ranked && (
<Fragment>
<EloTable gamemode={page.id.split("/")[1].split(".")[0]} client:load topFive={true}/>
<a class="text-neutral-800 dark:text-neutral-400 hover:underline" href={l(`/rangliste/${page.id.split("/")[1].split(".")[0]}`)}>{t("rules.ranking")}</a>
</Fragment>
)}
{publics && publics.length != 0 && (
<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>
</a>
))}
</div>
<a class="text-neutral-800 dark:text-neutral-400 hover:underline" href={l(`/publics/${page.id.split("/")[1].split(".")[0]}`)}>{t("rules.publics")}</a>
</Fragment>
)}
<PageLayout title={t("rules.title", { mode: t(`${page.data.translationKey}.title`) })}>
<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>
<EloTable gamemode={page.id.split("/")[1].split(".")[0]} client:load topFive={true} />
<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")}
</a>
</Fragment>
)
}
{
publics && publics.length != 0 && (
<Fragment>
<div class="flex overflow-x-scroll gap-2 py-4">
{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} />
</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>
{german && (
<LanguageWarning/>
)}
<Content/>
{german && <LanguageWarning />}
<Content />
</article>
</PageLayout>
@@ -84,15 +89,28 @@ const {Content} = await page.render();
}
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 {
@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 {
@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 {getCollection, type CollectionEntry} from "astro:content";
import { t } from "astro-i18n";
import { getCollection, type CollectionEntry } from "astro:content";
import PageLayout from "@layouts/PageLayout.astro";
import {Image} from "astro:assets";
import {l} from "@utils/util";
import { Image } from "astro:assets";
import { l } from "@utils/util";
const imageMap = {
"wg": await getRandomFromMode("wargear"),
"mwg": await getRandomFromMode("miniwargear"),
"as": await getRandomFromMode("airship"),
"ws": await getRandomFromMode("warship"),
"qg": await getRandomFromMode("quickgear"),
wg: await getRandomFromMode("wargear"),
mwg: await getRandomFromMode("miniwargear"),
as: await getRandomFromMode("airship"),
ws: await getRandomFromMode("warship"),
qg: await getRandomFromMode("quickgear"),
};
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)];
}
const modes = await getCollection("modes", entry => entry.data.main);
const modes = await getCollection("modes", (entry) => entry.data.main);
---
<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
md:flex-row">
<a href={l(`/publics/${imageMap[value.data.translationKey].id}`)}>
<Image height="200" width="200" src={imageMap[value.data.translationKey].data.image}
alt={t("rules." + value.data.translationKey + ".title")} class="h-full aspect-square max-w-fit"></Image>
</a>
<div class="ml-4">
<a href={l(`/rules/${value.id}`)}>
<h1 class="text-2xl font-bold">{t(value.data.translationKey + ".title")}</h1>
<div>{t("rules." + value.data.translationKey + ".description")}</div>
{
modes.map((value) => (
<div class="sw-mode-card">
<a href={l(`/publics/${imageMap[value.data.translationKey].id}`)}>
<Image
height="200"
width="200"
src={imageMap[value.data.translationKey].data.image}
alt={t("rules." + value.data.translationKey + ".title")}
class="h-full aspect-square max-w-fit"
/>
</a>
<div class="mt-2 flex flex-col">
<a href={l(`/publics/${value.id}`)} class="text-yellow-300 hover:underline w-fit">{t("rules.publics")}</a>
{value.data.ranked
? <a href={l(`/ranked/${value.id}`)}
class="text-yellow-300 hover:underline w-fit">{t("rules.ranking")}</a>
: null}
<div class="ml-4">
<a href={l(`/rules/${value.id}`)}>
<h1 class="text-2xl font-bold" style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.04em;">
{t(value.data.translationKey + ".title")}
</h1>
<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>))}
<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 FightStatistics from "../../components/FightStatistics.svelte";
import {t} from "astro-i18n";
import { t } from "astro-i18n";
---
<PageLayout title={t("stats.title")}>
<h1>{t("stats.title")}</h1>
<FightStatistics client:only="svelte"/>
</PageLayout>
<h1 style="font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.06em;">{t("stats.title")}</h1>
<FightStatistics client:only="svelte" />
</PageLayout>

View File

@@ -23,67 +23,77 @@
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--background: 0 0% 3.1%;
--foreground: 0 0% 96%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--muted: 0 0% 10%;
--muted-foreground: 0 0% 55%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--popover: 0 0% 5%;
--popover-foreground: 0 0% 96%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--card: 0 0% 5%;
--card-foreground: 0 0% 96%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--border: 0 0% 12%;
--input: 0 0% 12%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--primary: 38 92% 50%;
--primary-foreground: 0 0% 4%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--secondary: 0 0% 10%;
--secondary-foreground: 0 0% 96%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--accent: 38 92% 50%;
--accent-foreground: 0 0% 4%;
--destructive: 0 84.2% 60.2%;
--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 {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--background: 0 0% 3.1%;
--foreground: 0 0% 96%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--muted: 0 0% 10%;
--muted-foreground: 0 0% 55%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--popover: 0 0% 5%;
--popover-foreground: 0 0% 96%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--card: 0 0% 5%;
--card-foreground: 0 0% 96%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--border: 0 0% 12%;
--input: 0 0% 12%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--primary: 38 92% 50%;
--primary-foreground: 0 0% 4%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--secondary: 0 0% 10%;
--secondary-foreground: 0 0% 96%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--accent: 38 92% 50%;
--accent-foreground: 0 0% 4%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--ring: 240 3.7% 15.9%;
--ring: 38 92% 50%;
}
}
@@ -93,6 +103,13 @@
}
body {
@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 {
@@ -101,15 +118,26 @@
}
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 {
@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 {
@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 {
@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;
@apply hover:bg-yellow-300 hover:text-black hover:shadow-2xl hover:scale-105;
transition: all 0.5s cubic-bezier(.2, 3, .67, .6),
background-color .1s ease-in-out,
outline-width .1s ease-in-out,
outline-color .1s ease-in-out;
@apply active:scale-90;
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-family: "Barlow Condensed", sans-serif;
font-weight: 700;
font-size: 0.8rem;
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 {
@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 {
@apply relative mx-0.5 md:mx-2;
position: relative;
> :nth-child(1) {
@apply !mx-0;
}
> :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 {
> :nth-child(2) {
@apply flex;
display: flex;
}
}
}
.btn-ghost {
@apply px-6 text-xl;
@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;
@apply hover:border-yellow-300 hover:shadow-yellow-800 hover:scale-110;
padding: 0.8rem 2.8rem;
font-size: 0.85rem;
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 {
@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 {
@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 {
@apply bg-transparent underline text-white;
@apply hover:bg-transparent hover:outline hover:outline-1;
.btn__text {
@apply underline;
}
background: transparent;
border: none;
clip-path: none;
color: #f59e0b;
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 {
@apply w-full overflow-clip;
:not(:has([data-no-head])) {
}
width: 100%;
overflow: clip;
border-collapse: collapse;
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 {
text-align: center;
}
tr:nth-child(odd) {
@apply backdrop-brightness-125;
}
tbody td {
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

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