Compare commits
5 Commits
f612b15b06
...
2204bb8663
| Author | SHA1 | Date | |
|---|---|---|---|
|
2204bb8663
|
|||
|
43f42c03c0
|
|||
|
1426536c88
|
|||
|
5d365bc744
|
|||
|
d2ee422d6d
|
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -49,29 +49,25 @@
|
||||
|
||||
function handleScroll() {
|
||||
if (window.scrollY > 0) {
|
||||
navbar!.classList.add("before:scale-y-100");
|
||||
navbar!.classList.add("sw-nav-scrolled");
|
||||
} else {
|
||||
navbar!.classList.remove("before:scale-y-100");
|
||||
navbar!.classList.remove("sw-nav-scrolled");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onscroll={handleScroll} />
|
||||
|
||||
<nav
|
||||
data-pagefind-ignore
|
||||
class="z-20 fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors flex justify-center before:backdrop-blur before:shadow-2xl before:absolute before:top-0 before:left-0 before:bottom-0 before:right-0 before:-z-10 before:scale-y-0 before:transition-transform before:origin-top"
|
||||
bind:this={navbar}
|
||||
>
|
||||
<nav data-pagefind-ignore class="sw-nav z-20 fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors flex justify-center" bind:this={navbar}>
|
||||
<div class="flex flex-row items-center justify-evenly md:justify-between match">
|
||||
<a class="flex items-center" href={l("/")}>
|
||||
{@render logo?.()}
|
||||
<span class="text-2xl uppercase font-bold text-white hidden md:inline-block">
|
||||
<span class="sw-nav-title hidden md:inline-block">
|
||||
{t("navbar.title")}
|
||||
<span class="before:scale-y-100" style="display: none" aria-hidden="true"></span>
|
||||
<span class="scrolled-trigger" style="display: none" aria-hidden="true"></span>
|
||||
</span>
|
||||
</a>
|
||||
<div class="flex justify-center flex-wrap">
|
||||
<div class="flex justify-center flex-wrap gap-2">
|
||||
<div class="btn-dropdown">
|
||||
<button class="btn btn-gray">
|
||||
<a href={l("/")}>
|
||||
@@ -104,8 +100,6 @@
|
||||
<a href={l("/rules/megawargear")} class="btn btn-gray">{t("navbar.links.rules.megawg")}</a>
|
||||
<a href={l("/rules/microwargear")} class="btn btn-gray">{t("navbar.links.rules.micro")}</a>
|
||||
<a href={l("/rules/streetfight")} class="btn btn-gray">{t("navbar.links.rules.sf")}</a>
|
||||
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.ranked")}</h2>
|
||||
<a href={l("/rangliste/missilewars")} class="btn btn-gray">{t("navbar.links.ranked.mw")}</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TODO: Add help center
|
||||
@@ -144,4 +138,29 @@
|
||||
.match {
|
||||
width: min(100vw, 70em);
|
||||
}
|
||||
|
||||
:global(.sw-nav) {
|
||||
backdrop-filter: none;
|
||||
background: transparent;
|
||||
transition:
|
||||
background 0.3s ease,
|
||||
backdrop-filter 0.3s ease,
|
||||
border-color 0.3s ease;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
:global(.sw-nav-scrolled) {
|
||||
background: rgba(8, 8, 8, 0.85) !important;
|
||||
backdrop-filter: blur(16px) !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.04) !important;
|
||||
}
|
||||
|
||||
.sw-nav-title {
|
||||
font-family: "Barlow Condensed", sans-serif;
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: #f5f5f5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
37
src/content/events/2026-OsterEvent.md
Normal file
37
src/content/events/2026-OsterEvent.md
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
eventId: 78
|
||||
mode: "warship"
|
||||
verwantwortlicher: "JajaKings"
|
||||
---
|
||||
|
||||
Ahoi Matrosen,
|
||||
|
||||
der Winter zieht sich langsam zurück, die ersten Blumen kämpfen sich durch den Schnee – und auch auf den Werften von SteamWar beginnt wieder geschäftiges Treiben.
|
||||
|
||||
Zu Ostern laden wir euch zu einem besonderen Warship-Event ein!
|
||||
Packt eure Werkzeuge aus, bringt eure Kanonen auf Hochglanz und bereitet eure Schiffe für den Kampf vor.
|
||||
|
||||
Am 25. und 26. April treffen sich die Kapitäne der Flotte, um ihre neuesten Kreationen auf dem Schlachtfeld zu testen. Egal ob altgedienter Konstrukteur oder neuer Schiffsbauer – jeder ist willkommen, seine Ideen zu Wasser zu lassen.
|
||||
|
||||
Ein Osterdesign ist gerne gesehen, aber keine Pflicht. Wer also sein Schiff mit Eiern, Frühlingsfarben oder kleinen Überraschungen schmücken möchte, darf seiner Kreativität freien Lauf lassen.
|
||||
|
||||
Termin
|
||||
|
||||
– 26. April
|
||||
|
||||
Version
|
||||
|
||||
Gespielt wird auf dem aktuellen 1.21 RW
|
||||
|
||||
Was euch erwartet
|
||||
|
||||
spannende Warship-Gefechte
|
||||
|
||||
neue Konstruktionen und kreative Designs
|
||||
|
||||
gemeinsames Event-Wochenende mit der Community
|
||||
|
||||
jede Menge Explosionen und epische Schlachten
|
||||
|
||||
Also: Werft die Maschinen an, hisst die Flaggen und macht eure Schiffe bereit.
|
||||
Wir freuen uns auf ein spannendes Osterwochenende mit euch!
|
||||
44
src/content/events/2026-wgs.md
Normal file
44
src/content/events/2026-wgs.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
eventId: 77
|
||||
mode: "wargear"
|
||||
verwantwortlicher: "JajaKings"
|
||||
---
|
||||
|
||||
# WarGear Season 2026
|
||||
|
||||
Die WarGear Season 2026 steht bevor!
|
||||
Bereitet eure Teams vor und stellt euch einer kompetitiven Season mit klaren Regeln und fairen Bedingungen.
|
||||
|
||||
## Allgemeine Infos
|
||||
|
||||
Version: 1.21.6
|
||||
WarGear Schem Type: ???
|
||||
Anzahl Teams: ∞
|
||||
Anmeldeschluss: 30.04.2026 (Anzahl Teams wird am 25.04.2026 entschieden)
|
||||
Einsendeschluss: 07.05.2026 (Ab dann nur noch Public Schematic wählbar)
|
||||
Erster Spieltag: 02.05.2026
|
||||
|
||||
Das Turnierformat wird zwischen dem 25.04.2026 und dem 30.04.2026 entschieden.
|
||||
|
||||
## Teams
|
||||
|
||||
Fighter pro Team: 6
|
||||
Spectate System: ja
|
||||
Gebannte Spieler: bleiben gebannt
|
||||
|
||||
## Schematic
|
||||
|
||||
Hotfixes erlaubt: ja (bis max. 4 Stunden vor dem ersten Fight des Tages)
|
||||
Schematic wird während der WGS an den User SteamWar gebunden.
|
||||
Regelwerk: WG21 ohne SFA, Windcharges zum komprimieren erlaubt (Regelwerk folgt in kürze)
|
||||
|
||||
## Challenge System
|
||||
|
||||
Am 25.04.2026 wird die Anzahl Teams auf die wir die WGS auslegen festgelegt. Alle dann angemeldeten
|
||||
Teams kommen auf jeden Fall rein. Alle Teams die sich darüber hinaus bis zum 30.04.2026 anmelden rücken
|
||||
bei Abmeldung nach oder können pro Spieltag/Wochenende das/die Team(s) auf dem letzten Platz herausfordern
|
||||
und bei erfolgreicher Herausforderung in die WGS eintreten.
|
||||
|
||||
Wenn ein Team **UNANGEKUNDIGT** an einem Spieltag fehlt, wird es durch ein Team welches nicht mehr in die WGS
|
||||
gekommen ist ersetzt. Das rausgeflogene Team kann sich nicht mehr durch eine Herausforderung in die WGS
|
||||
reinkämpfen.
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"translationKey": "as",
|
||||
"main": true,
|
||||
"ranked": true
|
||||
"ranked": false
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"translationKey": "microwg",
|
||||
"main": false,
|
||||
"ranked": true
|
||||
}
|
||||
"translationKey": "microwg",
|
||||
"main": false,
|
||||
"ranked": false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"translationKey": "mwg",
|
||||
"main": true,
|
||||
"ranked": true
|
||||
}
|
||||
"translationKey": "mwg",
|
||||
"main": true,
|
||||
"ranked": false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"translationKey": "mw",
|
||||
"main": false,
|
||||
"ranked": true
|
||||
}
|
||||
"translationKey": "mw",
|
||||
"main": false,
|
||||
"ranked": false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"translationKey": "wg",
|
||||
"main": true,
|
||||
"ranked": true
|
||||
}
|
||||
"translationKey": "wg",
|
||||
"main": true,
|
||||
"ranked": false
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,9 +3,7 @@ import dayjs from "dayjs";
|
||||
import NavbarLayout from "@layouts/NavbarLayout.astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import { astroI18n } from "astro-i18n";
|
||||
|
||||
import { Image } from "astro:assets";
|
||||
import Card from "@components/Card.svelte";
|
||||
import { CaretRight, Pause, Rocket, Crosshair1 } from "@astropub/icons";
|
||||
import { t } from "astro-i18n";
|
||||
import { l } from "@utils/util";
|
||||
@@ -31,194 +29,217 @@ germanPosts.forEach((value) => {
|
||||
|
||||
const latestPost = posts.sort((a, b) => dayjs(b.data.created).unix() - dayjs(a.data.created).unix()).at(0);
|
||||
|
||||
const prefixColorMap: {
|
||||
[key: string]: string;
|
||||
} = {
|
||||
Admin: "border-red-600 dark:border-red-800 shadow-red-600 dark:shadow-red-800",
|
||||
Dev: "border-sky-600 dark:border-sky-800 shadow-sky-600 dark:shadow-sky-800",
|
||||
Mod: "border-amber-600 dark:border-amber-800 shadow-amber-600 dark:shadow-amber-800",
|
||||
Sup: "border-blue-700 dark:border-blue-900 shadow-blue-700 dark:shadow-blue-900",
|
||||
Arch: "border-green-500 dark:border-green-700 shadow-green-500 dark:shadow-green-700",
|
||||
const prefixColors: { [key: string]: string } = {
|
||||
Admin: "#dc2626",
|
||||
Dev: "#0284c7",
|
||||
Mod: "#d97706",
|
||||
Sup: "#1d4ed8",
|
||||
Arch: "#22c55e",
|
||||
};
|
||||
|
||||
const featClass =
|
||||
"flex flex-col items-center text-center py-10 px-8 border-b border-white/[0.04] md:border-b-0 md:border-r md:last:border-r-0 transition-[background] duration-[400ms] hover:bg-amber-500/[0.025] [&_svg]:text-amber-500 [&_svg]:mb-5 [&_svg]:transition-transform [&_svg]:duration-300 [&:hover_svg]:scale-[1.15]";
|
||||
const featNum = "font-display text-[3.5rem] font-extrabold leading-none text-amber-500/10 mb-5";
|
||||
const featH3 = "font-display text-[1.15rem] font-bold text-white tracking-[0.08em] uppercase mb-3";
|
||||
const featP = "text-neutral-400/85 text-[0.85rem] leading-[1.65] mt-1";
|
||||
---
|
||||
|
||||
<NavbarLayout title={t("home.page")} description="SteamWar.de Homepage" transparentFooter={false}>
|
||||
<div class="w-full h-screen relative mb-4 z-10">
|
||||
<div style="height: calc(100vh + 1rem)">
|
||||
<section class="relative w-full h-screen overflow-hidden">
|
||||
<div class="absolute inset-0 h-[calc(100vh+1rem)]">
|
||||
<BackgroundImage />
|
||||
</div>
|
||||
<drop-in class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center">
|
||||
<h1
|
||||
class="text-4xl sm:text-6xl md:text-8xl font-extrabold text-white -translate-y-16 opacity-0 barlow tracking-wider"
|
||||
style="transition: transform .7s ease-out, opacity .7s linear; filter: drop-shadow(2px 2px 5px black);"
|
||||
>
|
||||
<span class="bg-gradient-to-tr from-yellow-400 to-yellow-300 bg-clip-text text-transparent">{t("home.title.first")}</span><span class="text-neutral-600">{t("home.title.second")}</span>
|
||||
<div class="absolute inset-0 z-[2] [background:linear-gradient(to_bottom,rgba(0,0,0,0.6)_0%,rgba(0,0,0,0.2)_35%,rgba(0,0,0,0.45)_65%,#080808_100%)]"></div>
|
||||
<div class="absolute inset-0 z-[3] pointer-events-none [background:repeating-linear-gradient(0deg,transparent_0px,transparent_3px,rgba(0,0,0,0.06)_3px,rgba(0,0,0,0.06)_4px)]"></div>
|
||||
|
||||
<div class="relative z-10 h-full flex flex-col items-center justify-center px-4">
|
||||
<h1 class="font-display text-[clamp(4.5rem,14vw,14rem)] font-black leading-[0.85] tracking-[-0.03em] select-none animate-[scaleIn_0.9s_0.25s_ease-out_both]">
|
||||
<span class="hero-text text-transparent">{t("home.title.first")}</span><span class="text-white [text-shadow:0_0_80px_rgba(255,255,255,0.06)]">{t("home.title.second")}</span>
|
||||
</h1>
|
||||
<text-carousel class="h-20 w-full relative select-none">
|
||||
<h2 class="-translate-y-16">{t("home.subtitle.1")}</h2>
|
||||
|
||||
<div class="w-0 h-0.5 bg-gradient-to-r from-transparent via-amber-500 to-transparent my-7 animate-[lineGrow_0.6s_0.7s_ease-out_both]"></div>
|
||||
|
||||
<text-carousel class="h-10 sm:h-12 w-full max-w-[28rem] sm:max-w-[32rem] relative pointer-events-none">
|
||||
<h2>{t("home.subtitle.1")}</h2>
|
||||
<h2>
|
||||
{t("home.subtitle.2")}
|
||||
<PlayerCount client:idle />
|
||||
</h2>
|
||||
<h2>{t("home.subtitle.3")}</h2>
|
||||
</text-carousel>
|
||||
<a href={l("join")} class="btn btn-ghost mt-32 px-8 flex" style="animation: normal flyIn forwards 1.2s ease-out"
|
||||
>{t("home.join")}
|
||||
<CaretRight width="24" height="24" />
|
||||
|
||||
<a
|
||||
href={l("join")}
|
||||
class="inline-flex items-center gap-[0.6rem] mt-12 py-[0.8rem] px-[2.8rem] border border-amber-500/50 text-amber-400 font-display text-[0.85rem] tracking-[0.2em] uppercase bg-amber-500/[0.04] backdrop-blur-[12px] [clip-path:polygon(0_0,calc(100%-14px)_0,100%_14px,100%_100%,14px_100%,0_calc(100%-14px))] transition-all duration-[350ms] animate-[fadeUp_0.6s_1s_ease-out_both] hover:bg-amber-500/[0.12] hover:border-amber-500/85 hover:text-white hover:[box-shadow:0_0_40px_rgba(245,158,11,0.1),inset_0_0_40px_rgba(245,158,11,0.03)] hover:scale-[1.04] active:scale-[0.97]"
|
||||
>
|
||||
<span>{t("home.join")}</span>
|
||||
<CaretRight width="18" height="18" />
|
||||
</a>
|
||||
<style>
|
||||
@keyframes flyIn {
|
||||
0% {
|
||||
translate: 0 16px;
|
||||
opacity: 0;
|
||||
}
|
||||
</div>
|
||||
|
||||
20% {
|
||||
translate: 0 16px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
translate: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
class TextCarousel extends HTMLElement {
|
||||
current = 0;
|
||||
|
||||
connectedCallback() {
|
||||
this._current.classList.add("!opacity-100");
|
||||
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
if (i !== this.current) {
|
||||
this.children[i].classList.add("translate-y-8");
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
this.next();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
get _current() {
|
||||
return this.children[this.current];
|
||||
}
|
||||
|
||||
next() {
|
||||
this._current.classList.remove("!opacity-100");
|
||||
this._current.classList.add("translate-y-8");
|
||||
this._current.classList.remove("!delay-500");
|
||||
this.current = (this.current + 1) % this.children.length;
|
||||
this._current.classList.add("!opacity-100");
|
||||
this._current.classList.remove("translate-y-8");
|
||||
this._current.classList.add("!delay-500");
|
||||
}
|
||||
}
|
||||
|
||||
class DropIn extends HTMLElement {
|
||||
connectedCallback() {
|
||||
for (let child of this.children) {
|
||||
if (child.classList.contains("opacity-0")) {
|
||||
child.classList.remove("opacity-0");
|
||||
child.classList.remove("-translate-y-16");
|
||||
} else {
|
||||
child.children[0].classList.remove("opacity-0");
|
||||
child.children[0].classList.remove("-translate-y-16");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("text-carousel", TextCarousel);
|
||||
customElements.define("drop-in", DropIn);
|
||||
</script>
|
||||
</drop-in>
|
||||
<div class="absolute left-1/2 bottom-6 -translate-x-1/2">
|
||||
<div class="absolute bottom-6 left-1/2 -translate-x-1/2 z-20 w-full max-w-[40rem] px-4">
|
||||
<PostComponent post={latestPost} slim={true} />
|
||||
</div>
|
||||
</div>
|
||||
<section class="w-full flex flex-col items-center justify-center shadow-2xl rounded-b-2xl pb-8">
|
||||
<div class="py-10 flex flex-col lg:flex-row">
|
||||
<Card client:idle>
|
||||
<Crosshair1 height="64" width="64" />
|
||||
<h1>{t("home.benefits.fights.title")}</h1>
|
||||
<p class="mt-4">{t("home.benefits.fights.description.1")}</p>
|
||||
<p class="mt-4">{t("home.benefits.fights.description.2")}</p>
|
||||
</Card>
|
||||
<Card client:idle>
|
||||
<Rocket height="64" width="64" />
|
||||
<h1>{t("home.benefits.bau.title")}</h1>
|
||||
<p class="mt-4">{t("home.benefits.bau.description")}</p>
|
||||
</Card>
|
||||
<Card client:idle>
|
||||
<Pause height="64" width="64" />
|
||||
<h1>{t("home.benefits.minigames.title")}</h1>
|
||||
<p class="mt-4">{t("home.benefits.minigames.description.1")}</p>
|
||||
<p class="mt-4">{t("home.benefits.minigames.description.2")}</p>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<section class="relative bg-[#080808] overflow-hidden">
|
||||
<div class="absolute -top-[100px] left-1/2 -translate-x-1/2 w-[500px] h-[250px] [background:radial-gradient(ellipse,rgba(245,158,11,0.06)_0%,transparent_70%)] pointer-events-none"></div>
|
||||
<div class="relative z-[2] max-w-5xl mx-auto py-24 px-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3">
|
||||
<article class={featClass}>
|
||||
<span class={featNum}>01</span>
|
||||
<Crosshair1 height="36" width="36" />
|
||||
<h3 class={featH3}>{t("home.benefits.fights.title")}</h3>
|
||||
<p class={featP}>{t("home.benefits.fights.description.1")}</p>
|
||||
<p class={featP}>{t("home.benefits.fights.description.2")}</p>
|
||||
</article>
|
||||
|
||||
<article class={featClass}>
|
||||
<span class={featNum}>02</span>
|
||||
<Rocket height="36" width="36" />
|
||||
<h3 class={featH3}>{t("home.benefits.bau.title")}</h3>
|
||||
<p class={featP}>{t("home.benefits.bau.description")}</p>
|
||||
</article>
|
||||
|
||||
<article class={featClass}>
|
||||
<span class={featNum}>03</span>
|
||||
<Pause height="36" width="36" />
|
||||
<h3 class={featH3}>{t("home.benefits.minigames.title")}</h3>
|
||||
<p class={featP}>{t("home.benefits.minigames.description.1")}</p>
|
||||
<p class={featP}>{t("home.benefits.minigames.description.2")}</p>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="w-full py-12 flex flex-wrap justify-center">
|
||||
{
|
||||
Object.entries(teamMember).map(([prefix, players]) => (
|
||||
<Fragment>
|
||||
{players.map((v, index) => (
|
||||
<div class="inline-flex flex-col justify-end">
|
||||
{index == 0 ? <h2 class="dark:text-white text-4xl font-bold text-center md:text-left md:pl-4">{t("home.prefix." + prefix)}</h2> : null}
|
||||
<Card extraClasses={`pt-8 pb-10 px-8 w-fit shadow-md ${prefixColorMap[prefix]}`} client:idle>
|
||||
<figure class="flex flex-col items-center" style="width: 150px">
|
||||
<figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption>
|
||||
|
||||
<section class="bg-[#0c0c0c] py-20">
|
||||
<div class="max-w-5xl mx-auto px-4 flex flex-wrap gap-4">
|
||||
{
|
||||
Object.entries(teamMember).map(([prefix, players]) => (
|
||||
<Fragment>
|
||||
{players.map((v, i) => (
|
||||
<div class="flex flex-col justify-end">
|
||||
{i === 0 && (
|
||||
<h2 class="font-display text-[1.35rem] font-bold tracking-[0.12em] uppercase text-[var(--rc)] pb-1 inline-block" style={`--rc: ${prefixColors[prefix] || "#666"}`}>
|
||||
{t("home.prefix." + prefix)}
|
||||
</h2>
|
||||
)}
|
||||
<div
|
||||
class="flex flex-col items-center py-5 px-6 bg-white/[0.025] border-t-2 border-[var(--rc)] transition-[transform,background,box-shadow] duration-300 hover:-translate-y-1.5 hover:bg-white/5 hover:shadow-[0_12px_32px_rgba(0,0,0,0.5)] [&:hover_img]:scale-[1.08]"
|
||||
style={`--rc: ${prefixColors[prefix] || "#666"}`}
|
||||
>
|
||||
<Image
|
||||
src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${v.uuid}`}
|
||||
class="transition duration-300 ease-in-out hover:scale-110 hover:backdrop-blur-lg hover:drop-shadow-2xl"
|
||||
alt={v.name + "s bust"}
|
||||
width="150"
|
||||
height="150"
|
||||
alt={v.name}
|
||||
width="120"
|
||||
height="120"
|
||||
class="w-[120px] h-[120px] transition-transform duration-300"
|
||||
/>
|
||||
</figure>
|
||||
</Card>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
<span class="font-display mt-3 text-[0.9rem] tracking-[0.1em] text-neutral-300/90">{v.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
class TextCarousel extends HTMLElement {
|
||||
current = 0;
|
||||
|
||||
connectedCallback() {
|
||||
this._current.classList.add("!opacity-100");
|
||||
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
if (i !== this.current) {
|
||||
this.children[i].classList.add("translate-y-4");
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(() => this.next(), 5000);
|
||||
}
|
||||
|
||||
get _current() {
|
||||
return this.children[this.current];
|
||||
}
|
||||
|
||||
next() {
|
||||
this._current.classList.remove("!opacity-100");
|
||||
this._current.classList.add("translate-y-4");
|
||||
this._current.classList.remove("!delay-500");
|
||||
this.current = (this.current + 1) % this.children.length;
|
||||
this._current.classList.add("!opacity-100");
|
||||
this._current.classList.remove("translate-y-4");
|
||||
this._current.classList.add("!delay-500");
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("text-carousel", TextCarousel);
|
||||
</script>
|
||||
</NavbarLayout>
|
||||
|
||||
<style>
|
||||
text-carousel {
|
||||
> * {
|
||||
@apply absolute top-0 left-0 w-full text-xl sm:text-4xl italic text-white text-center opacity-0;
|
||||
transition:
|
||||
transform 0.5s ease-out,
|
||||
opacity 0.5s linear;
|
||||
text-shadow: 2px 2px 5px black;
|
||||
.hero-text {
|
||||
background-image: linear-gradient(160deg, #fcd34d, #f59e0b);
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
text-carousel > * {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
font-family: "Barlow Condensed", sans-serif;
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
transform: translateY(8px);
|
||||
transition:
|
||||
transform 0.5s ease-out,
|
||||
opacity 0.5s linear;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
text-carousel > * {
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
}
|
||||
|
||||
.barlow {
|
||||
font-family:
|
||||
Barlow Condensed,
|
||||
sans-serif;
|
||||
@keyframes fadeUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg transition-transform duration-300 ease-in-out
|
||||
dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100
|
||||
hover:scale-105;
|
||||
|
||||
> h1 {
|
||||
@apply text-xl font-bold mt-4;
|
||||
@keyframes scaleIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
> p {
|
||||
@apply mt-4;
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
> svg {
|
||||
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl;
|
||||
@keyframes lineGrow {
|
||||
from {
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
width: 6rem;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,56 +18,137 @@
|
||||
*/
|
||||
|
||||
.btn {
|
||||
@apply bg-yellow-400 font-bold py-2 px-4 rounded cursor-pointer select-none mx-0.5 md:mx-2 text-black flex flex-row;
|
||||
@apply hover:bg-yellow-300 hover:text-black hover:shadow-2xl hover:scale-105;
|
||||
transition: all 0.5s cubic-bezier(.2, 3, .67, .6),
|
||||
background-color .1s ease-in-out,
|
||||
outline-width .1s ease-in-out,
|
||||
outline-color .1s ease-in-out;
|
||||
@apply active:scale-90;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
font-family: "Barlow Condensed", sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
padding: 0.5rem 1.2rem;
|
||||
color: #f5f5f5;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.25s ease;
|
||||
clip-path: polygon(
|
||||
0 0,
|
||||
calc(100% - 8px) 0,
|
||||
100% 8px,
|
||||
100% 100%,
|
||||
8px 100%,
|
||||
0 calc(100% - 8px)
|
||||
);
|
||||
|
||||
.btn__text {
|
||||
@apply inline-block;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: rgba(245, 158, 11, 0.08);
|
||||
border-color: rgba(245, 158, 11, 0.4);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
.btn-dropdown {
|
||||
@apply relative mx-0.5 md:mx-2;
|
||||
position: relative;
|
||||
|
||||
> :nth-child(1) {
|
||||
@apply !mx-0;
|
||||
}
|
||||
|
||||
> :nth-child(2) {
|
||||
@apply hidden absolute top-full left-1/2 -translate-x-1/2 bg-gray-800 list-none text-white rounded py-2 flex-col text-sm z-20;
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #111111;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
list-style: none;
|
||||
padding: 0.5rem 0;
|
||||
flex-direction: column;
|
||||
font-size: 0.8rem;
|
||||
z-index: 20;
|
||||
min-width: 10rem;
|
||||
}
|
||||
|
||||
> :nth-child(2) .btn {
|
||||
clip-path: none;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
&:hover, &:focus-within {
|
||||
> :nth-child(2) {
|
||||
@apply flex;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
@apply px-6 text-xl;
|
||||
@apply bg-transparent border-2 border-yellow-400 shadow-2xl shadow-yellow-400 backdrop-blur text-neutral-800 font-bold bg-gradient-to-br from-yellow-400 to-yellow-200 rounded-xl;
|
||||
@apply hover:border-yellow-300 hover:shadow-yellow-800 hover:scale-110;
|
||||
padding: 0.8rem 2.8rem;
|
||||
font-size: 0.85rem;
|
||||
letter-spacing: 0.2em;
|
||||
color: #fbbf24;
|
||||
background: rgba(245, 158, 11, 0.04);
|
||||
border: 1px solid rgba(245, 158, 11, 0.5);
|
||||
backdrop-filter: blur(12px);
|
||||
clip-path: polygon(
|
||||
0 0,
|
||||
calc(100% - 14px) 0,
|
||||
100% 14px,
|
||||
100% 100%,
|
||||
14px 100%,
|
||||
0 calc(100% - 14px)
|
||||
);
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
background: rgba(245, 158, 11, 0.12);
|
||||
border-color: rgba(245, 158, 11, 0.85);
|
||||
color: #fff;
|
||||
box-shadow: 0 0 40px rgba(245, 158, 11, 0.1);
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.btn-gray {
|
||||
@apply bg-gray-800 text-white;
|
||||
background: transparent;
|
||||
border-color: rgba(255, 255, 255, 0.06);
|
||||
color: rgba(212, 212, 212, 0.9);
|
||||
}
|
||||
|
||||
.btn-gray:hover {
|
||||
background: rgba(245, 158, 11, 0.06);
|
||||
border-color: rgba(245, 158, 11, 0.3);
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.btn-neutral {
|
||||
@apply bg-neutral-800 text-white;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-color: rgba(255, 255, 255, 0.08);
|
||||
color: #f5f5f5;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
@apply bg-transparent underline text-white;
|
||||
@apply hover:bg-transparent hover:outline hover:outline-1;
|
||||
|
||||
.btn__text {
|
||||
@apply underline;
|
||||
}
|
||||
background: transparent;
|
||||
border: none;
|
||||
clip-path: none;
|
||||
color: #f59e0b;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
|
||||
.btn-text:hover {
|
||||
background: transparent;
|
||||
border-bottom-color: #f59e0b;
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ const config = {
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [...fontFamily.sans],
|
||||
sans: ["Roboto", ...fontFamily.sans],
|
||||
display: ["Barlow Condensed", ...fontFamily.sans],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user