Improve frontpage performance (#929)

* initial commit

* merge with origin/main

* fix package-lock.json

* fix tiptap mentions

* remove flutter

* update package-lock.json

* disable tailwindcss/typography until https://github.com/tailwindlabs/tailwindcss/issues/19946 is fixed

* reactivate tailwind/typography after fix

---------

Co-authored-by: Christian Beutel <>
Co-authored-by: slothful-vassal <89943360+slothful-vassal@users.noreply.github.com>
This commit is contained in:
Flomp
2026-04-22 09:04:53 +02:00
committed by GitHub
parent c9256aeb29
commit 345452e720
19 changed files with 4133 additions and 959 deletions

3007
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,28 +16,28 @@
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.58.2", "@playwright/test": "^1.58.2",
"@sveltejs/adapter-auto": "^7.0.1", "@sveltejs/adapter-auto": "^7.0.1",
"@sveltejs/enhanced-img": "^0.10.4",
"@sveltejs/kit": "^2.57.1", "@sveltejs/kit": "^2.57.1",
"@sveltejs/vite-plugin-svelte": "^7.0.0", "@sveltejs/vite-plugin-svelte": "^7.0.0",
"@tailwindcss/typography": "^0.5.19",
"@types/canvas-confetti": "^1.9.0", "@types/canvas-confetti": "^1.9.0",
"@types/node": "^25.3.3", "@types/node": "^25.3.3",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"svelte": "^5.53.6", "svelte": "^5.53.6",
"svelte-check": "^4.3.6", "svelte-check": "^4.3.6",
"sveltekit-openapi-generator": "^0.1.5", "sveltekit-openapi-generator": "^0.1.5",
"tailwindcss": "^4.0.0",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"vite": "^8.0.8" "vite": "^8.0.9"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@felte/validator-zod": "^1.0.18", "@felte/validator-zod": "^1.0.18",
"@fortawesome/fontawesome-free": "^7.1.0", "@fortawesome/fontawesome-free": "^7.1.0",
"@sveltejs/adapter-node": "^5.5.2", "@sveltejs/adapter-node": "^5.5.2",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.2.4",
"@tailwindcss/vite": "^4.2.2", "@threlte/core": "^8.5.9",
"@threlte/core": "^8.3.1", "@threlte/extras": "^9.14.6",
"@threlte/extras": "^9.7.1",
"@tiptap/core": "^2.14.0", "@tiptap/core": "^2.14.0",
"@tiptap/extension-heading": "^2.14.0", "@tiptap/extension-heading": "^2.14.0",
"@tiptap/extension-link": "^2.14.0", "@tiptap/extension-link": "^2.14.0",
@@ -50,7 +50,7 @@
"@turf/destination": "^7.3.3", "@turf/destination": "^7.3.3",
"@turf/distance": "^7.3.3", "@turf/distance": "^7.3.3",
"@types/chart.js": "^4.0.1", "@types/chart.js": "^4.0.1",
"@types/three": "^0.182.0", "@types/three": "^0.183.1",
"@xmldom/xmldom": "^0.8.12", "@xmldom/xmldom": "^0.8.12",
"activitypub-types": "^1.1.0", "activitypub-types": "^1.1.0",
"autoprefixer": "^10.4.24", "autoprefixer": "^10.4.24",
@@ -78,7 +78,8 @@
"pocketbase": "^0.26.8", "pocketbase": "^0.26.8",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"svelte-i18n": "^4.0.0", "svelte-i18n": "^4.0.0",
"three": "^0.182.0", "tailwindcss": "^4.2.4",
"three": "^0.183.1",
"vitest": "^4.1.4", "vitest": "^4.1.4",
"zod": "^3.24.1" "zod": "^3.24.1"
}, },
@@ -87,4 +88,4 @@
"vite": "$vite" "vite": "$vite"
} }
} }
} }

View File

@@ -0,0 +1,59 @@
<!--
Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf
Command: npx @threlte/gltf@3.0.7 ../../../../static/models/marker.glb -T
-->
<script lang="ts">
import { T } from "@threlte/core";
import { useGltf, useDraco } from "@threlte/extras";
import type { Snippet } from "svelte";
interface Props {
fallback?: Snippet;
error?: Snippet<[any]>;
children?: Snippet<[any]>;
[key: string]: any;
}
let {
fallback,
error,
children,
ref = $bindable(),
...props
}: Props = $props();
const gltf = useGltf("/models/marker.glb", { dracoLoader: useDraco() });
</script>
<T.Group bind:ref dispose={false} {...props}>
{#await gltf}
{@render fallback?.()}
{:then gltf}
<T.Group>
<T.Mesh
geometry={gltf.nodes["Node-Mesh003"].geometry}
material={gltf.materials.mat8}
/>
<T.Mesh
geometry={gltf.nodes["Node-Mesh003_1"].geometry}
material={gltf.materials.mat23}
>
<T.PointLight
position={[-1, 0.8, 0]}
color="#ffffff"
intensity={3}
></T.PointLight>
<T.PointLight
position={[1, 0.8, 0]}
color="#ffffff"
intensity={3}
></T.PointLight></T.Mesh
>
</T.Group>
{:catch err}
{@render error?.({ error: err })}
{/await}
{@render children?.({ ref })}
</T.Group>

View File

@@ -0,0 +1,42 @@
<!--
Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf
Command: npx @threlte/gltf@3.0.7 ../../../../static/models/moon.glb -T
-->
<script lang="ts">
import { T } from "@threlte/core";
import { useGltf, useDraco } from "@threlte/extras";
import type { Snippet } from "svelte";
interface Props {
fallback?: Snippet;
error?: Snippet<[any]>;
children?: Snippet<[any]>;
[key: string]: any;
}
let {
fallback,
error,
children,
ref = $bindable(),
...props
}: Props = $props();
const gltf = useGltf("/models/moon.glb", { dracoLoader: useDraco() });
</script>
<T.Group bind:ref dispose={false} {...props}>
{#await gltf}
{@render fallback?.()}
{:then gltf}
<T.Mesh geometry={gltf.nodes.Moon.geometry} material={gltf.materials.DD9944}
><T.PointLight position={[0.5, 5, 0.5]} color="#a2cbf5" intensity={7}
></T.PointLight>
</T.Mesh>
{:catch err}
{@render error?.({ error: err })}
{/await}
{@render children?.({ ref })}
</T.Group>

View File

@@ -0,0 +1,117 @@
<script lang="ts">
import { theme } from "$lib/stores/theme_store";
import { T, useTask } from "@threlte/core";
import { backInOut } from "svelte/easing";
import { Tween } from "svelte/motion";
import Marker from "./marker.svelte";
import Moon from "./moon.svelte";
import Sun from "./sun.svelte";
import { useViewport } from "@threlte/extras";
import { innerHeight, innerWidth } from "svelte/reactivity/window";
const viewport = useViewport();
const canvasHeight = $derived((innerHeight.current ?? 0) - 112);
const canvasWidth = $derived((innerWidth.current ?? 0) * 0.6);
const dioramaImageMaxHeight = 0.75;
const imgWidth = $derived(
Math.min(
((dioramaImageMaxHeight * canvasHeight) / 9) * 16,
canvasWidth,
),
);
const imgHeight = $derived((imgWidth / 16) * 9);
const imgWorldWidth = $derived((imgWidth / canvasWidth) * $viewport.width);
const imgWorldHeight = $derived(
(imgHeight / canvasHeight) * $viewport.height,
);
const peak1PositionRelativeToImage = [-0.195, 0.7];
const peak1Position = $derived([
peak1PositionRelativeToImage[0] * (imgWorldWidth / 2),
peak1PositionRelativeToImage[1] * (imgWorldHeight / 2),
20,
]);
const peak2PositionRelativeToImage = [0.05, 1];
const peak2Position = $derived([
peak2PositionRelativeToImage[0] * (imgWorldWidth / 2),
peak2PositionRelativeToImage[1] * (imgWorldHeight / 2),
20,
]);
const peak3PositionRelativeToImage = [0.55, 0.37];
const peak3Position = $derived([
peak3PositionRelativeToImage[0] * (imgWorldWidth / 2),
peak3PositionRelativeToImage[1] * (imgWorldHeight / 2),
20,
]);
const sunUpPosition = $derived(($viewport.height / 2) * 0.6);
let rotation = $state(0);
useTask((delta) => {
rotation += delta / 2;
});
const sunZPosition = new Tween($theme == "light" ? sunUpPosition : 0, {
duration: 1000,
easing: backInOut,
});
$effect(() => {
sunZPosition.set($theme == "light" ? sunUpPosition : 0);
});
const sunLightIntensity = new Tween($theme == "light" ? 4 : 0, {
duration: 400,
});
$effect(() => {
sunLightIntensity.set($theme == "light" ? 4 : 0);
});
const moonZPosition = new Tween($theme == "dark" ? sunUpPosition : 0, {
duration: 1000,
easing: backInOut,
});
$effect(() => {
moonZPosition.set($theme == "dark" ? sunUpPosition : 0);
});
</script>
<T.OrthographicCamera
makeDefault
zoom={14}
position={[0, 0, 30]}
oncreate={(c) => {
c.lookAt(0, 0, 0);
}}
/>
<!-- <Earth {rotation}></Earth> -->
<!-- <Mountain {rotation}></Mountain> -->
<Sun
position={[7.5, sunZPosition.current, 0]}
rotation={[0, 0, 0]}
scale={0.11}
lightIntesity={sunLightIntensity.current}
/>
<Moon
position={[-5, moonZPosition.current, 0]}
rotation={[0, -Math.PI / 2, 0]}
scale={0.53}
></Moon>
<Marker position={peak1Position} rotation={[0, rotation, 0]} scale={1}></Marker>
<Marker position={peak2Position} rotation={[0, rotation - 1, 0]} scale={1}
></Marker>
<Marker
position={peak3Position}
rotation={[0, rotation + 1, 0]}
scale={1}
></Marker>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,139 @@
<script lang="ts">
import starLayers from "./star_postions.json";
</script>
<div class="stars-wrapper">
{#each starLayers as layer, i}
<svg
class="stars layer-{i}"
width="100%"
height="100%"
preserveAspectRatio="none"
>
{#each layer as star}
<circle cx="{star.x}%" cy="{star.y}%" r={star.r} fill="white" />
{/each}
</svg>
{/each}
<svg class="extras" width="100%" height="100%" preserveAspectRatio="none">
<defs>
<radialGradient id="comet-gradient" cx="0" cy=".5" r="0.5">
<stop offset="0%" stop-color="rgba(255,255,255,.8)" />
<stop offset="100%" stop-color="rgba(255,255,255,0)" />
</radialGradient>
</defs>
<g transform="rotate(-135)">
<ellipse
class="comet comet-a"
fill="url(#comet-gradient)"
cx="0"
cy="0"
rx="150"
ry="2"
/>
</g>
<g transform="rotate(20)">
<ellipse
class="comet comet-b"
fill="url(#comet-gradient)"
cx="100%"
cy="0"
rx="150"
ry="2"
/>
</g>
<g transform="rotate(300)">
<ellipse
class="comet comet-c"
fill="url(#comet-gradient)"
cx="40%"
cy="100%"
rx="150"
ry="2"
/>
</g>
</svg>
</div>
<style>
.stars-wrapper {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
transition:
opacity 1.5s ease-in-out,
visibility 1.5s;
will-change: opacity;
}
.visible {
opacity: 1;
visibility: visible;
}
.hidden {
opacity: 0;
visibility: hidden;
}
.stars {
position: absolute;
inset: 0;
/* Force GPU to handle the transparency fade */
transform: translateZ(0);
}
/* Your working twinkle animation from before */
.layer-0 {
animation: twinkle 4s infinite -0s;
}
.layer-1 {
animation: twinkle 4s infinite -1.3s;
}
.layer-2 {
animation: twinkle 4s infinite -2.6s;
}
@keyframes twinkle {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.3;
}
}
.comet {
animation: comet 10s linear infinite;
}
@keyframes comet {
0% {
transform: translateX(0);
opacity: 0;
}
1% {
opacity: 1;
}
20% {
transform: translateX(-2000px);
opacity: 0;
}
100% {
transform: translateX(-2000px);
opacity: 0;
}
}
.comet-b {
animation-delay: -3.3s;
}
.comet-c {
animation-delay: -5s;
}
</style>

View File

@@ -0,0 +1,72 @@
<!--
Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf
Command: npx @threlte/gltf@3.0.7 ../../../../static/models/sun.glb -T
-->
<script lang="ts">
import { T } from "@threlte/core";
import { useDraco, useGltf } from "@threlte/extras";
import type { Snippet } from "svelte";
interface Props {
lightIntesity?: number;
fallback?: Snippet;
error?: Snippet<[any]>;
children?: Snippet<[any]>;
[key: string]: any;
}
let {
lightIntesity = 7,
fallback,
error,
children,
ref = $bindable(),
...props
}: Props = $props();
const gltf = useGltf("/models/sun.glb", { dracoLoader: useDraco() });
</script>
<T.Group bind:ref dispose={false} {...props}>
{#await gltf}
{@render fallback?.()}
{:then gltf}
<T.Mesh
geometry={gltf.nodes.GeoSphere003_1.geometry}
material={gltf.materials._Sol02___Default}
/>
<T.Mesh
geometry={gltf.nodes.GeoSphere003_1_1.geometry}
material={gltf.materials._Sol01___Default}
></T.Mesh>
<T.SpotLight
decay={0}
position={[20, 45, 80]}
target={ref}
intensity={lightIntesity}
></T.SpotLight>
<T.SpotLight
decay={0}
position={[0, 65, 80]}
target={ref}
intensity={lightIntesity}
></T.SpotLight>
<T.SpotLight
decay={0}
position={[-20, 45, 80]}
target={ref}
intensity={lightIntesity}
></T.SpotLight>
<T.SpotLight
decay={0}
position={[0, 25, 80]}
target={ref}
intensity={lightIntesity}
></T.SpotLight>
{:catch err}
{@render error?.({ error: err })}
{/await}
{@render children?.({ ref })}
</T.Group>

View File

@@ -91,6 +91,7 @@
options.HTMLAttributes, options.HTMLAttributes,
), ),
options.renderText?.({ options.renderText?.({
suggestion: {...options.suggestion, editor: editor!},
options: options, options: options,
node, node,
}), }),

View File

@@ -1,61 +0,0 @@
<script lang="ts">
import { theme } from "$lib/stores/theme_store";
import { T, useTask } from "@threlte/core";
import { onMount } from "svelte";
import { Tween } from "svelte/motion";
import * as THREE from "three";
import Mountain from "./mountain.svelte";
let rotation = $state(0);
useTask((delta) => {
rotation += delta / 2;
});
const ambientIntensity = new Tween($theme == "light" ? 3 : 0, {
duration: 500,
});
let ambientColor = $derived($theme == "light" ? "#ffffff" : "#4c7fe6");
$effect(() => {
ambientIntensity.set($theme == "light" ? 3 : 0.1);
});
let starsGeometry: THREE.BufferGeometry | undefined = $state();
let starsMaterial: THREE.PointsMaterial | undefined = $state();
onMount(() => {
const stars = new Array(0);
for (var i = 0; i < 500; i++) {
let x = THREE.MathUtils.randFloatSpread(500);
let y = THREE.MathUtils.randFloatSpread(500);
let z = THREE.MathUtils.randFloatSpread(500);
stars.push(x, y, z);
}
starsGeometry = new THREE.BufferGeometry();
starsGeometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(stars, 3),
);
starsMaterial = new THREE.PointsMaterial({ color: 0xffffff });
});
</script>
<T.PerspectiveCamera
makeDefault
position={[35.5, 28, 35.5]}
oncreate={(c) => {
c.lookAt(-17, 0, 12);
}}
/>
{#if starsGeometry && starsMaterial}
<T.Points geometry={starsGeometry} material={starsMaterial}></T.Points>
{/if}
<!-- <Earth {rotation}></Earth> -->
<Mountain {rotation}></Mountain>
<T.HemisphereLight
skyColor={ambientColor}
groundColor="#242734"
intensity={ambientIntensity.current}
/>

View File

@@ -8,7 +8,7 @@
} from "$lib/components/base/search.svelte"; } from "$lib/components/base/search.svelte";
import EmptyStateFeed from "$lib/components/empty_states/empty_state_feed.svelte"; import EmptyStateFeed from "$lib/components/empty_states/empty_state_feed.svelte";
import FeedCard from "$lib/components/profile/feed_card.svelte"; import FeedCard from "$lib/components/profile/feed_card.svelte";
import Scene from "$lib/components/scene.svelte"; import Scene from "$lib/components/3D/scene.svelte";
import TrailCard from "$lib/components/trail/trail_card.svelte"; import TrailCard from "$lib/components/trail/trail_card.svelte";
import { import {
defaultTrailSearchAttributes, defaultTrailSearchAttributes,
@@ -27,6 +27,8 @@
import { marked } from "marked"; import { marked } from "marked";
import { onMount, untrack } from "svelte"; import { onMount, untrack } from "svelte";
import { _ } from "svelte-i18n"; import { _ } from "svelte-i18n";
import StarSky from "$lib/components/3D/star_sky.svelte";
import { fade } from "svelte/transition";
let { data } = $props(); let { data } = $props();
@@ -153,14 +155,14 @@
<svelte:window onscroll={onScroll} /> <svelte:window onscroll={onScroll} />
<section <section
class="hero grid grid-cols-1 lg:grid-cols-2 md:px-8 gap-4 md:gap-8" class="hero grid grid-cols-1 lg:grid-cols-2 lg:px-8 gap-4 lg:gap-8"
style="min-height: calc(100vh - 112px)" style="min-height: calc(100vh - 112px)"
> >
<div <div
class="flex flex-col justify-center gap-8 px-8 md:px-24 mt-0 lg:sticky" class="flex flex-col justify-center gap-8 px-8 lg:px-24 mt-0 lg:sticky"
style="max-height: calc(100vh - 112px); top: 112px;" style="max-height: calc(100vh - 112px); top: 112px;"
> >
<h2 class="text-5xl sm:text-6xl md:text-7xl font-bold"> <h2 class="text-5xl sm:text-6xl lg:text-7xl font-bold">
{$_("welcome_to")} <span class="-tracking-[0.075em]">wanderer</span> {$_("welcome_to")} <span class="-tracking-[0.075em]">wanderer</span>
</h2> </h2>
<h5> <h5>
@@ -190,16 +192,34 @@
</section> </section>
{#if !page.data.user} {#if !page.data.user}
<div <div
class="hidden md:block w-full fixed top-[112px] -z-10" class="hidden lg:block w-full fixed top-[112px] -z-10"
style="min-height: calc(100vh - 112px)" style="min-height: calc(100vh - 112px)"
> >
<Canvas toneMapping={0}> <div class="invisible dark:visible">
<Scene></Scene> <StarSky></StarSky>
</Canvas> </div>
<div class="absolute h-full w-[60%] left-[40%]">
<Canvas toneMapping={0}>
<Scene></Scene>
</Canvas>
{#if $theme === "light"}
<enhanced:img
class="absolute top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2 max-h-3/4"
src="/static/models/wanderer_diorama_light.png"
alt="wanderer Diorama"
/>
{:else}
<enhanced:img
class="absolute top-1/2 -translate-y-1/2 left-1/2 -translate-x-1/2 max-h-3/4"
src="/static/models/wanderer_diorama_dark.png"
alt="wanderer Diorama"
/>
{/if}
</div>
</div> </div>
<section class="md:px-8 md:max-w-1/2 mb-24" id="about"> <section class="lg:px-8 lg:max-w-1/2 mb-24" id="about">
<div class="px-8 md:px-24"> <div class="px-8 lg:px-24">
<h2 class="text-4xl md:text-5xl font-bold mt-1 mb-8"> <h2 class="text-4xl lg:text-5xl font-bold mt-1 mb-8">
{$_("about")} {$_("about")}
</h2> </h2>
<div class="prose dark:prose-invert"> <div class="prose dark:prose-invert">
@@ -207,9 +227,9 @@
</div> </div>
</div> </div>
</section> </section>
<section class="md:px-8 md:max-w-1/2 mb-24" id="trails"> <section class="lg:px-8 lg:max-w-1/2 mb-24" id="trails">
<div class="px-8 md:px-24 space-y-4"> <div class="px-8 lg:px-24 space-y-4">
<h2 class="text-4xl md:text-5xl font-bold"> <h2 class="text-4xl lg:text-5xl font-bold">
{$_("explore-some-trails")} {$_("explore-some-trails")}
</h2> </h2>
<h5> <h5>
@@ -243,9 +263,9 @@
{/if} {/if}
</div> </div>
</section> </section>
<section id="get-started" class="md:px-8 md:max-w-1/2 mb-24"> <section id="get-started" class="lg:px-8 lg:max-w-1/2 mb-24">
<div class="px-8 md:px-24 space-y-4 text-center"> <div class="px-8 lg:px-24 space-y-4 text-center">
<h2 class="text-4xl md:text-5xl font-bold">{$_("get-started")}</h2> <h2 class="text-4xl lg:text-5xl font-bold">{$_("get-started")}</h2>
<p>{$_("ready-to-join")}?</p> <p>{$_("ready-to-join")}?</p>
<a <a
class="inline-block btn-primary btn-large" class="inline-block btn-primary btn-large"

View File

@@ -2,7 +2,7 @@
"openapi": "3.0.3", "openapi": "3.0.3",
"info": { "info": {
"title": "Wanderer API", "title": "Wanderer API",
"version": "v0.18.5", "version": "0.18.5",
"description": "API documentation for wanderer backend" "description": "API documentation for wanderer backend"
}, },
"servers": [], "servers": [],

Binary file not shown.

BIN
web/static/models/moon.glb Normal file

Binary file not shown.

Binary file not shown.

BIN
web/static/models/sun.glb Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -1,13 +1,14 @@
import tailwindcss from '@tailwindcss/vite'; import { enhancedImages } from '@sveltejs/enhanced-img';
import { sveltekit } from '@sveltejs/kit/vite'; import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config'; import tailwindcss from '@tailwindcss/vite';
import openapiPlugin from 'sveltekit-openapi-generator';
import fs from 'fs'; import fs from 'fs';
import openapiPlugin from 'sveltekit-openapi-generator';
import { defineConfig } from 'vitest/config';
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf-8')); const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf-8'));
export default defineConfig({ export default defineConfig({
plugins: [openapiPlugin({ plugins: [enhancedImages(), openapiPlugin({
info: { info: {
title: 'Wanderer API', title: 'Wanderer API',
version: `${packageJson.version}`, version: `${packageJson.version}`,
@@ -16,7 +17,7 @@ export default defineConfig({
outputPath: 'static/docs/api/wanderer.openapi.json', outputPath: 'static/docs/api/wanderer.openapi.json',
include: ['src/routes/api/v1/**/*.{js,ts}'], include: ['src/routes/api/v1/**/*.{js,ts}'],
baseSchemasPath: 'src/lib/models/api/openapi_schemas.ts', baseSchemasPath: 'src/lib/models/api/openapi_schemas.ts',
}),tailwindcss(), sveltekit()], }), tailwindcss(), sveltekit()],
test: { include: ['src/**/*.{test,spec}.{js,ts}'] }, test: { include: ['src/**/*.{test,spec}.{js,ts}'] },
ssr: { noExternal: ['three'] }, ssr: { noExternal: ['three'] },
...(process.env.WANDERER_ENV == "dev" ? { ...(process.env.WANDERER_ENV == "dev" ? {