5 Commits

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -49,29 +49,25 @@
function handleScroll() { function handleScroll() {
if (window.scrollY > 0) { if (window.scrollY > 0) {
navbar!.classList.add("before:scale-y-100"); navbar!.classList.add("sw-nav-scrolled");
} else { } else {
navbar!.classList.remove("before:scale-y-100"); navbar!.classList.remove("sw-nav-scrolled");
} }
} }
</script> </script>
<svelte:window onscroll={handleScroll} /> <svelte:window onscroll={handleScroll} />
<nav <nav data-pagefind-ignore class="sw-nav z-20 fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors flex justify-center" bind:this={navbar}>
data-pagefind-ignore
class="z-20 fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors flex justify-center before:backdrop-blur before:shadow-2xl before:absolute before:top-0 before:left-0 before:bottom-0 before:right-0 before:-z-10 before:scale-y-0 before:transition-transform before:origin-top"
bind:this={navbar}
>
<div class="flex flex-row items-center justify-evenly md:justify-between match"> <div class="flex flex-row items-center justify-evenly md:justify-between match">
<a class="flex items-center" href={l("/")}> <a class="flex items-center" href={l("/")}>
{@render logo?.()} {@render logo?.()}
<span class="text-2xl uppercase font-bold text-white hidden md:inline-block"> <span class="sw-nav-title hidden md:inline-block">
{t("navbar.title")} {t("navbar.title")}
<span class="before:scale-y-100" style="display: none" aria-hidden="true"></span> <span class="scrolled-trigger" style="display: none" aria-hidden="true"></span>
</span> </span>
</a> </a>
<div class="flex justify-center flex-wrap"> <div class="flex justify-center flex-wrap gap-2">
<div class="btn-dropdown"> <div class="btn-dropdown">
<button class="btn btn-gray"> <button class="btn btn-gray">
<a href={l("/")}> <a href={l("/")}>
@@ -104,8 +100,6 @@
<a href={l("/rules/megawargear")} class="btn btn-gray">{t("navbar.links.rules.megawg")}</a> <a href={l("/rules/megawargear")} class="btn btn-gray">{t("navbar.links.rules.megawg")}</a>
<a href={l("/rules/microwargear")} class="btn btn-gray">{t("navbar.links.rules.micro")}</a> <a href={l("/rules/microwargear")} class="btn btn-gray">{t("navbar.links.rules.micro")}</a>
<a href={l("/rules/streetfight")} class="btn btn-gray">{t("navbar.links.rules.sf")}</a> <a href={l("/rules/streetfight")} class="btn btn-gray">{t("navbar.links.rules.sf")}</a>
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.ranked")}</h2>
<a href={l("/rangliste/missilewars")} class="btn btn-gray">{t("navbar.links.ranked.mw")}</a>
</div> </div>
</div> </div>
<!-- TODO: Add help center <!-- TODO: Add help center
@@ -144,4 +138,29 @@
.match { .match {
width: min(100vw, 70em); width: min(100vw, 70em);
} }
:global(.sw-nav) {
backdrop-filter: none;
background: transparent;
transition:
background 0.3s ease,
backdrop-filter 0.3s ease,
border-color 0.3s ease;
border-bottom: 1px solid transparent;
}
:global(.sw-nav-scrolled) {
background: rgba(8, 8, 8, 0.85) !important;
backdrop-filter: blur(16px) !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.04) !important;
}
.sw-nav-title {
font-family: "Barlow Condensed", sans-serif;
font-size: 1.3rem;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: #f5f5f5;
}
</style> </style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,10 +4,14 @@ key: 2026-easter_event
description: Das Oster Event 2026 steht an. Wir freuen uns auf Eure Teilnahme! description: Das Oster Event 2026 steht an. Wir freuen uns auf Eure Teilnahme!
created: 2026-03-10T00:00:00.000Z created: 2026-03-10T00:00:00.000Z
tags: tags:
- event - event
- warship - warship
--- ---
## Diese Version ist nicht die Aktuelle!
Bite schaue [hier](/events/2026-OsterEvent)!
Ahoi Matrosen, 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. 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! description: Die WarGear Season 2026 steht an. Wir freuen uns auf Eure Teilnahme!
created: 2026-02-18T00:00:00.000Z created: 2026-02-18T00:00:00.000Z
tags: tags:
- event - event
- wargear - wargear
--- ---
# Diese Version ist nicht die Aktuelle!
Bite schaue [hier](/events/2026-wgs)!
# WarGear Season 2026 # WarGear Season 2026
Die WarGear Season 2026 steht bevor! Die WarGear Season 2026 steht bevor!
Bereitet eure Teams vor und stellt euch einer kompetitiven Season mit klaren Regeln und fairen Bedingungen. Bereitet eure Teams vor und stellt euch einer kompetitiven Season mit klaren Regeln und fairen Bedingungen.
## Allgemeine Infos ## Allgemeine Infos
Version: 1.21.6 Version: 1.21.6
WarGear Schem Type: ??? WarGear Schem Type: ???
Anzahl Teams: ∞ Anzahl Teams: ∞
Anmeldeschluss: 30.04.2026 (Anzahl Teams wird am 25.04.2026 entschieden) Anmeldeschluss: 30.04.2026 (Anzahl Teams wird am 25.04.2026 entschieden)
Einsendeschluss: 07.05.2026 (Ab dann nur noch Public Schematic wählbar) 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. Das Turnierformat wird zwischen dem 25.04.2026 und dem 30.04.2026 entschieden.
## Teams ## Teams
Fighter pro Team: 6 Fighter pro Team: 6
Spectate System: ja Spectate System: ja
Gebannte Spieler: bleiben gebannt Gebannte Spieler: bleiben gebannt
## Schematic ## Schematic
Hotfixes erlaubt: ja (bis max. 4 Stunden vor dem ersten Fight des Tages) Hotfixes erlaubt: ja (bis max. 4 Stunden vor dem ersten Fight des Tages)
Schematic wird während der WGS an den User SteamWar gebunden. 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 ## Challenge System
Am 25.04.2026 wird die Anzahl Teams auf die wir die WGS auslegen festgelegt. Alle dann angemeldeten 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 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 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 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 gekommen ist ersetzt. Das rausgeflogene Team kann sich nicht mehr durch eine Herausforderung in die WGS

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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