19 Commits

Author SHA1 Message Date
470d715a7a Add view configuration for Gruppenphase and Finale in OsterEvent
All checks were successful
SteamWarCI Build successful
2026-04-25 01:21:58 +02:00
3948009c35 Remove teamserver.json configuration file
All checks were successful
SteamWarCI Build successful
2026-04-23 23:51:03 +02:00
a410830a93 Refactor z-index and background styles for improved layout consistency
All checks were successful
SteamWarCI Build successful
2026-04-23 23:48:59 +02:00
f1d7b60fae Fix z-Index
All checks were successful
SteamWarCI Build successful
2026-04-23 23:43:54 +02:00
01077da029 Increase z-index for dropdown button to improve visibility
All checks were successful
SteamWarCI Build successful
2026-04-23 23:42:08 +02:00
a195b074a7 Preserve Pathname on Lang Change
All checks were successful
SteamWarCI Build successful
Co-authored-by: Copilot <copilot@github.com>
2026-04-23 15:07:04 +02:00
a8817115a3 Fix Safari
All checks were successful
SteamWarCI Build successful
2026-04-23 14:59:56 +02:00
5639655f99 Fix Lang Switcher
All checks were successful
SteamWarCI Build successful
2026-04-23 14:10:54 +02:00
ba4aa67ff1 Update language selection links in Navbar to use data-astro-reload attribute
Some checks failed
SteamWarCI Build failed
2026-04-23 14:08:54 +02:00
6fde748088 Merge pull request 'page-redesign' (#22) from page-redesign into master
All checks were successful
SteamWarCI Build successful
Reviewed-on: #22
Reviewed-by: YoyoNow <yoyonow@noreply.localhost>
2026-04-23 12:23:26 +02:00
01da718802 Merge branch 'master' into page-redesign
All checks were successful
SteamWarCI Build successful
2026-04-23 12:23:15 +02:00
23566e72d3 Refactor Navbar component to replace search button with language selection dropdown
All checks were successful
SteamWarCI Build successful
2026-04-23 12:22:42 +02:00
9d06dc7d0b Merge branch 'master' into page-redesign
All checks were successful
SteamWarCI Build successful
2026-04-23 12:11:04 +02:00
c41b1f9daa Refactor i18n configuration and utility functions for improved clarity and simplicity
All checks were successful
SteamWarCI Build successful
2026-04-23 11:53:56 +02:00
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
43 changed files with 935 additions and 644 deletions

View File

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

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

@@ -19,11 +19,11 @@
<script lang="ts">
import "../styles/button.css";
import { CaretDownOutline, SearchOutline } from "flowbite-svelte-icons";
import { t } from "astro-i18n";
import { l } from "../util/util";
import { CaretDownOutline, GlobeOutline } from "flowbite-svelte-icons";
import { t, l } from "astro-i18n";
import { onMount } from "svelte";
import { loggedIn } from "@repo/authv2.ts";
import { astroI18n } from "astro-i18n";
interface Props {
logo?: import("svelte").Snippet;
}
@@ -35,6 +35,8 @@
let accountBtn = $state<HTMLAnchorElement>();
let currentPage = $state(astroI18n.route);
$effect(() => {
if ($loggedIn) {
accountBtn!.href = l("/dashboard");
@@ -45,33 +47,34 @@
onMount(() => {
handleScroll();
document.addEventListener("astro:page-load", () => {
astroI18n.route = location.pathname;
currentPage = astroI18n.route;
});
});
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 +107,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
@@ -125,11 +126,21 @@
<a class="btn" href={l("/login")} bind:this={accountBtn}>
<span class="btn__text">{t("navbar.links.account")}</span>
</a>
<!--
<button class="btn my-1" onclick={() => searchOpen = true}>
<SearchOutline ariaLabel="Site Search" class="inline-block h-6"/>
</button>
-->
<div class="btn-dropdown">
<button class="btn btn-gray">
<GlobeOutline />
</button>
<div>
<a
data-astro-reload
href={l(currentPage, {}, { targetLocale: typeof navigator !== "undefined" ? navigator.language.split("-")[0] : "de" })}
onclick={() => cookieStore.delete("MANUAL_LANGUAGE")}
class="btn btn-gray">Auto</a
>
<a data-astro-reload href={l(currentPage, {}, { targetLocale: "de" })} onclick={() => cookieStore.set("MANUAL_LANGUAGE", "TRUE")} class="btn btn-gray">Deutsch</a>
<a data-astro-reload href={l(currentPage, {}, { targetLocale: "en" })} onclick={() => cookieStore.set("MANUAL_LANGUAGE", "TRUE")} class="btn btn-gray">English</a>
</div>
</div>
</div>
</div>
</nav>
@@ -144,4 +155,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>

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,7 +82,7 @@
</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>

View File

@@ -1,9 +0,0 @@
{
"name": "SteamWarTeamserver",
"description": "Dieses Plugin ermöglicht die einfache Einbindung deines Servers in SteamWar. Wie du deinen (Team-)Server über SteamWar erreichbar machen kannst findest du hier.",
"url": {
"Info": "/teamserverintegration",
"Download": "https://git.steamwar.de/SteamWar/SteamWarTeamserverIntegration/releases/download/latest/SteamWarTeamserverIntegration.jar"
},
"sourceUrl": "https://git.steamwar.de/SteamWar/SteamWarTeamserverIntegration"
}

View File

@@ -2,6 +2,17 @@
eventId: 78
mode: "warship"
verwantwortlicher: "ItonaCA"
viewConfig:
groups:
name: Gruppenphase
view:
type: "GROUP"
groups: [14]
elim:
name: Finale
view:
type: "ELEMINATION"
finalFight: 1661
---
Ahoi Matrosen,

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>

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>

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 [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 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 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 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>
<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,13 +18,13 @@ 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>

View File

@@ -1,12 +1,12 @@
---
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;
@@ -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 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>
))
}
<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"/>
<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: 200;
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

@@ -17,25 +17,6 @@
* 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 = [
"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;
};
export const l = proxyL;

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],
},
},
},