30 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
6daee3a58b Fix 2026-wgs.md
All checks were successful
SteamWarCI Build successful
2026-04-23 11:09:42 +02:00
4d361415ed Fix 2026-wgs.md
All checks were successful
SteamWarCI Build successful
2026-04-23 11:06:29 +02:00
78259e7263 Fix 2026-wgs.md
All checks were successful
SteamWarCI Build successful
2026-04-23 10:52:10 +02:00
76d9ee9810 Fix 2026-wgs.md
All checks were successful
SteamWarCI Build successful
2026-04-23 10:14:24 +02:00
3b1a491bc2 Fix events
All checks were successful
SteamWarCI Build successful
2026-04-23 08:35:12 +02:00
b357c89dac Remove duplicated title
All checks were successful
SteamWarCI Build successful
2026-04-22 17:51:51 +02:00
f8399e4f31 Fix grammar
All checks were successful
SteamWarCI Build successful
2026-04-22 09:51:57 +02:00
dea500e70d Add timeline.md
All checks were successful
SteamWarCI Build successful
2026-04-22 09:07:47 +02:00
2204bb8663 fixes
Some checks failed
SteamWarCI Build failed
2026-04-21 21:52:02 +02:00
0f20cc0485 Fix link case sensitivity in 2026 Oster-Event announcement
All checks were successful
SteamWarCI Build successful
2026-04-21 21:36:36 +02:00
e44a3f81e4 Add event announcements for Oster-Event 2026 and WarGear Season 2026
All checks were successful
SteamWarCI Build successful
2026-04-21 21:34:31 +02:00
43f42c03c0 Add event announcements for Oster-Event 2026 and WarGear Season 2026 2026-04-21 21:33:31 +02:00
DreamJxnas
931d59565d Übersetzung Brücke und Auflösen von Doppeldeutigkeit in der Werfer-Ansteuerung
All checks were successful
SteamWarCI Build successful
2026-04-19 16:32:29 +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
48 changed files with 1169 additions and 655 deletions

View File

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

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>
</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

@@ -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

@@ -0,0 +1,48 @@
---
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,
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 & YoyoNow"
---
# 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: WarGearSeason26 oder WGS26
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

@@ -0,0 +1,130 @@
---
title: Timeline
description: Timeline
slug: timeline
slugs:
en: timeline
# First Trail
# HullHider
# Lobby:
# - Flugschiff
# - Vulkan
# - Jetzige Lobby
---
### Dynamic Bau
Aktuell im Bau befindet sich der Dynamic Bau, womit der BauServer selbst gestaltet werden kann.
### WarGear AI
Aktuell im Bau befindet sich eine WarGear KI, gegen die man anstelle von Spielern Kämpfen kann.
## 500.000 Spielstunden
Die ersten 500.000 Spielstunden wurden am 12.05.2025 geknackt.
## 100.000 Replays
Am 23.02.2025 wurde das 100.000 replay gespeichert.
## Server Umzug
Als das Development am 12.01.2025 aufsteht wurde festgestellt, dass man nicht mehr auf SteamWar spielen kann.
Der Velocity (Proxy Server) ist in der Nacht auf den 12.01.2025 ausgefallen.
Dieser wurde kurzerhand versucht neuzustarten um festzustellen, dass aufgrund einer fehlerhaften Festplatte dieser nicht mehr startet.
Kurzerhand wurde ein letztes Backup von der Datenbank und dem Server gemacht, sodass alle Daten weiter bestehen bleiben.
Nach kurzen internen Überlegungen wurde ein neuer Server gemietet und SteamWar neu aufgesetzt.
Am 14.01.2025 um 20:43 war SteamWar unter der Domain `dampfkrieg.de` wieder erreichbar.
Einige Funktionen blieben bis zum 20.01.2025 deaktiviert.
An diesem Tag wurde die postalisch umgezogene Domain `steamwar.de` wieder aktiviert und eine neue Webseite released.
## Geburtstag von SteamWar
Am 18.04.2024 wurde SteamWar 5 Jahre alt.
## 10.000 players
Am 25.01.2024 ist der 10.000 Spieler das erste mal auf SteamWar gejoint.
## 100.000 Fights
Am 10.04.2023 wurde der 100.000 Fight ausgetragen.
Es wurden 44585 MiniWarGear Fights, 36001 WarGear Fights und 7622 WarShip Fights ausgetragen.
Hinzukommen noch 6647 AirShip Fights und 3223 MicroWarGear Fights.
Die restlichen Kämpfe teilen sich auf 35er WarGear, MegaWarGear, MPP WarGear und Sea-Giants WarShip auf.
## 10.000 replays
Am 16.03.2022 wurde das 10.000 replay in der Datenbank gespeichert.
Die Replay Tabelle war schon seit einiger Zeit die größte Tabelle der Datenbank.
## Datenverlust
Einige Bauweltmember, ignorierte Spieler, Kits und Bau-GUI Einstellungen wurden am 31.12.2021 wegen des Laden eines Backups gelöscht.
Insgesamt waren die Daten eines Zeitraumes von ca 3,5 Monaten betroffen, welche teilweise verloren gingen.
## 10.000 Schematics
Am 20.11.2022 wurde die 10.000 Schematic erstellt.
## Replay System
Am 22.08.2021 wurde das erste Replay gespeichert.
Das Replay System nimmt ab dann jeden Fight auf, welche bis heute noch abgespielt werden können.
## TheJoCraft Videos
TheJoCraft hat zwischen dem 18.06.2021 und dem 22.06.2021 4 Videos über WarGears auf YouTube hochgeladen.
Innerhalb dieser 4 Tage spielten 1090 Spieler auf SteamWar.
Davon waren 706 neue Spieler, die das erste mal gejoint sind.
Knapp unter 3390 Stunden wurden auf StemWar während diesen Tagen gespielt.
Am 21.06.2021 sind mit 217 Spielern die meisten neuen Spieler das erste mal SteamWar gejoint.
## Erster Simulator
Am 07.03.2021 wurde der erste öffentliche Simulator auf SteamWar released.
## WarGear Season
Zwischen dem 05.03.2021 und dem 10.07.2021 fand das erste Langzeit Event, die WarGearSeason, statt.
In 77 Fights haben sich 15 Teams duelliert um zu entscheiden, wer die bessere Technik und Taktik hat.
Insgesamt haben 43 Spieler an der ersten WGS teilgenommen.
## Bau Design
Am 19.01.2021 wurde die neue Bauwelt released.
Diese wurde in den Wochen davor von Miny___ und der Hilfe von YoyoNow gebaut und eingerichtet.
## 100.000 Spielstunden
Die ersten 100.000 Spielstunden wurden am 14.01.2021 geknackt.
## WarGear Liga
Die erste und einzige WarGear Liga wurde zwischen dem 09.11.2022 und 15.11.2020 ausgetragen.
Es fanden 29 Kämpfe zwischen 12 Teams statt.
29 Kämpfer haben sich in den Fights duelliert.
## 10.000 Fights
Am 11.06.2020 wurde der 10.000 Fight ausgetragen.
Dabei wurden 6186 MiniWarGear fights, 1583 WarGear fights und 671 35er WarGear fights ausgetragen.
Die restlichen Fights wurden in WarShip, AirShip, MPP WarGear und SeaGiants WarShip gespielt.
## 10.000 Spielstunden
Die ersten 10.000 Spielstunden wurden am 22.02.2020 geknackt.
## Erstes Event
Das erste Event, namens PublicClash2019, wurde am 14.09.2019 veranstaltet.
Mit 4 angemeldeten Teams wurde über 15 Kämpfen entschieden wer der Sieger ist.
Das Event wurde in MiniWarGear, WarGear, WarShip und AirShip ausgetragen.
Insgesamt haben 14 Spieler in den Kämpfen teilgenommen.
## Gründung
SteamWar wird am 18.05.2019 von AdmiralSeekrank gegründet.

View File

@@ -56,7 +56,7 @@ After the 6th shot the amount of projectiles may decrease, and must not increase
Between individual shots of an automatic cannon must be at least 4 seconds of delay (40 redstone ticks, 80 game ticks).
A MiniWarGear may be equipped with up to two automatic cannons.
## Brücke
## Bridge
A MiniWarGear must feature a command bridge in the form of a clearly distinguishable room.
The command bridge must be separated from the rest of the MiniWarGear by doors, fence gates, trapdoors or pistons.
@@ -69,7 +69,7 @@ A command bridge must adhere to the following conditions:
- Controls for at least 4 headlights that are visible from the opposing position
- Controls for automatic cannons (if any are installed)
- Controls for shield technology (if there is any)
- The command bridge is the only place where dispensers which aim at the opponent may be controlled
- The command bridge is the only place where dispensers which shoot fireballs or arrows at the opponent may be controlled
## Design
@@ -155,4 +155,4 @@ The propellant of any one cannon must only affect projectiles of that same canno
- DROPPER
- SHULKER_BOX
- JUKEBOX
- COMPARATOR
- COMPARATOR

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 [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>
</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: 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],
},
},
},