New rewrite with svelte and pocketbase

This commit is contained in:
zuma 2025-04-12 16:08:00 +02:00
parent 72bfc2ed89
commit 160617af60
95 changed files with 4402 additions and 0 deletions

34
frontend/src/app.css Normal file
View file

@ -0,0 +1,34 @@
@import 'tailwindcss';
@font-face {
font-family: "Beleren";
src: url("/fonts/Beleren2016-Bold.woff");
}
@font-face {
font-family: "Inter-Tight-Normal";
src: url("/fonts/inter-tight-latin-400-normal.ttf");
}
@font-face {
font-family: "Inter-Tight-Italic";
src: url("/fonts/inter-tight-latin-400-italic.ttf");
}
@font-face {
font-family: "Inter-Tight-Bold";
src: url("/fonts/inter-tight-latin-800-normal.ttf");
}
@font-face {
font-family: "Inter-Tight-Bold-Italic";
src: url("/fonts/inter-tight-latin-800-italic.ttf");
}
.font-beleren {
font-family: 'Beleren';
}
.font-inter-tight {
font-family: 'Inter-Tight-Normal';
}

13
frontend/src/app.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
// See https://svelte.dev/docs/kit/types#app.d.ts
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

12
frontend/src/app.html Normal file
View file

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -0,0 +1,17 @@
<script>
let { name = "", url = "#", sanitizedName = "", smallImage = "", normalImage="", price = 0, cardmarketUri = "", numberOfDecks = 0, numberOfPossibleDecks = undefined, synergy = undefined } = $props()
</script>
<div class="flex flex-col w-full gap-0">
<a class="w-full" href={url}><img src={normalImage} alt={"Scan de carte pour " + name} class="w-full rounded-md aspect-[488/680]" loading="lazy"/></a>
<a href={cardmarketUri} target="_blank" class="text-xs w-full text-center mt-2">{price}</a>
<span class="w-full text-center text-xs">{name}</span>
{#if numberOfPossibleDecks == undefined}
<span class="w-full text-center">{numberOfDecks} Decks</span>
{:else}
<span class="w-full text-center">{numberOfDecks} Decks sur {numberOfPossibleDecks} ({numberOfPossibleDecks != 0 ? Math.round(100 * (numberOfDecks / numberOfPossibleDecks)) : 0}%)</span>
{/if}
{#if synergy != undefined}
<span class="w-full text-center">Synergy {synergy > 0 ? "+" + Math.round(100*synergy).toString() : Math.round(100*synergy).toString()}%</span>
{/if}
</div>

View file

@ -0,0 +1,7 @@
<script>
let { children } = $props();
</script>
<div class="grid grid-cols-1 pl-4 pr-4 md:grid-cols-3 lg:grid-cols-5 gap-4 w-full max-w-6xl">
{@render children()}
</div>

View file

@ -0,0 +1,38 @@
<script>
import Input from "./base/Input.svelte";
let { dialog = $bindable() } = $props()
let searchQuery = $state("")
let searchResult = $state([])
async function search() {
searchResult = []
if(searchQuery.length > 3) {
fetch("/api/search?q=" + searchQuery)
.then( response => response.json() )
.then( data => { searchResult = data; console.log(data) } )
}
}
</script>
<dialog class="max-w-96 w-full backdrop:backdrop-blur-sm backdrop:bg-black/30 p-4 rounded-md"
style="margin: auto; width: calc(100% - 20px);"
bind:this={dialog}
onclick={(e) => { if (e.target === dialog) dialog.close(); }}>
<div>
<Input type="text" placeholder="Rechercher des cartes" bind:value={searchQuery} oninput={search} focus/>
<div class="mt-4 flex flex-col gap-2 h-64 overflow-y-scroll items-center">
{#if searchResult.length == 0}
<span class="mt-8 text-xs">Pas de résultat...</span>
{:else}
{#each searchResult as result}
<a class="flex flex-col gap-0 w-full" href={result.Url}>
<span>{result.Name}</span>
<span class="text-xs">{result.SetName}</span>
</a>
{/each}
{/if}
</div>
</div>
</dialog>

View file

@ -0,0 +1,3 @@
<div class="w-full flex flex-row justify-center text-xs text-center p-4">
<span>Brawlset is unofficial Fan Content permitted under the Fan Content Policy. Not approved/endorsed by Wizards. Portions of the materials used are property of Wizards of the Coast. ©Wizards of the Coast LLC.</span>
</div>

View file

@ -0,0 +1,226 @@
<script>
import { getContext, onMount } from "svelte";
import Blue from "./icons/Blue.svelte";
import White from "./icons/White.svelte";
import Black from "./icons/Black.svelte";
import Colorless from "./icons/Colorless.svelte";
import Red from "./icons/Red.svelte";
import Green from "./icons/Green.svelte";
import IconBase from "./icons/IconBase.svelte";
import CardSearch from "./CardSearch.svelte";
import SearchIcon from "./icons/SearchIcon.svelte";
import DeckIcon from "./icons/DeckIcon.svelte";
import MenuIcon from "./icons/MenuIcon.svelte";
let dialog
let username = $state("")
let brawlsets = $state([])
let isLoggedIn = $state(false)
onMount( async () => {
const storageData = window.localStorage.getItem("pocketbase_auth")
if (storageData != null) {
const jsonData = JSON.parse(storageData)
username = jsonData.record.name
isLoggedIn = true
}
fetch("/json/misc/brawlsets")
.then( response => response.json() )
.then( data => { brawlsets = data; console.log(data) } )
});
</script>
<div class="font-inter-tight flex flex-row justify-between w-screen p-4 h-16">
<a class="flex flex-row gap-2 items-center" href="/">
<img alt="brawlset website logo" src="/assets/logo.png" class="h-8" />
<span class="font-beleren text-3xl mt-2 bg-gradient-to-r from-black to-orange-500 bg-clip-text text-transparent">BrawlSet</span>
</a>
<div class="hidden flex-row gap-4 items-center md:flex">
<details class="dropdown w-0 md:w-fit">
<summary role="button">
<a class="cursor-pointer text-stone-500">Commandants</a>
</summary>
<ul>
<li>Top commandants</li>
<li>
<div>Mono</div>
<ul>
<li><a class="flex flex-row items-center gap-2" href="#"><White/><span>Blanc</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Blue/><span>Bleu</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Black/><span>Noir</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Red/><span>Rouge</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Green/><span>Vert</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><Colorless/><span>Incolor</span></a></li>
</ul>
</li>
<li>
<div>2 couleurs</div>
<ul>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/></div><span>Azorius</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/></div><span>Dimir</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/></div><span>Rakdos</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/></div><span>Gruul</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/></div><span>Selesnya</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Black/></div><span>Orzhov</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Red/></div><span>Izzet</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Green/></div><span>Golgari</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><White/></div><span>Boros</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><Blue/></div><span>Simic</span></a></li>
</ul>
</li>
<li>
<div>3 couleurs</div>
<ul>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/></div><span>Esper</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/><Red/></div><span>Grixis</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/><Green/></div><span>Jund</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/><White/></div><span>Naya</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/><Blue/></div><span>Bant</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Black/><Green/></div><span>Abzan</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Red/><White/></div><span>Jeskai</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Green/><Blue/></div><span>Sultai</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><White/><Black/></div><span>Mardu</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><Blue/><Red/></div><span>Temur</span></a></li>
</ul>
</li>
<li>
<div>4+ couleurs</div>
<ul>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/><Red/></div><span>Yore-Tiller</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Blue/><Black/><Red/><Green/></div><span>Glint-Eye</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Black/><Red/><Green/><White/></div><span>Dune-Brood</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Red/><Green/><White/><Blue/></div><span>Ink-Treader</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><Green/><White/><Blue/><Black/></div><span>Witch-Maw</span></a></li>
<li><a class="flex flex-row items-center gap-2" href="#"><div class="flex flex-row gap-0"><White/><Blue/><Black/><Red/><Green/></div><span>5 couleurs</span></a></li>
</ul>
</li>
</ul>
</details>
<details class="dropdown">
<summary role="button">
<a class="cursor-pointer text-stone-500">BSets</a>
</summary>
<ul>
{#each brawlsets.slice(0,5) as brawlset}
<li>
<a class="flex flex-row gap-2 items-center" href={"/bset/" + brawlset.SanitizedName}>
<div class="flex flex-row gap-1">
{#each brawlset.IconsSvgUri as icon}
<IconBase src={icon} alt="brawlset icon"/>
{/each}
</div>
{brawlset.Name}
</a>
</li>
{/each}
<li><a href="/bset/all">Plus de BSets...</a></li>
</ul>
</details>
<a class="cursor-pointer text-stone-500" href="/regles">Règles</a>
<a class="cursor-pointer text-stone-500" href="/faq">F.A.Q</a>
</div>
<div class="flex flex-row gap-4 items-center text-stone-500">
<button class="flex flex-row items-center" onclick={() => dialog.showModal()}>
<SearchIcon class="visible w-fit md:invisible md:w-0" />
<span class="invisible w-0 md:w-fit md:visible">Rechercher</span>
</button>
{#if isLoggedIn}
<a class="cursor-pointer flex flex-row items-center" href="/decks">
<DeckIcon class="visible w-fit md:invisible md:w-0" />
<span class="invisible w-0 md:w-fit md:visible">Decks</span>
</a>
<a class="cursor-pointer hidden md:inline" href="/profil">
<span class="invisible w-0 md:w-fit md:visible">{username}</span>
</a>
{:else}
<a class="cursor-pointer" href="/connexion">Connexion</a>
<a class="cursor-pointer" href="#">Inscription</a>
{/if}
<details class="dropdown inline md:hidden">
<summary role="button">
<a class="cursor-pointer"><MenuIcon /></a>
</summary>
<ul>
{#each brawlsets.slice(0,5) as brawlset}
<li>
<a class="flex flex-row gap-2 items-center" href={"/bset/" + brawlset.SanitizedName}>
<div class="flex flex-row gap-1">
{#each brawlset.IconsSvgUri as icon}
<IconBase src={icon} alt="brawlset icon"/>
{/each}
</div>
{brawlset.Name}
</a>
</li>
{/each}
<li><a href="/bset/all">Plus de BSets...</a></li>
</ul>
</details>
</div>
</div>
<CardSearch bind:dialog={dialog} />
<style>
/* Close the dropdown with outside clicks */
.dropdown > summary::before {
display: none;
}
.dropdown[open] > summary::before {
content: ' ';
display: block;
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 1;
}
.dropdown summary {
list-style: none;
list-style-type: none;
}
.dropdown ul {
white-space: nowrap;
position: absolute;
margin-top: 5px;
padding: 5px;
box-sizing: border-box;
z-index: 2;
background-color: white;
border-radius: 5px;
border: 1px solid #D3D3D3;
box-shadow: 0px 4px 5px #D3D3D3;
list-style: none;
}
.dropdown li {
padding: 5px;
border-radius: 5px;
cursor: pointer;
position: relative;
}
.dropdown ul ul {
display: none;
left: 100%;
position: absolute;
top: 0;
}
.dropdown li:hover {
background-color: hsl(240 4.8% 95.9%);
}
.dropdown li:hover > ul {
display: block;
}
</style>

View file

@ -0,0 +1,8 @@
<script>
let { children, ...restProps } = $props()
</script>
<button class="bg-orange-500 cursor-pointer text-white rounded-md p-2" {...restProps}>
{@render children()}
</button>

View file

@ -0,0 +1,5 @@
<script>
let { type, placeholder = "", name = "", value = $bindable(), ...restProps} = $props()
</script>
<input type={type} placeholder={placeholder} name={name} class="flex h-auto min-h-9 w-full border-l-orange-500 border-l-4 bg-transparent px-3 py-1 text-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50" bind:value {...restProps} />

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/B.svg" alt="Black mana icon"/>

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/U.svg" alt="Blue mana icon"/>

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/C.svg" alt="Colorless mana icon"/>

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M3 9a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2M3 11h3c.8 0 1.6.3 2.1.9l1.1.9c1.6 1.6 4.1 1.6 5.7 0l1.1-.9c.5-.5 1.3-.9 2.1-.9H21" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 399 B

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/G.svg" alt="Green mana icon"/>

View file

@ -0,0 +1,5 @@
<script>
let { src, alt } = $props();
</script>
<img src={src} alt={alt} loading="lazy" class="min-h-4 min-w-4 size-4" />

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 12h16M4 6h16M4 18h16" />
</svg>

After

Width:  |  Height:  |  Size: 240 B

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/R.svg" alt="Red mana icon"/>

View file

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" {...$$props}>
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<circle cx="11" cy="11" r="8" />
<path d="m21 21l-4.3-4.3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 279 B

View file

@ -0,0 +1,5 @@
<script>
import IconBase from "./IconBase.svelte";
</script>
<IconBase src="https://svgs.scryfall.io/card-symbols/W.svg" alt="White mana icon"/>

View file

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

View file

@ -0,0 +1,15 @@
<script lang="ts">
import '../app.css';
import Navbar from '$lib/components/Navbar.svelte';
import Footer from '$lib/components/Footer.svelte';
let { children } = $props();
</script>
<div class="w-screen min-h-screen flex flex-col gap-0">
<Navbar />
<div class="mt-8 font-inter-tight grow">
{@render children()}
</div>
<Footer />
</div>

View file

@ -0,0 +1,6 @@
<div class="flex flex-col w-full items-center mt-8 md:mt-32 p-4 text-center">
<p class="mb-12 text-3xl text-orange-500">CE SITE EST EN COURS DE DEVELOPPEMENT - NE SAUVEGARDEZ PAS VOS DONNÉES UNIQUEMENT SUR BRAWLSET</p>
<h1 class="text-8xl font-beleren">The BrawlSet</h1>
<p class="text-center text-stone-500 mt-12">Un système de règles MTG basé sur le mode de jeu commander et inventé à Rennes, pays de la galette saucisse.<br />
Pour plus d&apos;informations allez voir les <a href="/rules" class="text-orange-500">règles</a> ou la <a href="/faq" class="text-orange-500">FAQ</a>.</p>
</div>

View file

@ -0,0 +1,95 @@
<script lang="ts">
import Card from '$lib/components/Card.svelte';
import CardGrid from '$lib/components/CardGrid.svelte';
import { onMount } from 'svelte';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
let commander = $state([])
let creature = $state([])
let instant = $state([])
let planeswalker = $state([])
let artifact = $state([])
let sorcery = $state([])
let enchantment = $state([])
let land = $state([])
let slug = $derived(data.slug)
let test = $state("")
$effect(() => {
commander = creature = instant = planeswalker = artifact = sorcery = enchantment = []
fetch("/json/brawlset/"+slug)
.then( response => response.json() )
.then( data => {
commander = data.Cards.commander
creature = data.Cards.creature
sorcery = data.Cards.sorcery
instant = data.Cards.instant
land = data.Cards.land
enchantment = data.Cards.enchantment
artifact = data.Cards.artifact
planeswalker = data.Cards.planeswalker
})
});
</script>
<div class="flex flex-col w-full items-center">
<h1>{slug}</h1>
<h2>Commandants</h2>
<CardGrid>
{#each commander as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks}/>
{/each}
</CardGrid>
<h2>Planeswalker</h2>
<CardGrid>
{#each planeswalker as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Creature</h2>
<CardGrid>
{#each creature as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Rituels</h2>
<CardGrid>
{#each sorcery as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Artefacts</h2>
<CardGrid>
{#each artifact as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Éphémères</h2>
<CardGrid>
{#each instant as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Enchantements</h2>
<CardGrid>
{#each enchantment as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
<h2>Terrains</h2>
<CardGrid>
{#each land as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} />
{/each}
</CardGrid>
</div>

View file

@ -0,0 +1,6 @@
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
console.log("test")
return {slug: params.slug, test: "yeeeeheeee"}
};

View file

@ -0,0 +1,29 @@
<script>
import Input from "$lib/components/base/Input.svelte";
import IconBase from "$lib/components/icons/IconBase.svelte";
import { onMount } from "svelte";
let brawlsets = $state([])
onMount(async () => {
fetch("/json/misc/brawlsets")
.then( response => response.json() )
.then( data => { brawlsets = data; console.log(data) } )
})
</script>
<div class="m-auto mt-16 max-w-4xl flex flex-col gap-4">
<Input type="text" placeholder="Rechercher des brawlsets" />
<div class="grid grid-cols-3 gap-2">
{#each brawlsets as brawlset}
<a class="flex flex-row gap-2 items-center" href={"/bset/" + brawlset.SanitizedName}>
<div class="flex flex-row items-center">
{#each brawlset.IconsSvgUri as icon}
<IconBase src={icon} alt={"Set icon for " + brawlset.Name} />
{/each}
</div>
<span>{brawlset.Name}</span>
</a>
{/each}
</div>
</div>

View file

@ -0,0 +1,90 @@
<script lang="ts">
import Card from '$lib/components/Card.svelte';
import CardGrid from '$lib/components/CardGrid.svelte';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
let creature = $state([])
let instant = $state([])
let planeswalker = $state([])
let artifact = $state([])
let sorcery = $state([])
let enchantment = $state([])
let land = $state([])
let slug = $derived(data.slug)
let mainCardData = $state({})
$effect(() => {
creature = instant = planeswalker = artifact = sorcery = enchantment = []
console.log(data)
fetch("/json/commander/"+slug)
.then( response => response.json() )
.then( data => {
creature = data.Cards.creature
sorcery = data.Cards.sorcery
instant = data.Cards.instant
land = data.Cards.land
enchantment = data.Cards.enchantment
artifact = data.Cards.artifact
planeswalker = data.Cards.planeswalker
mainCardData = data.MainCard
})
});
</script>
<div class="flex flex-col w-full items-center">
<div class="w-full max-w-64">
<Card class="" normalImage={mainCardData.NormalImage} name={mainCardData.Name} price={mainCardData.Price} mainCardDatamarketUri={mainCardData.CardmarketUri} numberOfDecks={mainCardData.NumberOfDecks} />
</div>
<h2>Planeswalker</h2>
<CardGrid>
{#each planeswalker as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Creature</h2>
<CardGrid>
{#each creature as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Rituels</h2>
<CardGrid>
{#each sorcery as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Artefacts</h2>
<CardGrid>
{#each artifact as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Éphémères</h2>
<CardGrid>
{#each instant as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Enchantements</h2>
<CardGrid>
{#each enchantment as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
<h2>Terrains</h2>
<CardGrid>
{#each land as card}
<Card normalImage={card.NormalImage} url={card.Url} name={card.Name} price={card.Price} cardmarketUri={card.CardmarketUri} numberOfDecks={card.NumberOfDecks} numberOfPossibleDecks={card.NumberOfPossibleDecks} synergy={card.Synergy} />
{/each}
</CardGrid>
</div>

View file

@ -0,0 +1,6 @@
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
console.log("test")
return {slug: params.slug, test: "yeeeeheeee"}
};

View file

@ -0,0 +1,34 @@
<script>
import PocketBase from 'pocketbase';
import Input from '$lib/components/base/Input.svelte';
import { setContext } from 'svelte';
const pb = new PocketBase('http://localhost:8090');
async function login(form) {
const formData = new FormData(form.target)
const data = {};
for (let field of formData) {
const [key, value] = field;
data[key] = value;
}
console.log(data)
try {
const authData = await pb.collection('users').authWithPassword(data.email, data.password);
console.log(authData)
console.log(pb.authStore.token);
window.location.href = "/"
} catch (err) {
console.log("Failed to log");
}
}
</script>
<div class="flex flex-col items-center mt-32 h-full">
<form class="flex flex-col gap-4 w-lg" on:submit|preventDefault={(e) => login(e)}>
<Input name="email" placeholder="email" type="email" />
<Input name="password" placeholder="password" type="password" />
<button class="border rounded p-2 bg-gray-800 text-white hover:bg-gray-700">Connexion</button>
</form>
</div>

View file

@ -0,0 +1,104 @@
<script>
import Button from "$lib/components/base/Button.svelte";
import Input from "$lib/components/base/Input.svelte";
import { onMount } from "svelte";
let brawlsets = $state([])
let deckImporter = $state("")
let selectedBset = $state("")
let deckName = $state("")
let deckUrl = $state("")
let commander = $state("")
let token = $state("")
onMount( async () => {
const storageData = window.localStorage.getItem("pocketbase_auth")
if (storageData != null) {
const jsonData = JSON.parse(storageData)
token = jsonData.token
}
fetch("/json/misc/brawlsets")
.then( response => response.json() )
.then( data => {
let sortedData = data
sortedData.sort((a,b) => a.Name.localeCompare(b.Name))
brawlsets = sortedData
})
})
function setCommander(txt) {
let lines = txt.split("\n")
commander = lines[lines.length - 1].slice(1)
}
function getDataFromLine(line){
if(line != "") {
const data = line.split(" ")
const amount = parseInt(data[0])
const name = data.slice(1).join(" ").split(" // ")[0].split("/")[0]
return {"name":name, "amount":amount}
} else {
return null
}
}
function importDeck(){
const deckText = deckImporter
let lines = deckText.split("\n")
lines = lines.filter((line) => line.match(/[0-9]+\s[\w]+/) != undefined)
const dataToSend = { name: deckName, url: deckUrl, selected_bset: selectedBset,commander_name: getDataFromLine(commander).name, cards: [] }
lines.slice(0, lines.length - 1).forEach((line) => {
const data = getDataFromLine(line)
if(data != null) {
dataToSend.cards.push(data)
}
});
console.log(dataToSend)
fetch('/api/deck/create', {
method: "POST",
headers: {Authorization: token, "Content-Type": "application/json"},
body: JSON.stringify(dataToSend)
}).then((res) => {
if(res.status == 200) {
res.json().then((apiData) => {
deckImporter = ""
selectedBset = ""
deckName = ""
deckUrl = ""
commander = ""
console.log(apiData)
})
} else if (res.status == 401) {
res.json().then((apiData) => {
console.log(apiData)
})
}
})
}
</script>
<div class="flex flex-col w-full items-center p-4">
<div class="flex flex-col-reverse md:flex-row max-w-4xl w-full gap-4">
<div class="flex flex-col w-full">
<textarea placeholder="Deck list dans le format MTGO" class="h-60" bind:value={deckImporter} oninput={(e) => setCommander(e.target.value)}></textarea>
<span>Commandant : {commander}</span>
</div>
<div class="flex flex-col w-full gap-4">
<Input type="text" placeholder="Nom du deck" bind:value={deckName} />
<Input type="text" placeholder="Url du deck (facultatif)" bind:value={deckUrl} />
<select bind:value={selectedBset}>
<option value="0">Selectionnez un Brawlset...</option>
{#each brawlsets as brawlset}
<option value={brawlset.SanitizedName}>
{brawlset.Name}
</option>
{/each}
</select>
<Button onclick={importDeck}>Importer</Button>
</div>
</div>
</div>

View file

@ -0,0 +1,4 @@
<div class="flex flex-col mt-16 w-full items-center">
<h1>F.A.Q</h1>
<p>Coming soon...</p>
</div>

View file

@ -0,0 +1,13 @@
<script>
import Button from "$lib/components/base/Button.svelte";
function removeAuth() {
window.localStorage.removeItem("pocketbase_auth")
window.location.href = "/"
}
</script>
<div class="flex flex-col items-center mt-16">
<Button onclick={removeAuth}>Deconnexion</Button>
</div>

View file

@ -0,0 +1,31 @@
<div class="flex flex-col items-center">
<div class="flex flex-col items-center w-full">
<h1 class="text-3xl">BrawlSet Canon Règles Officielles</h1>
<h2>BrawlSet Committee</h2>
<h2>17 Octobre 2022</h2>
</div>
<div class="flex flex-col max-w-5xl gap-4 mt-8 mb-32">
<h2 class="font-bold">903.BS. Le mode de jeu BrawlSet</h2>
<ul class="flex flex-col gap-4">
<li><strong>903.BS.1</strong> Le BrawlSet est une variante du Commander. Cette variante utilise les règles classiques du Commander avec les modifications suivantes.</li>
<li><strong>903.BS.2</strong> La Règle dOr du format est dutiliser des cartes provenant du même set. Pour des raisons de niveau des cartes et/ou dhistoire, certains sets sont regroupés en « BSet »</li>
<li><strong>903.BS.3</strong> La liste des BSets est disponible dans le document List_BSet_FR.pdf</li>
<li><strong>903.BS.4</strong> Une carte fait partie dun BSet si elle a été imprimée au moins une fois avec le symbole dédition dun des sets qui composent le BSet. La liste complète des symboles dédition peut être trouvée dans la section Card Set Archive du site Magic de Wizard of the Coast (<a href="https://magic.wizards.com/en/products/card-set-archive">lien</a>)</li>
<li>Exemple : <em>Cela prend en compte les cartes Timeshifted pour Time Spiral, les cartes de Brawl Decks, de Planeswalker Decks et de Theme Boosters, ainsi que les cartes de Buy a Box. Cela ne prend pas en compte les cartes des Masterpiece series (Explorer dIxalan, Expeditions de Zendikar, Inventions de Kaladesh, Invocations de Amonkhet, et Planeswalker Mythic Editions de Guilds of Ravnica, Ravnica Allegeance et War of the Spark).</em></li>
<li><strong>903.BS.5</strong> Il ny a pas de sideboard pour modifier son deck entre deux parties de BrawlSet. Cependant, comme certaines cartes permettent damener des cartes dans la partie depuis lextérieur du jeu, les joueur·euses peuvent avoir un « wishboard ». Le nombre maximum de cartes dun wishboard est 15, il ny pas de nombre minimum de cartes.</li>
<li><strong>903.BS.6</strong> Les cartes des wishboard sont hors de la partie, ainsi aucun effet de la partie ne peut permettre à un·e joueur·euse de regarder ou rechercher dans le wishboard dun·e autre joueur·euse. Un·e joueur·euse ne peut amener une carte hors de la partie dans la partie sauf si cette carte vient de son wishboard ou dune partie principale dune sous-partie. Il ny a pas de restriction sur lidentité colorée des cartes quun·e joueur·euse peut amener dans la partie depuis lextérieur du jeu, ceci est une modification de la règle 903.11.</li>
<li>Exemple : <em>Léon lance Emrakul, the Promised End pour prendre le contrôle de ladversaire ciblé durant son prochain tour et prend le contrôle du prochain tour de Dylan. Léon peut faire en sorte que Dylan lance Granted, laventure de Fae of Wishes pendant quil contrôle le tour de Dylan. Léon ne pourra pas chercher ou trouver quoi que ce soit dans le wishboard de Dylan. Parce que leffet demande de chercher dans le wishboard du joueur / de la joueuse lançant cette carte, Léon ne peut pas chercher dans son wishboard. Aucune carte ne sera amenée dans la partie.</em></li>
<li><strong>903.BS.7</strong> Un·e joueur·euse désigne comme commandant un Planeswalker légendaire ou une créature légendaire. Ceci est une modification de la règle 903.3</li>
<li><strong>903.BS.8</strong> Si un deck de BrawlSet utilise un·e compagnon·ne, le/la compagnon·ne doit respecter lidentité colorée du commandant.</li>
<li><strong>903.BS.9</strong> La règle de construction en singleton du Commander sapplique à la combinaison du deck et du wishboard. Ceci est une modification de la règle 903.5b</li>
<li><strong>903.BS.10</strong> Toutes les cartes dun deck de BrawlSet doivent faire partie du même BSet. Cette règle sapplique à la combinaison du deck et du wishboard. Cette règle sapplique aux terrains de base. Ce BSet est le BSet du deck.</li>
<li><strong>903.BS.11</strong> Une carte avec un type de terrain de base ne peut être inclue dans un deck que si elle ne produit que du mana de lidentité colorée du commandant. Si aucun terrain de base faisant partie du BSet de ce deck ne vérifie cette propriété, cette règle ne sapplique pas pour les terrains de base pour ce deck. Ceci est une modification de la règle 903.5d.</li>
<li>Exemple : <em>Paul veut construire un deck de BrawlSet War of the Spark avec Karn, the Great Creator en commandant. Comme lidentité colorée de Karn est incolore et que les Wastes nont pas été éditées dans War of the Spark, Paul peut utiliser nimporte quel terrain de base édité dans War of the Spark pour construire son deck, par exemple une Forest, huit Islands, neuf Mountains, treize Plains et quinze Swamps.</em></li>
<li><strong>903.BS.12</strong> La taille minimum dun deck de BrawlSet est 60, il ny a pas de taille maximum de deck. Ceci est une modification de la règle 903.5a.</li>
<li><strong>903.BS.13</strong> Dans une partie de BrawlSet, le nombre de point de vie de départ est 20. Ceci est une modification de la règle 119.1c</li>
<li><strong>903.BS.14</strong> Les parties de BrawlSet nutilisent pas la règle des blessures de commandant, ainsi laction décrite dans la règle 704.5v, qui fait perdre un·e joueur·euse qui a subi 21 blessures de combat ou plus dun commandant ne sapplique pas. Ceci est une modification de la règle 704.5v.</li>
<li><strong>903.BS.15</strong> Au début dun match, chaque joueur·euse doit déclarer son BSet, il ne peut pas être changé entre les parties dun même match.</li>
<li><strong>903.BS.16</strong> Le format de BrawlSet utilise la règle du commander swap. Cela signifie que nimporte quel joueur·euse peut changer de commandant entre deux parties dun match. Le nouveau commandant doit être une carte du deck, lancien commandant devient une carte normale du deck. Le commandant du deck reste secret jusquà ce quil soit révélé au début de la partie.</li>
</ul>
</div>
</div>