47 Commits

Author SHA1 Message Date
bc879d7cad Add login.page to de.json
All checks were successful
SteamWarCI Build successful
2025-06-26 15:02:08 +02:00
TheBreadBeard
96f0019dc1 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-22 16:55:31 +02:00
TheBreadBeard
7418b608ab Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-21 23:33:31 +02:00
TheBreadBeard
3802b9bc26 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-13 14:21:16 +02:00
03effd2fd2 src/content/downloads/advancedscripts.json aktualisiert
All checks were successful
SteamWarCI Build successful
2025-06-12 20:35:23 +02:00
TheBreadBeard
a4669a897b Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-06 19:20:33 +02:00
eac0d5592d src/i18n/common/de.json aktualisiert
All checks were successful
SteamWarCI Build successful
2025-06-03 14:11:22 +02:00
TheBreadBeard
bd9aea8f35 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-03 09:53:14 +02:00
TheBreadBeard
6e715cee07 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-03 07:11:00 +02:00
TheBreadBeard
4147a1d243 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-02 23:46:46 +02:00
TheBreadBeard
46dba2a6f9 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-02 23:44:12 +02:00
TheBreadBeard
3d8ad3a129 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-02 23:40:31 +02:00
TheBreadBeard
7d50a4db12 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-06-02 23:30:42 +02:00
4ecb5fa024 Merge branch 'master' of https://git.steamwar.de/SteamWar/Website
All checks were successful
SteamWarCI Build successful
2025-05-31 21:02:48 +02:00
27f0b962c1 feat: Enhance article styling with code and link formatting 2025-05-31 21:02:24 +02:00
e37583329c src/content/pages/de/verhaltensrichtlienien.md aktualisiert
All checks were successful
SteamWarCI Build successful
2025-05-31 20:55:31 +02:00
20b7a32b1b package.json aktualisiert
All checks were successful
SteamWarCI Build successful
2025-05-31 20:54:29 +02:00
dd7d701c48 Fix formatting
Some checks failed
SteamWarCI Build failed
2025-05-31 09:56:01 +02:00
3173b537bc Fix 'letztes update'
Some checks failed
SteamWarCI Build failed
2025-05-31 09:53:36 +02:00
5e2e4e2281 Fix some verhaltensrichtlienien.md not copied over from the old version.
Some checks failed
SteamWarCI Build failed
2025-05-31 09:50:52 +02:00
TheBreadBeard
86bfaf4683 Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-05-26 10:56:46 +02:00
TheBreadBeard
f9212649ad Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-05-26 10:55:26 +02:00
TheBreadBeard
4972ebf9bb Update spacheship-event-ankündigung-und-regelwerk.md
All checks were successful
SteamWarCI Build successful
2025-05-25 19:19:47 +02:00
d5a2fc20e8 src/content/announcements/de/spacheship-event-ankündigung-und-regelwerk.md aktualisiert
All checks were successful
SteamWarCI Build successful
2025-05-25 17:31:44 +02:00
27c5698ac8 tsconfig.json aktualisiert
All checks were successful
SteamWarCI Build successful
2025-05-25 17:27:55 +02:00
fa5f25f37e Merge pull request 'Merge branch spacheship-event-ankündigung-und-regelwerk' (#15) from spacheship-event-ankündigung-und-regelwerk into master
Some checks failed
SteamWarCI Build failed
Reviewed-on: #15
2025-05-25 17:26:03 +02:00
260b7b24c4 src/content/announcements/de/spacheship-event-ankündigung-und-regelwerk.md aktualisiert
All checks were successful
SteamWarCI Build successful
2025-05-25 17:25:56 +02:00
TheBreadBeard
4aea0c7fea Update spacheship-event-ankündigung-und-regelwerk-2.md
Some checks failed
SteamWarCI Build failed
2025-05-25 17:20:45 +02:00
TheBreadBeard
314ff3e7c3 Create page announcements/de/spacheship-event-ankündigung-und-regelwerk.md
Some checks failed
SteamWarCI Build failed
2025-05-25 14:47:46 +02:00
2f2c1be958 Merge pull request 'Fix micro rw' (#13) from fix-micro-rw into master
All checks were successful
SteamWarCI Build successful
Reviewed-on: #13
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2025-05-21 23:39:31 +02:00
D4rkr34lm
41c7df0d68 Fix micro rw
All checks were successful
SteamWarCI Build successful
2025-05-21 23:37:08 +02:00
Chaoscaot
cedf641039 Update sw-arcade-fightplan.md
All checks were successful
SteamWarCI Build successful
2025-05-18 13:41:00 +02:00
d9bdc636e3 Merge pull request 'Merge branch sw-arcade-fightplan' (#12) from sw-arcade-fightplan into master
All checks were successful
SteamWarCI Build successful
Reviewed-on: #12
2025-05-14 19:44:22 +02:00
Chaoscaot
c8d05cb268 Update sw-arcade-fightplan.md
All checks were successful
SteamWarCI Build successful
2025-05-14 19:43:02 +02:00
cb2564c9ce src/content/announcements/de/sw-arcade-fightplan.md aktualisiert
Some checks failed
SteamWarCI Build failed
2025-05-14 19:37:05 +02:00
Chaoscaot
80caf8fe6d Create page announcements/de/sw-arcade-fightplan
All checks were successful
SteamWarCI Build successful
2025-05-14 19:36:07 +02:00
Tim7077
fd3d621fd5 Update warship.md
All checks were successful
SteamWarCI Build successful
2025-05-10 21:44:18 +02:00
6377799e1b style: Improve code formatting and readability across multiple components
All checks were successful
SteamWarCI Build successful
2025-05-07 14:33:48 +02:00
b3598e1ee1 style: Improve code formatting and readability in FightStatistics component
All checks were successful
SteamWarCI Build successful
2025-05-06 13:42:49 +02:00
b9db5be858 style: Improve formatting and readability of WarShip rules
All checks were successful
SteamWarCI Build successful
2025-04-22 23:34:13 +02:00
3e54934806 feat: Enable autoDarkMode in Basic layout for admin new page
All checks were successful
SteamWarCI Build successful
2025-04-18 12:46:21 +02:00
98638f94fc feat: Add autoDarkMode support to Basic layout and update admin index
All checks were successful
SteamWarCI Build successful
2025-04-18 12:43:09 +02:00
4da8fe50c0 feat: Refactor EventEdit and EventFightList components for improved UI and functionality
All checks were successful
SteamWarCI Build successful
- Enhanced EventEdit component with AlertDialog for delete confirmation.
- Added Menubar component to EventFightList for batch editing options.
- Updated alert-dialog components to streamline props and improve reactivity.
- Refactored menubar components for better structure and usability.
- Improved accessibility and code readability across various components.
2025-04-16 12:55:10 +02:00
7757978668 refactor: clean up imports and improve player search functionality in RefereesList
All checks were successful
SteamWarCI Build successful
2025-04-16 00:17:10 +02:00
9eea0b2b3f feat: enhance EventFightList with grouping and selection features
All checks were successful
SteamWarCI Build successful
- Added grouping functionality to the EventFightList component, allowing fights to be grouped by their associated group.
- Implemented row selection with checkboxes for both individual fights and groups, enabling bulk selection.
- Updated columns definition to include a checkbox for selecting all rows and individual row selection checkboxes.
- Modified the checkbox component to support indeterminate state and improved styling.
- Enhanced date formatting for fight start times in the table.
2025-04-15 16:28:19 +02:00
063638d016 Add TeamTable component and improve EventView layout
All checks were successful
SteamWarCI Build successful
2025-04-14 23:31:19 +02:00
f5a778d9b4 Trigger Rebuild
All checks were successful
SteamWarCI Build successful
2025-04-14 18:21:26 +02:00
51 changed files with 953 additions and 9851 deletions

1
.gitignore vendored
View File

@@ -26,3 +26,4 @@ pnpm-debug.log*
/src/env.d.ts /src/env.d.ts
/src/pages/en/ /src/pages/en/
/.idea /.idea
pnpm-lock.yaml

View File

@@ -21,6 +21,7 @@
"@astrojs/tailwind": "^5.1.5", "@astrojs/tailwind": "^5.1.5",
"@astropub/icons": "^0.2.0", "@astropub/icons": "^0.2.0",
"@internationalized/date": "^3.7.0", "@internationalized/date": "^3.7.0",
"@lucide/svelte": "^0.488.0",
"@types/color": "^4.2.0", "@types/color": "^4.2.0",
"@types/node": "^22.9.3", "@types/node": "^22.9.3",
"@types/three": "^0.170.0", "@types/three": "^0.170.0",
@@ -60,7 +61,7 @@
"@codemirror/lang-json": "^6.0.1", "@codemirror/lang-json": "^6.0.1",
"@ddietr/codemirror-themes": "^1.4.4", "@ddietr/codemirror-themes": "^1.4.4",
"@tanstack/table-core": "^8.21.2", "@tanstack/table-core": "^8.21.2",
"astro": "^5.1.8", "astro": "5.7.14",
"astro-i18n": "^2.2.4", "astro-i18n": "^2.2.4",
"astro-robots-txt": "^1.0.0", "astro-robots-txt": "^1.0.0",
"astro-seo": "^0.8.4", "astro-seo": "^0.8.4",

9276
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,13 +18,13 @@
--> -->
<script lang="ts"> <script lang="ts">
import {twMerge} from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import {onMount} from "svelte"; import { onMount } from "svelte";
let cardElement: HTMLDivElement = $state(); let cardElement: HTMLDivElement = $state();
function rotateElement(event: MouseEvent) { function rotateElement(event: MouseEvent) {
if(!hoverEffect) return; if (!hoverEffect) return;
const x = event.clientX; const x = event.clientX;
const y = event.clientY; const y = event.clientY;
@@ -36,23 +36,23 @@
const rotateX = (centerY - y) / 20; const rotateX = (centerY - y) / 20;
const rotateY = -(centerX - x) / 20; const rotateY = -(centerX - x) / 20;
cardElement.style.setProperty('--rotate-x', `${rotateX}deg`); cardElement.style.setProperty("--rotate-x", `${rotateX}deg`);
cardElement.style.setProperty('--rotate-y', `${rotateY}deg`); cardElement.style.setProperty("--rotate-y", `${rotateY}deg`);
} }
function resetElement() { function resetElement() {
cardElement.style.setProperty('--rotate-x', "0"); cardElement.style.setProperty("--rotate-x", "0");
cardElement.style.setProperty('--rotate-y', "0"); cardElement.style.setProperty("--rotate-y", "0");
} }
interface Props { interface Props {
hoverEffect?: boolean; hoverEffect?: boolean;
extraClasses?: string; extraClasses?: string;
children?: import('svelte').Snippet; children?: import("svelte").Snippet;
} }
let { hoverEffect = true, extraClasses = '', children }: Props = $props(); let { hoverEffect = true, extraClasses = "", children }: Props = $props();
let classes = $derived(twMerge("w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100", extraClasses)) 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));
</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>
@@ -61,20 +61,20 @@
<style lang="scss"> <style lang="scss">
div { div {
transform: perspective(1000px) rotateX(var(--rotate-x, 0)) rotateY(var(--rotate-y, 0)) !important; transform: perspective(1000px) rotateX(var(--rotate-x, 0)) rotateY(var(--rotate-y, 0)) !important;
transition: scale 300ms cubic-bezier(.2, 3, .67, .6); transition: scale 300ms cubic-bezier(0.2, 3, 0.67, 0.6);
:global(h1) { :global(h1) {
@apply text-xl font-bold mt-4; @apply text-xl font-bold mt-4;
} }
:global(svg) { :global(svg) {
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl @apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl;
} }
} }
.hoverEffect:hover { .hoverEffect:hover {
scale: 105%; scale: 105%;
} }
</style> </style>

View File

@@ -19,8 +19,8 @@
<script lang="ts"> <script lang="ts">
import FightStatsChart from "./FightStatsChart.svelte"; import FightStatsChart from "./FightStatsChart.svelte";
import {t} from "astro-i18n"; import { t } from "astro-i18n";
import {statsRepo} from "@repo/stats.ts"; import { statsRepo } from "@repo/stats.ts";
let request = getStats(); let request = getStats();
@@ -35,4 +35,4 @@
<FightStatsChart data={stats} /> <FightStatsChart data={stats} />
{:catch error} {:catch error}
<p>error: {error}</p> <p>error: {error}</p>
{/await} {/await}

View File

@@ -19,18 +19,18 @@
<script lang="ts"> <script lang="ts">
import "../styles/button.css"; import "../styles/button.css";
import {CaretDownOutline, SearchOutline} from "flowbite-svelte-icons"; import { CaretDownOutline, SearchOutline } from "flowbite-svelte-icons";
import {t} from "astro-i18n"; import { t } from "astro-i18n";
import {l} from "../util/util"; import { l } from "../util/util";
import {onMount} from "svelte"; import { onMount } from "svelte";
import {loggedIn} from "@repo/authv2.ts"; import { loggedIn } from "@repo/authv2.ts";
interface Props { interface Props {
logo?: import('svelte').Snippet; logo?: import("svelte").Snippet;
} }
let { logo }: Props = $props(); let { logo }: Props = $props();
let navbar = $state<HTMLDivElement>(); let navbar = $state<HTMLElement>();
let searchOpen = $state(false); let searchOpen = $state(false);
let accountBtn = $state<HTMLAnchorElement>(); let accountBtn = $state<HTMLAnchorElement>();
@@ -41,11 +41,11 @@
} else { } else {
accountBtn!.href = l("/login"); accountBtn!.href = l("/login");
} }
}) });
onMount(() => { onMount(() => {
handleScroll(); handleScroll();
}) });
function handleScroll() { function handleScroll() {
if (window.scrollY > 0) { if (window.scrollY > 0) {
@@ -56,13 +56,17 @@
} }
</script> </script>
<svelte:window onscroll={handleScroll}/> <svelte:window onscroll={handleScroll} />
<nav data-pagefind-ignore class="fixed top-0 left-0 right-0 sm:px-4 py-1 transition-colors z-10 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="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 dark:text-white hidden md:inline-block"> <span class="text-2xl uppercase font-bold text-white 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="before:scale-y-100" style="display: none" aria-hidden="true"></span>
</span> </span>
@@ -73,7 +77,7 @@
<a href={l("/")}> <a href={l("/")}>
<span class="btn__text">{t("navbar.links.home.title")}</span> <span class="btn__text">{t("navbar.links.home.title")}</span>
</a> </a>
<CaretDownOutline class="ml-2 mt-auto"/> <CaretDownOutline class="ml-2 mt-auto" />
</button> </button>
<div> <div>
<a class="btn btn-gray" href={l("/announcements")}>{t("navbar.links.home.announcements")}</a> <a class="btn btn-gray" href={l("/announcements")}>{t("navbar.links.home.announcements")}</a>
@@ -87,7 +91,7 @@
<a rel="prefetch" href={l("/rules")}> <a rel="prefetch" href={l("/rules")}>
<span class="btn__text">{t("navbar.links.rules.title")}</span> <span class="btn__text">{t("navbar.links.rules.title")}</span>
</a> </a>
<CaretDownOutline class="ml-2 mt-auto"/> <CaretDownOutline class="ml-2 mt-auto" />
</button> </button>
<div> <div>
<a href={l("/rules/wargear")} class="btn btn-gray">{t("navbar.links.rules.wg")}</a> <a href={l("/rules/wargear")} class="btn btn-gray">{t("navbar.links.rules.wg")}</a>
@@ -96,10 +100,8 @@
<a href={l("/rules/airship")} class="btn btn-gray">{t("navbar.links.rules.as")}</a> <a href={l("/rules/airship")} class="btn btn-gray">{t("navbar.links.rules.as")}</a>
<a href={l("/rules/quickgear")} class="btn btn-gray">{t("navbar.links.rules.qg")}</a> <a href={l("/rules/quickgear")} class="btn btn-gray">{t("navbar.links.rules.qg")}</a>
<h2 class="px-2 text-gray-300">{t("navbar.links.rules.rotating")}</h2> <h2 class="px-2 text-gray-300">{t("navbar.links.rules.rotating")}</h2>
<a href={l("/rules/megawargear")} <a href={l("/rules/megawargear")} class="btn btn-gray">{t("navbar.links.rules.megawg")}</a>
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> <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> <a href={l("/rangliste/missilewars")} class="btn btn-gray">{t("navbar.links.ranked.mw")}</a>
@@ -141,4 +143,4 @@
.match { .match {
width: min(100vw, 70em); width: min(100vw, 70em);
} }
</style> </style>

View File

@@ -1,50 +1,60 @@
--- ---
import type {CollectionEntry} from "astro:content"; import type { CollectionEntry } from "astro:content";
import {l} from "../util/util"; import { l } from "../util/util";
import {astroI18n} from "astro-i18n"; import { astroI18n } from "astro-i18n";
import {Image} from "astro:assets"; import { Image } from "astro:assets";
import TagComponent from "./TagComponent.astro"; import TagComponent from "./TagComponent.astro";
import P from "./P.astro"; import P from "./P.astro";
import Card from "@components/Card.svelte"; import Card from "@components/Card.svelte";
interface Props { interface Props {
post: CollectionEntry<"announcements"> post: CollectionEntry<"announcements">;
} }
const { post, slim }: { const {
post: CollectionEntry<"announcements">, post,
slim: boolean, slim,
}: {
post: CollectionEntry<"announcements">;
slim: boolean;
} = Astro.props as Props; } = Astro.props as Props;
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" : ""}`} hoverEffect={false}> <Card extraClasses={`w-full items-start mx-0 ${slim ? "m-0 p-1 backdrop-blur-xl bg-transparent" : ""}`} 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 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="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" transition:name={post.data.title + "-title"}>{post.data.title}</h2>
<P class="text-gray-500">{Intl.DateTimeFormat(astroI18n.locale, { <P class="text-gray-500"
day: "numeric", >{
month: "long", Intl.DateTimeFormat(astroI18n.locale, {
year: "numeric", day: "numeric",
}).format(post.data.created)}</P> month: "long",
year: "numeric",
}).format(post.data.created)
}</P
>
<P>{post.data.description}</P> <P>{post.data.description}</P>
</a> </a>
<div class="mt-1" transition:name={post.data.title + "-tags"}> <div class="mt-1" transition:name={post.data.title + "-tags"}>
{post.data.tags.map((tag) => ( {post.data.tags.map((tag) => <TagComponent tag={tag} />)}
<TagComponent tag={tag} />
))}
</div> </div>
</div> </div>
</div> </div>
</Card> </Card>

View File

@@ -18,24 +18,35 @@
--> -->
<script lang="ts"> <script lang="ts">
import {Input} from "@components/ui/input"; import { Input } from "@components/ui/input";
import {Label} from "@components/ui/label"; import { Label } from "@components/ui/label";
import {Popover, PopoverContent, PopoverTrigger} from "@components/ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover";
import type {SWEvent} from "@type/event.ts" import type { SWEvent } from "@type/event.ts";
import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte"; import DateTimePicker from "@components/ui/datetime-picker/DateTimePicker.svelte";
import {fromAbsolute} from "@internationalized/date"; import { fromAbsolute } from "@internationalized/date";
import {Button} from "@components/ui/button"; import { Button, buttonVariants } from "@components/ui/button";
import {ChevronsUpDown} from "lucide-svelte"; import { ChevronsUpDown } from "lucide-svelte";
import {Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList} from "@components/ui/command"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command";
import {schemTypes} from "@stores/stores.ts"; import { schemTypes } from "@stores/stores.ts";
import Check from "lucide-svelte/icons/check"; import Check from "lucide-svelte/icons/check";
import {cn} from "@components/utils.ts"; import { cn } from "@components/utils.ts";
import {Switch} from "@components/ui/switch"; import { Switch } from "@components/ui/switch";
import {eventRepo} from "@repo/event.ts"; import { eventRepo } from "@repo/event.ts";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@components/ui/alert-dialog";
const { event }: { event: SWEvent } = $props(); const { event }: { event: SWEvent } = $props();
let rootEvent: SWEvent = $state(event) let rootEvent: SWEvent = $state(event);
let eventName = $state(rootEvent.name); let eventName = $state(rootEvent.name);
let eventDeadline = $state(fromAbsolute(rootEvent.deadline, "Europe/Berlin")); let eventDeadline = $state(fromAbsolute(rootEvent.deadline, "Europe/Berlin"));
@@ -45,13 +56,15 @@
let eventSchematicType = $state(rootEvent.schemType); let eventSchematicType = $state(rootEvent.schemType);
let eventPublicsOnly = $state(rootEvent.publicSchemsOnly); let eventPublicsOnly = $state(rootEvent.publicSchemsOnly);
let dirty = $derived(eventName !== rootEvent.name || let dirty = $derived(
eventDeadline.toDate().getTime() !== rootEvent.deadline || eventName !== rootEvent.name ||
eventStart.toDate().getTime() !== rootEvent.start || eventDeadline.toDate().getTime() !== rootEvent.deadline ||
eventEnd.toDate().getTime() !== rootEvent.end || eventStart.toDate().getTime() !== rootEvent.start ||
eventTeamSize !== rootEvent.maxTeamMembers || eventEnd.toDate().getTime() !== rootEvent.end ||
eventSchematicType !== rootEvent.schemType || eventTeamSize !== rootEvent.maxTeamMembers ||
eventPublicsOnly !== rootEvent.publicSchemsOnly); eventSchematicType !== rootEvent.schemType ||
eventPublicsOnly !== rootEvent.publicSchemsOnly
);
async function updateEvent() { async function updateEvent() {
rootEvent = await $eventRepo.updateEvent(event.id.toString(), { rootEvent = await $eventRepo.updateEvent(event.id.toString(), {
@@ -62,7 +75,7 @@
maxTeamMembers: eventTeamSize, maxTeamMembers: eventTeamSize,
schemType: eventSchematicType, schemType: eventSchematicType,
publicSchemsOnly: eventPublicsOnly, publicSchemsOnly: eventPublicsOnly,
}) });
} }
</script> </script>
@@ -81,13 +94,8 @@
<Popover> <Popover>
<PopoverTrigger> <PopoverTrigger>
{#snippet child({ props })} {#snippet child({ props })}
<Button <Button variant="outline" class="justify-between" {...props} role="combobox">
variant="outline" {$schemTypes.find((value) => value.db === eventSchematicType)?.name || eventSchematicType || "Select a schematic type..."}
class="justify-between"
{...props}
role="combobox"
>
{$schemTypes.find(value => value.db === eventSchematicType)?.name || eventSchematicType || "Select a schematic type..."}
<ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" /> <ChevronsUpDown class="ml-2 size-4 shrink-0 opacity-50" />
</Button> </Button>
{/snippet} {/snippet}
@@ -100,17 +108,12 @@
<CommandGroup> <CommandGroup>
{#each $schemTypes as type} {#each $schemTypes as type}
<CommandItem <CommandItem
value={type.db} value={type.db}
onSelect={() => { onSelect={() => {
eventSchematicType = type.db; eventSchematicType = type.db;
}} }}
> >
<Check <Check class={cn("mr-2 size-4", eventSchematicType !== type.db && "text-transparent")} />
class={cn(
"mr-2 size-4",
eventSchematicType !== type.db && "text-transparent"
)}
/>
{type.name} {type.name}
</CommandItem> </CommandItem>
{/each} {/each}
@@ -122,7 +125,19 @@
<Label for="event-publics">Publics Schematics Only</Label> <Label for="event-publics">Publics Schematics Only</Label>
<Switch id="event-publics" bind:checked={eventPublicsOnly} /> <Switch id="event-publics" bind:checked={eventPublicsOnly} />
<div class="flex flex-row justify-end border-t pt-2 gap-4"> <div class="flex flex-row justify-end border-t pt-2 gap-4">
<Button variant="destructive">Delete</Button> <AlertDialog>
<AlertDialogTrigger class={buttonVariants({ variant: "destructive" })}>Delete</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction disabled>Delete</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<Button disabled={!dirty} onclick={updateEvent}>Update</Button> <Button disabled={!dirty} onclick={updateEvent}>Update</Button>
</div> </div>
</div> </div>

View File

@@ -18,26 +18,27 @@
--> -->
<script lang="ts"> <script lang="ts">
import type {ExtendedEvent} from "@type/event"; import type { ExtendedEvent } from "@type/event";
import {createSvelteTable, FlexRender} from "@components/ui/data-table"; import { createSvelteTable, FlexRender } from "@components/ui/data-table";
import { import { type ColumnFiltersState, getCoreRowModel, getFilteredRowModel, getGroupedRowModel, getSortedRowModel, type RowSelectionState, type SortingState } from "@tanstack/table-core";
type ColumnFiltersState, import { columns } from "./columns";
getCoreRowModel, getFilteredRowModel, import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@components/ui/table";
getPaginationRowModel, getSortedRowModel, import { Checkbox } from "@components/ui/checkbox";
type SortingState, import { Menubar, MenubarContent, MenubarItem, MenubarGroup, MenubarGroupHeading, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@components/ui/menubar";
} from "@tanstack/table-core";
import { columns } from "./columns"
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@components/ui/table";
let { data }: { data: ExtendedEvent } = $props(); let { data }: { data: ExtendedEvent } = $props();
let sorting = $state<SortingState>([]); let sorting = $state<SortingState>([]);
let columnFilters = $state<ColumnFiltersState>([]); let columnFilters = $state<ColumnFiltersState>([]);
let selection = $state<RowSelectionState>({});
const table = createSvelteTable({ const table = createSvelteTable({
get data() { get data() {
return data.fights; return data.fights;
}, },
initialState: {
columnOrder: ["auswahl", "begegnung", "group"],
},
state: { state: {
get sorting() { get sorting() {
return sorting; return sorting;
@@ -45,6 +46,12 @@
get columnFilters() { get columnFilters() {
return columnFilters; return columnFilters;
}, },
get grouping() {
return ["group"];
},
get rowSelection() {
return selection;
},
}, },
onSortingChange: (updater) => { onSortingChange: (updater) => {
if (typeof updater === "function") { if (typeof updater === "function") {
@@ -60,13 +67,47 @@
columnFilters = updater; columnFilters = updater;
} }
}, },
onRowSelectionChange: (updater) => {
if (typeof updater === "function") {
selection = updater(selection);
} else {
selection = updater;
}
},
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
getGroupedRowModel: getGroupedRowModel(),
groupedColumnMode: "remove",
getRowId: (row) => row.id.toString(),
}); });
</script> </script>
<div class="w-fit">
<Menubar>
<MenubarMenu>
<MenubarTrigger>Mehrfach Bearbeiten</MenubarTrigger>
<MenubarContent>
<MenubarItem disabled>Gruppe Ändern</MenubarItem>
<MenubarItem disabled>Startzeit Verschieben</MenubarItem>
<MenubarItem disabled>Spectate Port Ändern</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Erstellen</MenubarTrigger>
<MenubarContent>
<MenubarItem disabled>Fight Erstellen</MenubarItem>
<MenubarGroup>
<MenubarGroupHeading>Generatoren</MenubarGroupHeading>
<MenubarItem disabled>Gruppenphase</MenubarItem>
<MenubarItem disabled>K.O. Phase</MenubarItem>
</MenubarGroup>
</MenubarContent>
</MenubarMenu>
</Menubar>
</div>
<Table> <Table>
<TableHeader> <TableHeader>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)} {#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
@@ -74,10 +115,7 @@
{#each headerGroup.headers as header (header.id)} {#each headerGroup.headers as header (header.id)}
<TableHead> <TableHead>
{#if !header.isPlaceholder} {#if !header.isPlaceholder}
<FlexRender <FlexRender content={header.column.columnDef.header} context={header.getContext()} />
content={header.column.columnDef.header}
context={header.getContext()}
/>
{/if} {/if}
</TableHead> </TableHead>
{/each} {/each}
@@ -85,23 +123,41 @@
{/each} {/each}
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{#each table.getRowModel().rows as row (row.id)} {#each table.getRowModel().rows as groupRow (groupRow.id)}
<TableRow data-state={row.getIsSelected() && "selected"}> {#if groupRow.getIsGrouped()}
{#each row.getVisibleCells() as cell (cell.id)} <TableRow class="bg-muted font-bold">
<TableCell> <TableCell colspan={columns.length}>
<FlexRender <Checkbox
content={cell.column.columnDef.cell} checked={groupRow.getIsSelected()}
context={cell.getContext()} indeterminate={groupRow.getIsSomeSelected() && !groupRow.getIsSelected()}
onCheckedChange={() => groupRow.toggleSelected()}
class="mr-4"
/> />
Gruppe: {groupRow.getValue("group") ?? "Keine"}
</TableCell> </TableCell>
</TableRow>
{#each groupRow.subRows as row (row.id)}
<TableRow data-state={row.getIsSelected() && "selected"}>
{#each row.getVisibleCells() as cell (cell.id)}
<TableCell>
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</TableCell>
{/each}
</TableRow>
{/each} {/each}
</TableRow> {:else}
<TableRow data-state={groupRow.getIsSelected() && "selected"}>
{#each groupRow.getVisibleCells() as cell (cell.id)}
<TableCell>
<FlexRender content={cell.column.columnDef.cell} context={cell.getContext()} />
</TableCell>
{/each}
</TableRow>
{/if}
{:else} {:else}
<TableRow> <TableRow>
<TableCell colspan={columns.length} class="h-24 text-center"> <TableCell colspan={columns.length} class="h-24 text-center">No results.</TableCell>
No results.
</TableCell>
</TableRow> </TableRow>
{/each} {/each}
</TableBody> </TableBody>
</Table> </Table>

View File

@@ -18,14 +18,13 @@
--> -->
<script lang="ts"> <script lang="ts">
import type {ExtendedEvent} from "@type/event.ts"; import type { ExtendedEvent } from "@type/event.ts";
import EventEdit from "@components/moderator/pages/event/EventEdit.svelte"; import EventEdit from "@components/moderator/pages/event/EventEdit.svelte";
import EventFightList from "@components/moderator/pages/event/EventFightList.svelte"; import EventFightList from "@components/moderator/pages/event/EventFightList.svelte";
import RefereesList from "@components/moderator/pages/event/RefereesList.svelte"; import RefereesList from "@components/moderator/pages/event/RefereesList.svelte";
import TeamTable from "@components/moderator/pages/event/TeamTable.svelte";
const { const { event }: { event: ExtendedEvent } = $props();
event
}: { event: ExtendedEvent } = $props();
</script> </script>
<div class="flex flex-col m-4 p-4 rounded-md border gap-4"> <div class="flex flex-col m-4 p-4 rounded-md border gap-4">
@@ -35,12 +34,12 @@
<EventEdit event={event.event} /> <EventEdit event={event.event} />
</div> </div>
<div class="md:ml-4 md:pl-4 md:border-l md:w-1/3"> <div class="md:ml-4 md:pl-4 md:border-l md:w-1/3">
<h2>Teams</h2> <h2 class="text-xl font-bold mb-4">Teams</h2>
<TeamTable {event} />
</div> </div>
<div class="md:ml-4 md:pl-4 md:border-l md:w-1/3"> <div class="md:ml-4 md:pl-4 md:border-l md:w-1/3">
<h2>Referees</h2> <h2 class="text-xl font-bold mb-4">Referees</h2>
<RefereesList event={event} /> <RefereesList {event} />
</div> </div>
</div> </div>
<EventFightList data={event} /> <EventFightList data={event} />

View File

@@ -18,38 +18,35 @@
--> -->
<script lang="ts"> <script lang="ts">
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@components/ui/table/index.js"; import { Table, TableBody, TableCell, TableCaption, TableHead, TableHeader, TableRow } from "@components/ui/table";
import { import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@components/ui/command/index.js";
Command, import { Popover, PopoverContent, PopoverTrigger } from "@components/ui/popover/index.js";
CommandEmpty, import { Button } from "@components/ui/button/index.js";
CommandGroup, import type { ExtendedEvent } from "@type/event.ts";
CommandInput,
CommandItem,
CommandList,
} from "@components/ui/command/index.js";
import {Popover, PopoverContent, PopoverTrigger} from "@components/ui/popover/index.js";
import {Button} from "@components/ui/button/index.js";
import type {ExtendedEvent} from "@type/event.ts";
import { eventRepo } from "@repo/event"; import { eventRepo } from "@repo/event";
import { players } from "@stores/stores" import { players } from "@stores/stores";
const { const { event }: { event: ExtendedEvent } = $props();
event
}: { event: ExtendedEvent } = $props();
let referees = $state(event.event.referees) let referees = $state(event.event.referees);
async function addReferee(value: string) { async function addReferee(value: string) {
referees = (await $eventRepo.updateEvent(event.event.id.toString(), { referees = (
addReferee: [value] await $eventRepo.updateEvent(event.event.id.toString(), {
})).referees; addReferee: [value],
})
).referees;
} }
async function removeReferee(value: string) { async function removeReferee(value: string) {
referees = (await $eventRepo.updateEvent(event.event.id.toString(), { referees = (
removeReferee: [value] await $eventRepo.updateEvent(event.event.id.toString(), {
})).referees; removeReferee: [value],
})
).referees;
} }
let playerSearch = $state("");
</script> </script>
<Table> <Table>
@@ -69,24 +66,27 @@
</TableRow> </TableRow>
{/each} {/each}
</TableBody> </TableBody>
<Popover>
<TableCaption>
<PopoverTrigger>
<Button>Add</Button>
</PopoverTrigger>
</TableCaption>
<PopoverContent class="p-0">
<Command shouldFilter={false}>
<CommandInput bind:value={playerSearch} placeholder="Search players..." />
<CommandList>
<CommandEmpty>No Players found :(</CommandEmpty>
<CommandGroup heading="Players">
{#each $players
.filter((v) => v.name.includes(playerSearch))
.filter((v, i) => i < 50)
.filter((v) => !referees.some((k) => k.uuid === v.uuid)) as player (player.uuid)}
<CommandItem value={player.name} onSelect={() => addReferee(player.uuid)} keywords={[player.uuid]}>{player.name}</CommandItem>
{/each}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</Table> </Table>
<Popover>
<PopoverTrigger>
<Button>
Add
</Button>
</PopoverTrigger>
<PopoverContent class="p-0">
<Command>
<CommandInput placeholder="Search players..." />
<CommandList>
<CommandEmpty>No Players found :(</CommandEmpty>
<CommandGroup heading="Players">
{#each $players.filter(v => v.perms.length > 0).filter(v => !referees.some(k => k.uuid === v.uuid)) as player (player.uuid)}
<CommandItem value={player.uuid} onSelect={() => addReferee(player.uuid)}>{player.name}</CommandItem>
{/each}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>

View File

@@ -0,0 +1,55 @@
<!--
- This file is a part of the SteamWar software.
-
- Copyright (C) 2025 SteamWar.de-Serverteam
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script lang="ts">
import { Button } from "@components/ui/button";
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell, TableCaption } from "@components/ui/table";
import type { ExtendedEvent } from "@type/event.ts";
const { event }: { event: ExtendedEvent } = $props();
</script>
<Table>
<TableCaption>
<Button disabled>Add Team</Button>
</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Team</TableHead>
<TableHead>Name</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#each event.teams as team (team.id)}
<TableRow>
<TableCell>{team.kuerzel}</TableCell>
<TableCell>{team.name}</TableCell>
<TableCell>
<Button disabled>Remove</Button>
</TableCell>
</TableRow>
{/each}
{#if event.teams.length === 0}
<TableRow>
<TableCell class="text-center col-span-3">No teams available</TableCell>
</TableRow>
{/if}
</TableBody>
</Table>

View File

@@ -17,16 +17,64 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type {ColumnDef} from "@tanstack/table-core"; import { Checkbox } from "@components/ui/checkbox";
import type {EventFight} from "@type/event.ts"; import { renderComponent } from "@components/ui/data-table";
import type { ColumnDef } from "@tanstack/table-core";
import type { EventFight } from "@type/event.ts";
export const columns: ColumnDef<EventFight> = [ export const columns: ColumnDef<EventFight> = [
{ {
accessorFn: (r) => r.blueTeam.name, id: "auswahl",
header: "Team Blue", header: ({ table }) => {
return renderComponent(Checkbox, {
checked: table.getIsAllRowsSelected(),
indeterminate: table.getIsSomeRowsSelected(),
onCheckedChange: () => {
if (!table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()) {
const now = new Date();
const rows = table.getRowModel().rows.filter((row) => new Date(row.original.date) > now);
if (rows.length > 0) {
rows.forEach((row) => {
row.toggleSelected();
});
} else {
table.toggleAllRowsSelected(true);
}
} else if (table.getIsSomeRowsSelected() && !table.getIsAllRowsSelected()) {
table.toggleAllRowsSelected(true);
} else {
table.toggleAllRowsSelected(false);
}
},
});
},
cell: ({ row }) => {
return renderComponent(Checkbox, {
checked: row.getIsSelected(),
onCheckedChange: row.getToggleSelectedHandler(),
});
},
}, },
{ {
accessorFn: (r) => r.redTeam.name, accessorFn: (r) => r.blueTeam.name + " vs " + r.redTeam.name,
header: "Team Red", id: "begegnung",
header: "Begegnung",
}, },
]; {
header: "Gruppe",
accessorKey: "group",
id: "group",
},
{
header: "Datum",
accessorKey: "start",
id: "start",
cell: ({ row }) => {
return new Date(row.getValue("start")).toLocaleString("de-DE", {
dateStyle: "short",
timeStyle: "medium",
});
},
},
];

View File

@@ -3,19 +3,11 @@
import { buttonVariants } from "$lib/components/ui/button/index.js"; import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = AlertDialogPrimitive.ActionProps; let {
type $$Events = AlertDialogPrimitive.ActionEvents; ref = $bindable(null),
class: className,
let className: $$Props["class"] = undefined; ...restProps
export { className as class }; }: AlertDialogPrimitive.ActionProps = $props();
</script> </script>
<AlertDialogPrimitive.Action <AlertDialogPrimitive.Action bind:ref class={cn(buttonVariants(), className)} {...restProps} />
class={cn(buttonVariants(), className)}
{...$$restProps}
on:click
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Action>

View File

@@ -3,19 +3,15 @@
import { buttonVariants } from "$lib/components/ui/button/index.js"; import { buttonVariants } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = AlertDialogPrimitive.CancelProps; let {
type $$Events = AlertDialogPrimitive.CancelEvents; ref = $bindable(null),
class: className,
let className: $$Props["class"] = undefined; ...restProps
export { className as class }; }: AlertDialogPrimitive.CancelProps = $props();
</script> </script>
<AlertDialogPrimitive.Cancel <AlertDialogPrimitive.Cancel
bind:ref
class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)} class={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
{...$$restProps} {...restProps}
on:click />
on:keydown
let:builder
>
<slot {builder} />
</AlertDialogPrimitive.Cancel>

View File

@@ -1,28 +1,26 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive, type WithoutChild } from "bits-ui";
import * as AlertDialog from "./index.js"; import AlertDialogOverlay from "./alert-dialog-overlay.svelte";
import { cn, flyAndScale } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = AlertDialogPrimitive.ContentProps; let {
ref = $bindable(null),
export let transition: $$Props["transition"] = flyAndScale; class: className,
export let transitionConfig: $$Props["transitionConfig"] = undefined; portalProps,
...restProps
let className: $$Props["class"] = undefined; }: WithoutChild<AlertDialogPrimitive.ContentProps> & {
export { className as class }; portalProps?: AlertDialogPrimitive.PortalProps;
} = $props();
</script> </script>
<AlertDialog.Portal> <AlertDialogPrimitive.Portal {...portalProps}>
<AlertDialog.Overlay /> <AlertDialogOverlay />
<AlertDialogPrimitive.Content <AlertDialogPrimitive.Content
{transition} bind:ref
{transitionConfig}
class={cn( class={cn(
"bg-background fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg sm:rounded-lg md:w-full", "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg",
className className
)} )}
{...$$restProps} {...restProps}
> />
<slot /> </AlertDialogPrimitive.Portal>
</AlertDialogPrimitive.Content>
</AlertDialog.Portal>

View File

@@ -2,15 +2,15 @@
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = AlertDialogPrimitive.DescriptionProps; let {
ref = $bindable(null),
let className: $$Props["class"] = undefined; class: className,
export { className as class }; ...restProps
}: AlertDialogPrimitive.DescriptionProps = $props();
</script> </script>
<AlertDialogPrimitive.Description <AlertDialogPrimitive.Description
bind:ref
class={cn("text-muted-foreground text-sm", className)} class={cn("text-muted-foreground text-sm", className)}
{...$$restProps} {...restProps}
> />
<slot />
</AlertDialogPrimitive.Description>

View File

@@ -1,16 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { WithElementRef } from "bits-ui";
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>; let {
ref = $bindable(null),
let className: $$Props["class"] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div <div
bind:this={ref}
class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)} class={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...$$restProps} {...restProps}
> >
<slot /> {@render children?.()}
</div> </div>

View File

@@ -1,13 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { WithElementRef } from "bits-ui";
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from "svelte/elements";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = HTMLAttributes<HTMLDivElement>; let {
ref = $bindable(null),
let className: $$Props["class"] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
</script> </script>
<div class={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...$$restProps}> <div
<slot /> bind:this={ref}
class={cn("flex flex-col space-y-2 text-center sm:text-left", className)}
{...restProps}
>
{@render children?.()}
</div> </div>

View File

@@ -1,21 +1,19 @@
<script lang="ts"> <script lang="ts">
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { fade } from "svelte/transition";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = AlertDialogPrimitive.OverlayProps; let {
ref = $bindable(null),
let className: $$Props["class"] = undefined; class: className,
export let transition: $$Props["transition"] = fade; ...restProps
export let transitionConfig: $$Props["transitionConfig"] = { }: AlertDialogPrimitive.OverlayProps = $props();
duration: 150,
};
export { className as class };
</script> </script>
<AlertDialogPrimitive.Overlay <AlertDialogPrimitive.Overlay
{transition} bind:ref
{transitionConfig} class={cn(
class={cn("bg-background/80 fixed inset-0 z-50 backdrop-blur-sm ", className)} "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
{...$$restProps} className
)}
{...restProps}
/> />

View File

@@ -2,13 +2,17 @@
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = AlertDialogPrimitive.TitleProps; let {
ref = $bindable(null),
let className: $$Props["class"] = undefined; class: className,
export let level: $$Props["level"] = "h3"; level = 3,
export { className as class }; ...restProps
}: AlertDialogPrimitive.TitleProps = $props();
</script> </script>
<AlertDialogPrimitive.Title class={cn("text-lg font-semibold", className)} {level} {...$$restProps}> <AlertDialogPrimitive.Title
<slot /> bind:ref
</AlertDialogPrimitive.Title> class={cn("text-lg font-semibold", className)}
{level}
{...restProps}
/>

View File

@@ -1,9 +1,7 @@
import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; import { AlertDialog as AlertDialogPrimitive } from "bits-ui";
import Title from "./alert-dialog-title.svelte"; import Title from "./alert-dialog-title.svelte";
import Action from "./alert-dialog-action.svelte"; import Action from "./alert-dialog-action.svelte";
import Cancel from "./alert-dialog-cancel.svelte"; import Cancel from "./alert-dialog-cancel.svelte";
import Portal from "./alert-dialog-portal.svelte";
import Footer from "./alert-dialog-footer.svelte"; import Footer from "./alert-dialog-footer.svelte";
import Header from "./alert-dialog-header.svelte"; import Header from "./alert-dialog-header.svelte";
import Overlay from "./alert-dialog-overlay.svelte"; import Overlay from "./alert-dialog-overlay.svelte";
@@ -12,6 +10,7 @@ import Description from "./alert-dialog-description.svelte";
const Root = AlertDialogPrimitive.Root; const Root = AlertDialogPrimitive.Root;
const Trigger = AlertDialogPrimitive.Trigger; const Trigger = AlertDialogPrimitive.Trigger;
const Portal = AlertDialogPrimitive.Portal;
export { export {
Root, Root,

View File

@@ -1,35 +1,35 @@
<script lang="ts"> <script lang="ts">
import { Checkbox as CheckboxPrimitive } from "bits-ui"; import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from "bits-ui";
import Check from "lucide-svelte/icons/check"; import Check from "@lucide/svelte/icons/check";
import Minus from "lucide-svelte/icons/minus"; import Minus from "@lucide/svelte/icons/minus";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = CheckboxPrimitive.Props; let {
type $$Events = CheckboxPrimitive.Events; ref = $bindable(null),
checked = $bindable(false),
let className: $$Props["class"] = undefined; indeterminate = $bindable(false),
export let checked: $$Props["checked"] = false; class: className,
export { className as class }; ...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script> </script>
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
bind:ref
class={cn( class={cn(
"border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content h-4 w-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50", "border-primary ring-offset-background focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer box-content size-4 shrink-0 rounded-sm border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50",
className className
)} )}
bind:checked bind:checked
{...$$restProps} bind:indeterminate
on:click {...restProps}
> >
<CheckboxPrimitive.Indicator {#snippet children({ checked, indeterminate })}
class={cn("flex h-4 w-4 items-center justify-center text-current")} <div class="flex size-4 items-center justify-center text-current">
let:isChecked {#if indeterminate}
let:isIndeterminate <Minus class="size-3.5" />
> {:else}
{#if isChecked} <Check class={cn("size-3.5", !checked && "text-transparent")} />
<Check class="h-3.5 w-3.5" /> {/if}
{:else if isIndeterminate} </div>
<Minus class="h-3.5 w-3.5" /> {/snippet}
{/if}
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root> </CheckboxPrimitive.Root>

View File

@@ -1,10 +1,9 @@
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive } from "bits-ui";
import Root from "./menubar.svelte"; import Root from "./menubar.svelte";
import CheckboxItem from "./menubar-checkbox-item.svelte"; import CheckboxItem from "./menubar-checkbox-item.svelte";
import Content from "./menubar-content.svelte"; import Content from "./menubar-content.svelte";
import Item from "./menubar-item.svelte"; import Item from "./menubar-item.svelte";
import Label from "./menubar-label.svelte"; import GroupHeading from "./menubar-group-heading.svelte";
import RadioItem from "./menubar-radio-item.svelte"; import RadioItem from "./menubar-radio-item.svelte";
import Separator from "./menubar-separator.svelte"; import Separator from "./menubar-separator.svelte";
import Shortcut from "./menubar-shortcut.svelte"; import Shortcut from "./menubar-shortcut.svelte";
@@ -22,7 +21,7 @@ export {
CheckboxItem, CheckboxItem,
Content, Content,
Item, Item,
Label, GroupHeading,
RadioItem, RadioItem,
Separator, Separator,
Shortcut, Shortcut,
@@ -38,7 +37,7 @@ export {
CheckboxItem as MenubarCheckboxItem, CheckboxItem as MenubarCheckboxItem,
Content as MenubarContent, Content as MenubarContent,
Item as MenubarItem, Item as MenubarItem,
Label as MenubarLabel, GroupHeading as MenubarGroupHeading,
RadioItem as MenubarRadioItem, RadioItem as MenubarRadioItem,
Separator as MenubarSeparator, Separator as MenubarSeparator,
Shortcut as MenubarShortcut, Shortcut as MenubarShortcut,

View File

@@ -1,35 +1,40 @@
<script lang="ts"> <script lang="ts">
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive, type WithoutChildrenOrChild } from "bits-ui";
import Check from "lucide-svelte/icons/check"; import Check from "@lucide/svelte/icons/check";
import Minus from "@lucide/svelte/icons/minus";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
import type { Snippet } from "svelte";
type $$Props = MenubarPrimitive.CheckboxItemProps; let {
type $$Events = MenubarPrimitive.CheckboxItemEvents; ref = $bindable(null),
class: className,
let className: $$Props["class"] = undefined; checked = $bindable(false),
export let checked: $$Props["checked"] = false; indeterminate = $bindable(false),
export { className as class }; children: childrenProp,
...restProps
}: WithoutChildrenOrChild<MenubarPrimitive.CheckboxItemProps> & {
children?: Snippet;
} = $props();
</script> </script>
<MenubarPrimitive.CheckboxItem <MenubarPrimitive.CheckboxItem
bind:ref
bind:checked bind:checked
bind:indeterminate
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
on:click {...restProps}
on:keydown
on:focusin
on:focusout
on:pointerleave
on:pointermove
on:pointerdown
{...$$restProps}
> >
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> {#snippet children({ checked, indeterminate })}
<MenubarPrimitive.CheckboxIndicator> <span class="absolute left-2 flex size-3.5 items-center justify-center">
<Check class="h-4 w-4" /> {#if indeterminate}
</MenubarPrimitive.CheckboxIndicator> <Minus class="size-4" />
</span> {:else}
<slot /> <Check class={cn("size-4", !checked && "text-transparent")} />
{/if}
</span>
{@render childrenProp?.()}
{/snippet}
</MenubarPrimitive.CheckboxItem> </MenubarPrimitive.CheckboxItem>

View File

@@ -1,33 +1,32 @@
<script lang="ts"> <script lang="ts">
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = MenubarPrimitive.ContentProps; let {
type $$Events = MenubarPrimitive.ContentEvents; ref = $bindable(null),
class: className,
let className: $$Props["class"] = undefined; sideOffset = 8,
export let sideOffset: $$Props["sideOffset"] = 8; alignOffset = -4,
export let alignOffset: $$Props["alignOffset"] = -4; align = "start",
export let align: $$Props["align"] = "start"; side = "bottom",
export let side: $$Props["side"] = "bottom"; portalProps,
export let transition: $$Props["transition"] = flyAndScale; ...restProps
export let transitionConfig: $$Props["transitionConfig"] = undefined; }: MenubarPrimitive.ContentProps & {
export { className as class }; portalProps?: MenubarPrimitive.PortalProps;
} = $props();
</script> </script>
<MenubarPrimitive.Content <MenubarPrimitive.Portal {...portalProps}>
{transition} <MenubarPrimitive.Content
{transitionConfig} bind:ref
{sideOffset} {sideOffset}
{align} {align}
{alignOffset} {alignOffset}
{side} {side}
class={cn( class={cn(
"bg-popover text-popover-foreground z-50 min-w-[12rem] rounded-md border p-1 shadow-md focus:outline-none", "bg-popover text-popover-foreground z-50 min-w-[12rem] rounded-md border p-1 shadow-md focus:outline-none",
className className
)} )}
{...$$restProps} {...restProps}
on:keydown />
> </MenubarPrimitive.Portal>
<slot />
</MenubarPrimitive.Content>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { Menubar as MenubarPrimitive } from "bits-ui";
import { cn } from "$lib/components/utils.js";
let {
ref = $bindable(null),
class: className,
inset = undefined,
...restProps
}: MenubarPrimitive.GroupHeadingProps & {
inset?: boolean;
} = $props();
</script>
<MenubarPrimitive.GroupHeading
bind:ref
class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...restProps}
/>

View File

@@ -2,30 +2,22 @@
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive } from "bits-ui";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = MenubarPrimitive.ItemProps & { let {
ref = $bindable(null),
class: className,
inset = undefined,
...restProps
}: MenubarPrimitive.ItemProps & {
inset?: boolean; inset?: boolean;
}; } = $props();
type $$Events = MenubarPrimitive.ItemEvents;
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script> </script>
<MenubarPrimitive.Item <MenubarPrimitive.Item
bind:ref
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className
)} )}
{...$$restProps} {...restProps}
on:click />
on:keydown
on:focusin
on:focusout
on:pointerleave
on:pointermove
on:pointerdown
>
<slot />
</MenubarPrimitive.Item>

View File

@@ -1,35 +1,30 @@
<script lang="ts"> <script lang="ts">
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive, type WithoutChild } from "bits-ui";
import Circle from "lucide-svelte/icons/circle"; import Circle from "@lucide/svelte/icons/circle";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = MenubarPrimitive.RadioItemProps; let {
type $$Events = MenubarPrimitive.RadioItemEvents; ref = $bindable(null),
class: className,
let className: $$Props["class"] = undefined; children: childrenProp,
export let value: $$Props["value"]; ...restProps
export { className as class }; }: WithoutChild<MenubarPrimitive.RadioItemProps> = $props();
</script> </script>
<MenubarPrimitive.RadioItem <MenubarPrimitive.RadioItem
{value} bind:ref
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className className
)} )}
{...$$restProps} {...restProps}
on:click
on:keydown
on:focusin
on:focusout
on:pointerleave
on:pointermove
on:pointerdown
> >
<span class="absolute left-2 flex h-3.5 w-3.5 items-center justify-center"> {#snippet children({ checked })}
<MenubarPrimitive.RadioIndicator> <span class="absolute left-2 flex size-3.5 items-center justify-center">
<Circle class="h-2 w-2 fill-current" /> {#if checked}
</MenubarPrimitive.RadioIndicator> <Circle class="size-2 fill-current" />
</span> {/if}
<slot /> </span>
{@render childrenProp?.({ checked })}
{/snippet}
</MenubarPrimitive.RadioItem> </MenubarPrimitive.RadioItem>

View File

@@ -2,10 +2,15 @@
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive } from "bits-ui";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = MenubarPrimitive.SeparatorProps; let {
ref = $bindable(null),
let className: $$Props["class"] = undefined; class: className,
export { className as class }; ...restProps
}: MenubarPrimitive.SeparatorProps = $props();
</script> </script>
<MenubarPrimitive.Separator class={cn("bg-muted -mx-1 my-1 h-px", className)} {...$$restProps} /> <MenubarPrimitive.Separator
bind:ref
class={cn("bg-muted -mx-1 my-1 h-px", className)}
{...restProps}
/>

View File

@@ -1,16 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { HTMLAttributes } from "svelte/elements"; import type { HTMLAttributes } from "svelte/elements";
import type { WithElementRef } from "bits-ui";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = HTMLAttributes<HTMLSpanElement>; let {
ref = $bindable(null),
let className: $$Props["class"] = undefined; class: className,
export { className as class }; children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script> </script>
<span <span
bind:this={ref}
class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)} class={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
{...$$restProps} {...restProps}
> >
<slot /> {@render children?.()}
</span> </span>

View File

@@ -1,27 +1,19 @@
<script lang="ts"> <script lang="ts">
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive } from "bits-ui";
import { cn, flyAndScale } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = MenubarPrimitive.SubContentProps; let {
type $$Events = MenubarPrimitive.SubContentEvents; ref = $bindable(null),
class: className,
let className: $$Props["class"] = undefined; ...restProps
export let transition: $$Props["transition"] = flyAndScale; }: MenubarPrimitive.SubContentProps = $props();
export let transitionConfig: $$Props["transitionConfig"] = { x: -10, y: 0 };
export { className as class };
</script> </script>
<MenubarPrimitive.SubContent <MenubarPrimitive.SubContent
{transition} bind:ref
{transitionConfig}
class={cn( class={cn(
"bg-popover text-popover-foreground z-50 min-w-max rounded-md border p-1 focus:outline-none", "bg-popover text-popover-foreground z-50 min-w-max rounded-md border p-1 focus:outline-none",
className className
)} )}
{...$$restProps} {...restProps}
on:focusout />
on:pointermove
on:keydown
>
<slot />
</MenubarPrimitive.SubContent>

View File

@@ -1,32 +1,28 @@
<script lang="ts"> <script lang="ts">
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive, type WithoutChild } from "bits-ui";
import ChevronRight from "lucide-svelte/icons/chevron-right"; import ChevronRight from "@lucide/svelte/icons/chevron-right";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = MenubarPrimitive.SubTriggerProps & { let {
ref = $bindable(null),
class: className,
inset = undefined,
children,
...restProps
}: WithoutChild<MenubarPrimitive.SubTriggerProps> & {
inset?: boolean; inset?: boolean;
}; } = $props();
type $$Events = MenubarPrimitive.SubTriggerEvents;
let className: $$Props["class"] = undefined;
export let inset: $$Props["inset"] = undefined;
export { className as class };
</script> </script>
<MenubarPrimitive.SubTrigger <MenubarPrimitive.SubTrigger
bind:ref
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8", inset && "pl-8",
className className
)} )}
on:click {...restProps}
{...$$restProps}
on:keydown
on:focusin
on:focusout
on:pointerleave
on:pointermove
> >
<slot /> {@render children?.()}
<ChevronRight class="ml-auto h-4 w-4" /> <ChevronRight class="ml-auto size-4" />
</MenubarPrimitive.SubTrigger> </MenubarPrimitive.SubTrigger>

View File

@@ -2,22 +2,18 @@
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive } from "bits-ui";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = MenubarPrimitive.TriggerProps; let {
type $$Events = MenubarPrimitive.TriggerEvents; ref = $bindable(null),
class: className,
let className: $$Props["class"] = undefined; ...restProps
export { className as class }; }: MenubarPrimitive.TriggerProps = $props();
</script> </script>
<MenubarPrimitive.Trigger <MenubarPrimitive.Trigger
bind:ref
class={cn( class={cn(
"data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none", "data-[highlighted]:bg-accent data-[state=open]:bg-accent data-[highlighted]:text-accent-foreground data-[state=open]:text-accent-foreground flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none",
className className
)} )}
on:click {...restProps}
on:keydown />
on:pointerenter
{...$$restProps}
>
<slot />
</MenubarPrimitive.Trigger>

View File

@@ -2,15 +2,15 @@
import { Menubar as MenubarPrimitive } from "bits-ui"; import { Menubar as MenubarPrimitive } from "bits-ui";
import { cn } from "$lib/components/utils.js"; import { cn } from "$lib/components/utils.js";
type $$Props = MenubarPrimitive.Props; let {
ref = $bindable(null),
let className: $$Props["class"] = undefined; class: className,
export { className as class }; ...restProps
}: MenubarPrimitive.RootProps = $props();
</script> </script>
<MenubarPrimitive.Root <MenubarPrimitive.Root
bind:ref
class={cn("bg-background flex h-10 items-center space-x-1 rounded-md border p-1", className)} class={cn("bg-background flex h-10 items-center space-x-1 rounded-md border p-1", className)}
{...$$restProps} {...restProps}
> />
<slot />
</MenubarPrimitive.Root>

View File

@@ -0,0 +1,111 @@
---
title: Spaceship Event Ankündigung und Regelwerk
description: Alle Infos, Regeln und Termine zum ersten Spaceship-Event am 19.06.2025 jetzt mit neuer TNT-Physik und ohne Gravitation!
tags:
- spaceship
- event
created: 2025-05-25
key: spaceship-event-ankuendigung-und-regelwerk
---
**Ahoi Community**
Am 29.06. findet des erste Spaceship Event statt. Dieser Modus soll Airship ersetzen und hat die Besonderheit, dass TNT (und Sand) keine Gravitation hat. Gezündetes, ungebremstes TNT fliegt durch den typischen Sprung bei der Zündung zum Beispiel mit gleichbleibender Geschwindigkeit schräg nach oben.
Die neue Physik nutzt ihr mit ***/nogravity*** auf dem Bau
### Alle Infos zum Event:
- Die Technik und das Design der Publics werden nochmal seperat Public gestellt. So könnt ihr, wenn ihr keine oder weniger eigene Technik bauen wollt, einfacher die Public technik verwenden.
- Das Event findet am 29.06.2025 um 16 Uhr statt
- Es gibt kein Entern
- Kits können wie bei WG und MWG selbst erstellt werden
- es wird Redstone TKO verwendet
- Das Event findet in der 1.20 statt
- **Einsendeschluss** am 24.06.2025 23:59 Uhr
- Bis zu diesem Zeitpunkt muss eine **dem Modus entsprechende Kampfmaschine** vorhanden sein
- Bis zum 27.06.2025 23:59 Uhr **Hotfix-Phase**
- In dieser Phase dürfen lediglich Hotfixes eingesendet werden. Das bedeutet: Die Kampfmaschine darf **keine signifikanten Änderungen** erfahren. Es dürfen z. B. keine ganzen Kanonen ausgetauscht oder die Aufstellung verändert werden.
- Die Teams ernennen jeweils einen Repräsentanten der das jeweilige Team vertritt und **gut erreichbar** ist
- Es dürfen Maximal 2 Spieler pro Team an einem fight teilnehmen
### RW Änderungen:
- Es dürfen keine Blöcke über die Grenze des Designbereiches bewegt werden
- Das hat zur Folge: Kein slime und honey Limit
- Wasser darf in Kanonen verwendet werden
- Dadurch könnt ihr TNT einfacher komprimieren. Wasser muss nicht mehr den Zweck erfüllen, die Kanone vor eigenem Schaden zu bewahren
- 32 Projektile pro Seite
- Unterpunkt: Crossbows
- Es gibt keinen Techhider
# Regelwerk für das Event:
## Definitionen
Ein AirShip ist eine beidseitig bewaffnete Struktur in Minecraft 1.20 und sieht flugfähig aus.
## Maße
**Technikbereich**:
- 70 lang
- 35 breit
- 24 hoch
**Designbereich**:
- 130 lang
- 51 breit
- 32 hoch
Technik, welche für die Funktion der Kanonen nicht relevant ist, darf sich in maßen auch im Design-Bereich befinden.
Entfernung von Mitte zu Mitte des Gegners: 100 Block
Es dürfen keine Blöcke über die Grenze des Designbereiches bewegt werden
Panzerung darf nur im Technikbereich verbaut werden.
## Projektile
Ein Projektil ist ein gezündetes TNT, welches in die gegnerische Hälfte wechselt. Eine Kanone ist eine Vorrichtung zum Beschleunigen von Projektilen. Es ist verboten, mehrere Kanonen als eine Einzige auszugeben.
Jeder Seite stehen bis zu 32 Projektile zur Verfügung. Sie müssen auf der dem Gegner zugewandten Seite oder, wenn beidseitig, in der Mitte gezündet werden
## Crossbows
Kanonen, welche, ohne nachgeladen werden zu müssen, mehrere Schüsse abgeben können, Müssen:
- Jeden Schuss das TNT an den selben Punkten zünden.
- Für jedes Projektil das die Kanone für einen Schuss maximal zünden könnte, mindestens 1,5 Redstone Tick zwischen den Schüssen warten.
##### Beispiele
| Projektile | Ticks |
|-------|------------|
| 1 | 1,5 |
| 2 | 3 |
| 3 | 4,5 |
| 4 | 6 |
| ... | ... |
| 8 | 12 |
| 12 | 18 |
| 16 | 24 |
| ... | ... |
| 32 | 48 |
## Siegesbedingung
- Ein AirShip wird zu 60% (nach prozentualer Redstonekomponenten) zerstört.
- Alle Kämpfer eines AirShips sind tot.
- Nach 10 Minuten wird ein Unentschieden eingeleitet.
- Der Kampfleiter entscheidet.
## Blöcke
Es dürfen maximal je 32 TNT und 32 Werfer verbaut werden. Verbaute Blöcke dürfen einen TNT-Widerstand von maximal 6 haben. Ausgenommen davon ist Wasser, welches ausschließlich in Kanonen verbaut werden darf.
Inventar-Blöcke dürfen nur mit Blumen, Honigflaschen und Pferderüstungen gefüllt sein. Zusätzlich dürfen sich in Kisten und Fässern TNT sowie in Werfern 2·64 Feuerbälle, 2·64 reguläre Pfeile oder 1 Eimer zum Aufheben von powdered Snow befinden.
Zusätzlich verboten: Monster Spawner, Eis, Nether Portal, Silberfischsteine, Leuchtfeuer, unsichtbare Blöcke (Ausnahme: structure_void) Das Duplizieren von Blöcken und Entities ist verboten.
Blöcke aus dem eigenen AS dürfen nicht in großer Menge entfernt werden.
Bug-Using ist nicht erwünscht.

View File

@@ -0,0 +1,35 @@
---
title: SteamWar Arcade Eventplan
key: sw-arcade-eventplan
description: Der Eventplan für SteamWar Arcade
created: 2025-05-14
tags:
- event
- towerrun
- misslewars
- tntleague
- arcade
image: ../../../images/sw-arcade-image.png
---
### Infos:
Eventleitung: Chaoscaot
Fights werden nach Möglichkeit mit einer Pause von 10 Minuten vorverschoben.
# Kämpfe
## Punkte aus der Gruppenphase
<group-table data-event="70"> </group-table>
### Fights
<fight-table data-event="70" data-group="Gruppe 1"> </fight-table>
## Auswahl des Spielmodis
Die Teams können Ingame über den Befehl `/event vote` für einen Spielmodus abstimmen. Diese Abstimmung wird für die nächsten Fights gespeichert.
Wenn die Teams sich einig sind im Spielmodus, wird dieser gespielt. Wenn nicht, dann wird der von keinem Team gewählte Modus gespielt.
Sollte ein Team nicht abstimmen, wird zufällig eine Stimme abgegeben.

View File

@@ -3,6 +3,7 @@
"description": "Ein Fabric-Mod, der für den BauServer von SteamWar Hotkeys für das ScriptSystem hinzufügt. Hierzu werden die einzelnen Zeichen an den Server gesendet und vom Server verarbeitet.", "description": "Ein Fabric-Mod, der für den BauServer von SteamWar Hotkeys für das ScriptSystem hinzufügt. Hierzu werden die einzelnen Zeichen an den Server gesendet und vom Server verarbeitet.",
"url": { "url": {
"1.21.4": "https://git.steamwar.de/SteamWar/AdvancedScripts/releases/download/2.2.0/AdvancedScripts-2.2.0.jar", "1.21.4": "https://git.steamwar.de/SteamWar/AdvancedScripts/releases/download/2.2.0/AdvancedScripts-2.2.0.jar",
"1.21.3": "https://git.steamwar.de/SteamWar/AdvancedScripts/releases/download/2.2.1/AdvancedScripts-2.2.1.jar",
"1.20.6": "https://git.steamwar.de/SteamWar/AdvancedScripts/releases/download/2.1.0/AdvancedScripts-2.1.0.jar", "1.20.6": "https://git.steamwar.de/SteamWar/AdvancedScripts/releases/download/2.1.0/AdvancedScripts-2.1.0.jar",
"1.19.3": "https://git.steamwar.de/SteamWar/AdvancedScripts/releases/download/2.0.0/AdvancedScripts-2.0.0.jar", "1.19.3": "https://git.steamwar.de/SteamWar/AdvancedScripts/releases/download/2.0.0/AdvancedScripts-2.0.0.jar",
"Hotkey script": "https://git.steamwar.de/SteamWar/SteamWar/src/branch/main/BauSystem/hotkeys.lua" "Hotkey script": "https://git.steamwar.de/SteamWar/SteamWar/src/branch/main/BauSystem/hotkeys.lua"

View File

@@ -77,13 +77,19 @@ Folgende Richtlinien befassen sich mit dem Verhalten um jegliche Regelbrüche
# Handel # Handel
1. Jeglicher Handel, welcher mit einer reellen Vermögensveränderung verbunden ist, wird im Zusammenhang mit SteamWar nicht gestattet. 1. Jeglicher Handel, welcher mit einer reellen Vermögensveränderung verbunden ist, wird im Zusammenhang mit SteamWar nicht gestattet.
1. Bauten, die auf oder mithilfe von SteamWar.de entstanden sind, dürfen nicht verkauft werden. Das bezieht sich auch auf Handel außerhalb von SteamWar. 2. Bauten, die auf oder mithilfe von SteamWar.de entstanden sind, dürfen nicht verkauft werden. Das bezieht sich auch auf Handel außerhalb von SteamWar.
# Teamserver Integration # Teamserver Integration
1. Die Verhaltensrichtlinien gelten auch für Team-Server, die über die Team-Server-Integration mit dem SteamWar-Netzwerk verbunden sind, sofern mehr Spieler als die des verbundenen Teams und wenige weitere ausgewählte Personen auf dem Team-Server zugelassen sind. Ausgenommen hiervon sind Paragraph 4.3.1, da Vorfälle dieser Art dem Serverbetreiber gemeldet werden müssen. Es sei denn es geht um SteamWar, sowie Paragraph 3.1. 1. Die Verhaltensrichtlinien gelten auch für Team-Server, die über die Team-Server-Integration mit dem SteamWar-Netzwerk verbunden sind, sofern mehr Spieler als die des verbundenen Teams und wenige weitere ausgewählte Personen auf dem Team-Server zugelassen sind. Ausgenommen hiervon sind:
- Paragraf 4.3, da Vorfälle dieser Art dem Serverbetreiber gemeldet werden müssen. Es sei denn es geht um SteamWar
- Paragraf 3.1.
2. Das Hinzufügen von Regeln, welche unsere Verhaltensrichtlinien erweitern ist erlaubt, solange diese nicht unsere Verhaltensrichtlinien verletzen. 2. Das Hinzufügen von Regeln, welche unsere Verhaltensrichtlinien erweitern ist erlaubt, solange diese nicht unsere Verhaltensrichtlinien verletzen.
3. Diese Regel greift nur, solange entsprechende Spieler auch über SteamWar.de auf den Team-Server gelangt sind 3. Diese Regel greift nur, solange entsprechende Spieler auch über SteamWar.de auf den Team-Server gelangt sind
4. SteamWar ist für den Inhalt auf einem Team-Server nicht verantwortlich.
5. Bei jeglichem rechtlichen Verstoß gegen die Eula von Mojang oder Gesetzen auf dem Team-Server steht SteamWar nicht in der Verantwortung. Diese Verantwortung trägt einzig der/die Verantwortliche(n) des Team-Servers.
6. Wir behalten uns vor jeden integrierten Team-Server von SteamWar aus nicht mehr erreichbar zu machen und die Leader aus dem Team zu bestrafen.
# Events # Events
1. Kampfmaschinen, die von SteamWar in limitierter Form verteilt wurden, dürfen nicht weiterverbreitet werden. 1. Kampfmaschinen, die von SteamWar in limitierter Form verteilt wurden, dürfen nicht weiterverbreitet werden.
@@ -96,7 +102,7 @@ Mögliche strafen sind:
* No Schem Receiving: Es ist anderen unmöglich, dich auf ihre Schematics hinzuzufügen oder diese auf deinem Bau zu verwenden. Außerdem kannst du keine Schematics von anderen Spielern kopieren oder Schematics hochladen. * No Schem Receiving: Es ist anderen unmöglich, dich auf ihre Schematics hinzuzufügen oder diese auf deinem Bau zu verwenden. Außerdem kannst du keine Schematics von anderen Spielern kopieren oder Schematics hochladen.
* No Schem Sharing: Du kannst niemandem mehr erlauben, deine Schematics zu verwenden. Zudem kann niemand auf deinem Bau eine Schematic erstellen. Du kannst auch keine Schematics herunterladen. * No Schem Sharing: Du kannst niemandem mehr erlauben, deine Schematics zu verwenden. Zudem kann niemand auf deinem Bau eine Schematic erstellen. Du kannst auch keine Schematics herunterladen.
* No Schem Submitting: Du kannst keine Schematics mehr einreichen. * No Schem Submitting: Du kannst keine Schematics mehr einreichen.
* No Teamserver: Du kannst deinen Teamserver nicht mehr mit SteamWar verknüpfen. * No Teamserver: Du kannst deinen Team-Server nicht mehr mit SteamWar verknüpfen.
* No Event: Dein Team kann nicht mehr an Events teilnehmen. * No Event: Dein Team kann nicht mehr an Events teilnehmen.
* Note: Es wird eine Notiz zu deinem Account hinzugefügt. * Note: Es wird eine Notiz zu deinem Account hinzugefügt.
* Ban: Du kannst nicht mehr auf dem Server spielen. * Ban: Du kannst nicht mehr auf dem Server spielen.
@@ -107,4 +113,4 @@ Alle genannten Strafen können entweder temporär oder permanent sein.
Das Serverteam behält sich das Recht vor, diese Richtlinien jederzeit zu ändern oder zu ergänzen Das Serverteam behält sich das Recht vor, diese Richtlinien jederzeit zu ändern oder zu ergänzen
Unwissenheit schützt nicht vor Strafe! Unwissenheit schützt nicht vor Strafe!
Letztes update: `04.05.2024` Letztes update: `31.05.2025`

View File

@@ -39,7 +39,7 @@ Die Brücke muss folgende Voraussetzungen erfüllen:
- Min. 25 m² (1 Block = 1 Meter) - Min. 25 m² (1 Block = 1 Meter)
- Ein Sichtfenster auf das gegnerische MicroWarGear (Ausnahme bei Freiluftbrücken mit direkter Sicht auf den Gegner, diese benötigen kein extra Sichtfenster) - Ein Sichtfenster auf das gegnerische MicroWarGear (Ausnahme bei Freiluftbrücken mit direkter Sicht auf den Gegner, diese benötigen kein extra Sichtfenster)
- Die Ansteuerung für min. 4 vom Gegner aus sichtbare Frontscheinwerfer. - Die Ansteuerung für min. 2 vom Gegner aus sichtbare Frontscheinwerfer.
- Diese müssen ihren Zustand beibehalten, bis der/ die Mechanismen erneut manuell betätigt wird/ werden - Diese müssen ihren Zustand beibehalten, bis der/ die Mechanismen erneut manuell betätigt wird/ werden
- Die einzige Ansteuerung für ggf. vorhandene Werfer, die mit Pfeilen oder Feuerbällen den Gegner beschießen - Die einzige Ansteuerung für ggf. vorhandene Werfer, die mit Pfeilen oder Feuerbällen den Gegner beschießen

View File

@@ -21,10 +21,10 @@ Eine Kanone ist eine Vorrichtung zum Beschleunigen von Projektilen. Eine Kanone
## Maße ## Maße
- Länge: 230 Block - Länge: 230 Block
- Breite: 35 Block (+ 4 Block Design pro Seite) - Breite: 35 Block (+ 4 Block Design pro Seite)
- Höhe: 30 Block + 20 Block Design - Höhe: 30 Block + 20 Block Design
- Tiefe: Bis zu 8 Block unter dem Meeresspiegel - Tiefe: Bis zu 8 Block unter dem Meeresspiegel
Bei jedem WarShip müssen sich mindestens 10% der absoluten Blöcke (45.000 Blöcke) über der Wasserlinie befinden. Bei jedem WarShip müssen sich mindestens 10% der absoluten Blöcke (45.000 Blöcke) über der Wasserlinie befinden.
@@ -45,20 +45,22 @@ Wasser darf nicht zum Schutz des eigenen WarShips missbraucht werden.
Größere Hohlräume im Rumpf zum Ausweichen feindlicher Schüsse sind nicht gestattet, auch nicht während des Kampfes. Jedes WarShip braucht eine Flagge. Größere Hohlräume im Rumpf zum Ausweichen feindlicher Schüsse sind nicht gestattet, auch nicht während des Kampfes. Jedes WarShip braucht eine Flagge.
Ein WarShip benötigt einen fortbewegungsfähigen Rumpf mit entsprechendem Antrieb (z.B. Segel, Schiffsschrauben). Ein WarShip benötigt einen fortbewegungsfähigen Rumpf mit entsprechendem Antrieb (z.B. Segel, Schiffsschrauben).
Der Rumpf muss mindestens einen Block tief unter Wasser sowie mindestens 5 Block über dem Meeresspiegel sein (dies gilt auch während des Kampfes relativ zur Wasseroberfläche). Der Rumpf darf max. 16 Block hoch sein. Der Rumpf muss mindestens einen Block tief unter Wasser sowie mindestens 5 Block über dem Meeresspiegel sein (dies gilt auch während des Kampfes relativ zur Wasseroberfläche). Der Rumpf wird ab Wasserienie gemessen und darf max. 16 Blöcke hoch sein.
Jedes WarShip benötigt eine Brücke, welche die folgenden Kriterien erfüllt: Jedes WarShip benötigt eine Brücke, welche die folgenden Kriterien erfüllt:
- Min. 50 begehbare Blöcke - Min. 50 begehbare Blöcke
- Min. 2 Block hoch im gesamten Brückenraum - Min. 2 Block hoch im gesamten Brückenraum
- Ansteuerung von min. zwei zum Gegner gewandten Scheinwerfer - Ansteuerung von min. zwei zum Gegner gewandten Scheinwerfer
- Optische Brückeneinrichtung - Optische Brückeneinrichtung
## Anti-Lag-Regeln ## Anti-Lag-Regeln
Clocks müssen sich mit Ende ihres Einsatzzweckes selbst abschalten. Clocks müssen sich mit Ende ihres Einsatzzweckes selbst abschalten.
Sämtliche Redstonetechnik zum Schutz des eigenen WarShips muss ihre Aktivität vor dem Verteilen der Kits eingestellt haben. Sämtliche Redstonetechnik zum Schutz des eigenen WarShips muss ihre Aktivität vor dem Verteilen der Kits eingestellt haben.
Raketen und Flugmaschinen
## Raketen und Flugmaschinen
Ein WarShip darf sich maximal 12 Block vom Technikbereich an weit ausfahren, davon ausgenommen sind Raketen und Flugmaschinen. Raketen und Flugmaschinen dürfen sich im Flug nicht in mehrere Schleim/Honigfahrzeuge aufteilen. Ein WarShip darf sich maximal 12 Block vom Technikbereich an weit ausfahren, davon ausgenommen sind Raketen und Flugmaschinen. Raketen und Flugmaschinen dürfen sich im Flug nicht in mehrere Schleim/Honigfahrzeuge aufteilen.
@@ -74,6 +76,6 @@ Es dürften maximal 1000 der vorverbauten Slime- + Honig- + TNT-Blöcke das WarS
Der Kampf endet, wenn: Der Kampf endet, wenn:
- Der Kampf länger als 20 Minuten dauert - Der Kampf länger als 20 Minuten dauert
- Der Anführer eines Teams tot ist - Der Anführer eines Teams tot ist
- Ein WarShip zu 7% beschädigt wurde - Ein WarShip zu 7% beschädigt wurde

View File

@@ -209,6 +209,7 @@
} }
}, },
"login": { "login": {
"page": "SteamWar - Login",
"title": "Login", "title": "Login",
"placeholder": { "placeholder": {
"username": "Nutzername...", "username": "Nutzername...",
@@ -219,7 +220,7 @@
"password": "Passwort", "password": "Passwort",
"repeat": "Passwort Wiederholen" "repeat": "Passwort Wiederholen"
}, },
"setPassword": "Wie setzte ich mein Passwort?", "setPassword": "Wie setze ich mein Passwort?",
"submit": "Login", "submit": "Login",
"error": "Falscher Nutzername oder falsches Passwort" "error": "Falscher Nutzername oder falsches Passwort"
}, },

View File

@@ -3,7 +3,7 @@ import "$lib/styles/app.css";
import { astroI18n } from "astro-i18n"; import { astroI18n } from "astro-i18n";
import { SEO } from "astro-seo"; import { SEO } from "astro-seo";
import { ClientRouter } from "astro:transitions"; import { ClientRouter } from "astro:transitions";
const { title, description, clientSideRouter = 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";
--- ---
@@ -32,11 +32,13 @@ import "../../public/fonts/roboto/roboto.css";
}))} }))}
/> />
<script is:inline data-astro-rerun> {autoDarkMode && (
if (localStorage["theme-mode"] === "light" || (!("theme-mode" in localStorage) && window.matchMedia("(prefers-color-scheme: light)").matches)) { <script is:inline data-astro-rerun>
document.documentElement.classList.remove("dark"); if (localStorage["theme-mode"] === "light" || (!("theme-mode" in localStorage) && window.matchMedia("(prefers-color-scheme: light)").matches)) {
} document.documentElement.classList.remove("dark");
</script> }
</script>
)}
<slot name="head" /> <slot name="head" />

View File

@@ -1,31 +1,33 @@
--- ---
import {Image} from "astro:assets"; import { Image } from "astro:assets";
import Basic from "./Basic.astro"; import Basic from "./Basic.astro";
import "../styles/button.css"; import "../styles/button.css";
import localLogo from "../images/logo.png"; import localLogo from "../images/logo.png";
import {YoutubeSolid, DiscordSolid, FileCodeSolid} from "flowbite-svelte-icons"; import { YoutubeSolid, DiscordSolid, FileCodeSolid } from "flowbite-svelte-icons";
import {t} from "astro-i18n"; import { t } from "astro-i18n";
import {l} from "../util/util"; import { l } from "../util/util";
import Navbar from "@components/Navbar.svelte"; import Navbar from "@components/Navbar.svelte";
import ServerStatus from "../components/ServerStatus.svelte"; import ServerStatus from "../components/ServerStatus.svelte";
const {title, description} = Astro.props; const { title, description, transparentFooter = true } = Astro.props;
--- ---
<Basic title={title} description={description}> <Basic title={title} description={description} autoDarkMode={false}>
<slot name="head" slot="head"/> <slot name="head" slot="head" />
<Fragment> <Fragment>
<div class="min-h-screen flex flex-col"> <div class="min-h-screen flex flex-col">
<Navbar client:idle> <Navbar client:idle>
<Image src={localLogo} alt={t("navbar.logo.alt")} width="44" height="44" quality="max" <Image src={localLogo} alt={t("navbar.logo.alt")} width="44" height="44" quality="max" class="mr-2 p-1 bg-black rounded-full" slot="logo" />
class="mr-2 p-1 bg-black rounded-full" slot="logo"/>
</Navbar> </Navbar>
<main class="flex-1" data-pagefind-body> <main class="flex-1" data-pagefind-body>
<slot/> <slot />
</main> </main>
<footer class="bg-gray-900 w-full min-h-80 mt-4 pb-2 rounded-t-2xl flex flex-col dark:bg-neutral-900"> <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="flex-1 flex justify-evenly items-center md:items-start mt-4 md:flex-row flex-col gap-y-4">
<div class="footer-card"> <div class="footer-card">
<h1>Serverstatus</h1> <h1>Serverstatus</h1>
@@ -45,14 +47,17 @@ const {title, description} = Astro.props;
<div class="footer-card"> <div class="footer-card">
<h1>Social Media</h1> <h1>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
>
<a class="flex" href="/discord"> <a class="flex" href="/discord">
<DiscordSolid class="mr-2"/> <DiscordSolid class="mr-2" />
Discord</a> Discord</a
>
<a class="flex" href="https://git.steamwar.de"> <a class="flex" href="https://git.steamwar.de">
<FileCodeSolid class="mr-2"/> <FileCodeSolid class="mr-2" />
Gitea</a> Gitea</a
>
</div> </div>
</div> </div>
<span class="text-sm text-white text-center mt-1">© SteamWar.de - Made with ❤️ by Chaoscaot</span> <span class="text-sm text-white text-center mt-1">© SteamWar.de - Made with ❤️ by Chaoscaot</span>
@@ -77,4 +82,4 @@ const {title, description} = Astro.props;
.match { .match {
width: min(100vw, 70em); width: min(100vw, 70em);
} }
</style> </style>

View File

@@ -2,16 +2,16 @@
import NavbarLayout from "./NavbarLayout.astro"; import NavbarLayout from "./NavbarLayout.astro";
import BackgroundImage from "../components/BackgroundImage.astro"; import BackgroundImage from "../components/BackgroundImage.astro";
const {title, description} = Astro.props; const { title, description } = Astro.props;
--- ---
<NavbarLayout title={title} description={description}> <NavbarLayout title={title} description={description}>
<slot name="head" slot="head"/> <slot name="head" slot="head" />
<div class="h-screen w-screen fixed -z-10"> <div class="h-screen w-screen fixed -z-10">
<BackgroundImage /> <BackgroundImage />
</div> </div>
<div class="mx-auto bg-gray-100 p-8 rounded-b-md shadow-md pt-14 relative <div class="mx-auto p-8 rounded-b-md border-x-gray-100 shadow-md pt-14 relative
dark:text-white dark:bg-neutral-900" style="width: min(100%, 75em);"> text-white backdrop-blur-3xl" style="width: min(100%, 75em);">
<slot/> <slot />
</div> </div>
</NavbarLayout> </NavbarLayout>

View File

@@ -3,6 +3,6 @@ import App from "../../components/admin/App.svelte";
import Basic from "../../layouts/Basic.astro"; import Basic from "../../layouts/Basic.astro";
--- ---
<Basic clientSideRouter={false}> <Basic clientSideRouter={false} autoDarkMode={false}>
<App client:only="svelte"/> <App client:only="svelte" />
</Basic> </Basic>

View File

@@ -1,9 +1,8 @@
--- ---
import Basic from "../../layouts/Basic.astro"; import Basic from "../../layouts/Basic.astro";
import App from "@components/moderator/App.svelte"; import App from "@components/moderator/App.svelte";
--- ---
<Basic clientSideRouter={false}> <Basic clientSideRouter={false} autoDarkMode={false}>
<App client:only="svelte"/> <App client:only="svelte" />
</Basic> </Basic>

View File

@@ -1,29 +1,28 @@
--- ---
import dayjs from "dayjs"; 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 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";
import PlayerCount from "@components/PlayerCount.svelte"; import PlayerCount from "@components/PlayerCount.svelte";
import "../../public/fonts/barlow-condensed/barlow-condensed.css"; import "../../public/fonts/barlow-condensed/barlow-condensed.css";
import {type Player} from "../components/types/data"; import { type Player } from "../components/types/data";
import PostComponent from "../components/PostComponent.astro"; import PostComponent from "../components/PostComponent.astro";
import BackgroundImage from "../components/BackgroundImage.astro"; import BackgroundImage from "../components/BackgroundImage.astro";
const teamMember: { [key: string]: Player[]} = await fetch(import.meta.env.PUBLIC_API_SERVER + "/data/team") const teamMember: { [key: string]: Player[] } = await fetch(import.meta.env.PUBLIC_API_SERVER + "/data/team").then((value) => value.json());
.then(value => value.json());
const posts = await getCollection("announcements", entry => entry.id.split("/")[0] === astroI18n.locale); const posts = await getCollection("announcements", (entry) => entry.id.split("/")[0] === astroI18n.locale);
const germanPosts = await getCollection("announcements", entry => entry.id.split("/")[0] === astroI18n.fallbackLocale); const germanPosts = await getCollection("announcements", (entry) => entry.id.split("/")[0] === astroI18n.fallbackLocale);
germanPosts.forEach(value => { germanPosts.forEach((value) => {
if (posts.find(post => post.data.key === value.data.key)) { if (posts.find((post) => post.data.key === value.data.key)) {
return; return;
} else { } else {
posts.push(value); posts.push(value);
@@ -43,27 +42,29 @@ const prefixColorMap: {
}; };
--- ---
<NavbarLayout title={t("home.page")} description="SteamWar.de Homepage"> <NavbarLayout title={t("home.page")} description="SteamWar.de Homepage" transparentFooter={false}>
<div class="w-full h-screen relative mb-4"> <div class="w-full h-screen relative mb-4 z-10">
<div style="height: calc(100vh + 1rem)"> <div style="height: 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"> <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" <h1
style="transition: transform .7s ease-out, opacity .7s linear; filter: drop-shadow(2px 2px 5px black);"> class="text-4xl sm:text-6xl md:text-8xl font-extrabold text-white -translate-y-16 opacity-0 barlow tracking-wider"
<span class="bg-gradient-to-tr from-yellow-400 to-yellow-300 bg-clip-text text-transparent">{t("home.title.first")}</span><span style="transition: transform .7s ease-out, opacity .7s linear; filter: drop-shadow(2px 2px 5px black);"
class="text-neutral-600">{t("home.title.second")}</span> >
<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>
</h1> </h1>
<text-carousel class="h-20 w-full relative select-none"> <text-carousel class="h-20 w-full relative select-none">
<h2 class="-translate-y-16">{t("home.subtitle.1")}</h2> <h2 class="-translate-y-16">{t("home.subtitle.1")}</h2>
<h2>{t("home.subtitle.2")} <h2>
{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" <a href={l("join")} class="btn btn-ghost mt-32 px-8 flex" style="animation: normal flyIn forwards 1.2s ease-out"
style="animation: normal flyIn forwards 1.2s ease-out">{t("home.join")} >{t("home.join")}
<CaretRight width="24" height="24"/> <CaretRight width="24" height="24" />
</a> </a>
<style> <style>
@keyframes flyIn { @keyframes flyIn {
@@ -141,18 +142,18 @@ const prefixColorMap: {
<section class="w-full flex flex-col items-center justify-center shadow-2xl rounded-b-2xl pb-8"> <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"> <div class="py-10 flex flex-col lg:flex-row">
<Card client:idle> <Card client:idle>
<Crosshair1 height="64" width="64"/> <Crosshair1 height="64" width="64" />
<h1>{t("home.benefits.fights.title")}</h1> <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.1")}</p>
<p class="mt-4">{t("home.benefits.fights.description.2")}</p> <p class="mt-4">{t("home.benefits.fights.description.2")}</p>
</Card> </Card>
<Card client:idle> <Card client:idle>
<Rocket height="64" width="64"/> <Rocket height="64" width="64" />
<h1>{t("home.benefits.bau.title")}</h1> <h1>{t("home.benefits.bau.title")}</h1>
<p class="mt-4">{t("home.benefits.bau.description")}</p> <p class="mt-4">{t("home.benefits.bau.description")}</p>
</Card> </Card>
<Card client:idle> <Card client:idle>
<Pause height="64" width="64"/> <Pause height="64" width="64" />
<h1>{t("home.benefits.minigames.title")}</h1> <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.1")}</p>
<p class="mt-4">{t("home.benefits.minigames.description.2")}</p> <p class="mt-4">{t("home.benefits.minigames.description.2")}</p>
@@ -160,23 +161,29 @@ const prefixColorMap: {
</div> </div>
</section> </section>
<section class="w-full py-12 flex flex-wrap justify-center"> <section class="w-full py-12 flex flex-wrap justify-center">
{Object.entries(teamMember).map(([prefix, players]) => ( {
<Fragment> Object.entries(teamMember).map(([prefix, players]) => (
{players.map((v, index) => ( <Fragment>
<div class="inline-flex flex-col justify-end"> {players.map((v, index) => (
{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} <div class="inline-flex flex-col justify-end">
<Card extraClasses={`pt-8 pb-10 px-8 w-fit shadow-md ${prefixColorMap[prefix]}`} client:idle> {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}
<figure class="flex flex-col items-center" style="width: 150px"> <Card extraClasses={`pt-8 pb-10 px-8 w-fit shadow-md ${prefixColorMap[prefix]}`} client:idle>
<figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption> <figure class="flex flex-col items-center" style="width: 150px">
<Image src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${v.uuid}`} <figcaption class="text-center mb-4 text-2xl">{v.name}</figcaption>
class="transition duration-300 ease-in-out hover:scale-110 hover:backdrop-blur-lg hover:drop-shadow-2xl" <Image
alt={v.name + "s bust"} width="150" height="150"/> src={`${import.meta.env.PUBLIC_API_SERVER}/data/skin/${v.uuid}`}
</figure> class="transition duration-300 ease-in-out hover:scale-110 hover:backdrop-blur-lg hover:drop-shadow-2xl"
</Card> alt={v.name + "s bust"}
</div> width="150"
))} height="150"
</Fragment> />
))} </figure>
</Card>
</div>
))}
</Fragment>
))
}
</section> </section>
</NavbarLayout> </NavbarLayout>
@@ -184,13 +191,17 @@ const prefixColorMap: {
text-carousel { text-carousel {
> * { > * {
@apply absolute top-0 left-0 w-full text-xl sm:text-4xl italic text-white text-center opacity-0; @apply absolute top-0 left-0 w-full text-xl sm:text-4xl italic text-white text-center opacity-0;
transition: transform .5s ease-out, opacity .5s linear; transition:
transform 0.5s ease-out,
opacity 0.5s linear;
text-shadow: 2px 2px 5px black; text-shadow: 2px 2px 5px black;
} }
} }
.barlow { .barlow {
font-family: Barlow Condensed, sans-serif; font-family:
Barlow Condensed,
sans-serif;
} }
.card { .card {
@@ -207,7 +218,7 @@ const prefixColorMap: {
} }
> svg { > svg {
@apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl @apply transition-transform duration-300 ease-in-out hover:scale-110 hover:drop-shadow-2xl;
} }
} }
</style> </style>

View File

@@ -94,4 +94,22 @@
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
article {
> * {
all: revert;
}
code {
@apply dark:text-neutral-400 text-neutral-800;
}
pre.astro-code {
@apply w-fit p-4 rounded-md border-2 border-gray-600 my-4;
}
a {
@apply text-neutral-800 dark:text-neutral-400 hover:underline;
}
}
} }

View File

@@ -31,7 +31,7 @@ table {
text-align: center; text-align: center;
tr:nth-child(odd) { tr:nth-child(odd) {
@apply bg-neutral-200 dark:bg-neutral-800; @apply backdrop-brightness-125;
} }
} }
} }

View File

@@ -24,4 +24,4 @@
"$lib": ["./src"] "$lib": ["./src"]
} }
} }
} }