From 752041a375889849ebd295862099705636f1445e Mon Sep 17 00:00:00 2001 From: noe Date: Mon, 15 Jul 2024 14:13:23 -0400 Subject: [PATCH] sync --- .envrc | 1 + .gitignore | 1 + app/components/alert-timer.tsx | 6 +- app/components/faction-bar-sxs.css.ts | 15 + app/components/faction-bar-sxs.tsx | 41 + app/components/faction-bar.css.ts | 13 +- app/components/faction-bar.tsx | 2 +- app/components/faction-pie.tsx | 10 +- app/components/footer.css.ts | 2 +- app/components/index-world-container.tsx | 4 +- app/components/index-world.tsx | 20 +- app/components/world-zone-container.css.ts | 58 + app/components/world-zone-container.tsx | 98 + app/components/world.css.ts | 1 - app/entry.client.tsx | 18 - app/entry.server.tsx | 38 - app/images/icon_engi.png | Bin 0 -> 1065 bytes app/images/icon_heavy.png | Bin 0 -> 844 bytes app/images/icon_infil.png | Bin 0 -> 1008 bytes app/images/icon_light.png | Bin 0 -> 1278 bytes app/images/icon_max.png | Bin 0 -> 902 bytes app/images/icon_medic.png | Bin 0 -> 266 bytes app/images/vehicles/ant.png | Bin 0 -> 231 bytes app/images/vehicles/chimera.png | Bin 0 -> 2188 bytes app/images/vehicles/corsair.png | Bin 0 -> 375 bytes app/images/vehicles/dervish.png | Bin 0 -> 2841 bytes app/images/vehicles/flash.png | Bin 0 -> 429 bytes app/images/vehicles/galaxy.png | Bin 0 -> 466 bytes app/images/vehicles/harasser.png | Bin 0 -> 505 bytes app/images/vehicles/javelin.png | Bin 0 -> 361 bytes app/images/vehicles/liberator.png | Bin 0 -> 505 bytes app/images/vehicles/lightning.png | Bin 0 -> 505 bytes app/images/vehicles/magrider.png | Bin 0 -> 585 bytes app/images/vehicles/mosquito.png | Bin 0 -> 429 bytes app/images/vehicles/prowler.png | Bin 0 -> 554 bytes app/images/vehicles/reaver.png | Bin 0 -> 467 bytes app/images/vehicles/scythe.png | Bin 0 -> 535 bytes app/images/vehicles/sunderer.png | Bin 0 -> 511 bytes app/images/vehicles/valkyrie.png | Bin 0 -> 555 bytes app/images/vehicles/vanguard.png | Bin 0 -> 488 bytes app/root.tsx | 13 +- app/routes/_index.tsx | 14 +- app/routes/about.tsx | 4 +- app/routes/debug.components.tsx | 11 +- app/routes/worlds.$id.tsx | 56 +- app/utils/class-icons.ts | 17 + app/utils/saerro.ts | 12 +- app/utils/sorting.ts | 22 + app/utils/theme.ts | 8 + app/utils/vehicle-icons.ts | 60 + build/index.js | 1904 +++ build/index.js.map | 7 + build/metafile.css.json | 1 + build/metafile.js.json | 1 + build/metafile.server.json | 1 + build/version.txt | 1 + bun.lockb | Bin 0 -> 341358 bytes flake.lock | 58 + flake.nix | 23 + hack/download-vehicle-icons.mjs | 42 + package-lock.json | 12214 ++++++++----------- package.json | 52 +- remix.config.js | 23 - remix.env.d.ts | 2 - server.mjs | 26 + server.ts | 8 - shell.nix | 8 + tsconfig.json | 3 - vite.config.js | 7 + wrangler.toml | 1 - 70 files changed, 7484 insertions(+), 7443 deletions(-) create mode 100644 .envrc create mode 100644 app/components/faction-bar-sxs.css.ts create mode 100644 app/components/faction-bar-sxs.tsx create mode 100644 app/components/world-zone-container.css.ts create mode 100644 app/components/world-zone-container.tsx delete mode 100644 app/entry.client.tsx delete mode 100644 app/entry.server.tsx create mode 100644 app/images/icon_engi.png create mode 100644 app/images/icon_heavy.png create mode 100644 app/images/icon_infil.png create mode 100644 app/images/icon_light.png create mode 100644 app/images/icon_max.png create mode 100644 app/images/icon_medic.png create mode 100644 app/images/vehicles/ant.png create mode 100644 app/images/vehicles/chimera.png create mode 100644 app/images/vehicles/corsair.png create mode 100644 app/images/vehicles/dervish.png create mode 100644 app/images/vehicles/flash.png create mode 100644 app/images/vehicles/galaxy.png create mode 100644 app/images/vehicles/harasser.png create mode 100644 app/images/vehicles/javelin.png create mode 100644 app/images/vehicles/liberator.png create mode 100644 app/images/vehicles/lightning.png create mode 100644 app/images/vehicles/magrider.png create mode 100644 app/images/vehicles/mosquito.png create mode 100644 app/images/vehicles/prowler.png create mode 100644 app/images/vehicles/reaver.png create mode 100644 app/images/vehicles/scythe.png create mode 100644 app/images/vehicles/sunderer.png create mode 100644 app/images/vehicles/valkyrie.png create mode 100644 app/images/vehicles/vanguard.png create mode 100644 app/utils/class-icons.ts create mode 100644 app/utils/theme.ts create mode 100644 app/utils/vehicle-icons.ts create mode 100644 build/index.js create mode 100644 build/index.js.map create mode 100644 build/metafile.css.json create mode 100644 build/metafile.js.json create mode 100644 build/metafile.server.json create mode 100644 build/version.txt create mode 100755 bun.lockb create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 hack/download-vehicle-icons.mjs delete mode 100644 remix.config.js create mode 100644 server.mjs delete mode 100644 server.ts create mode 100644 shell.nix create mode 100644 vite.config.js delete mode 100644 wrangler.toml diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..44610e5 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake; diff --git a/.gitignore b/.gitignore index c523f8d..f7852a6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ node_modules .DS_Store **/.DS_Store +.direnv diff --git a/app/components/alert-timer.tsx b/app/components/alert-timer.tsx index ac035e1..b0b37e6 100644 --- a/app/components/alert-timer.tsx +++ b/app/components/alert-timer.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import type { MetagameWorld } from "~/utils/metagame"; -import { humanTimeAgo } from "~/utils/strings"; +import type { MetagameWorld } from "../utils/metagame"; +import { humanTimeAgo } from "../utils/strings"; import * as styles from "./alert-timer.css"; const endTime = (alert: Required["alert"]) => { @@ -51,7 +51,7 @@ export const AlertTimer = ({ }: { alert: MetagameWorld["zones"][0]["alert"]; }) => { - const [timeLeft, setTimeLeft] = useState(timeLeftString(alert)); + const [timeLeft, setTimeLeft] = useState(<>s); useEffect(() => { if (alert) { diff --git a/app/components/faction-bar-sxs.css.ts b/app/components/faction-bar-sxs.css.ts new file mode 100644 index 0000000..a0b40cb --- /dev/null +++ b/app/components/faction-bar-sxs.css.ts @@ -0,0 +1,15 @@ +import { style } from "@vanilla-extract/css"; +import { background100, background200 } from "../utils/theme"; + +export const bar = style({ + backgroundColor: background200, + width: "0.3em", + height: "1em", + border: `1px solid ${background100}`, + // margin: 2 +}); + +export const container = style({ + display: "flex", +}); + diff --git a/app/components/faction-bar-sxs.tsx b/app/components/faction-bar-sxs.tsx new file mode 100644 index 0000000..5d8ff34 --- /dev/null +++ b/app/components/faction-bar-sxs.tsx @@ -0,0 +1,41 @@ +import { useMemo } from "react"; +import { Population } from "../utils/saerro"; +import * as styles from "./faction-bar-sxs.css"; +import { background200, ncFaction, trFaction, vsFaction } from "../utils/theme"; + +export const FactionBarSxS = ({ + population: { nc, vs, tr }, +}: { + population: Population; +}) => { + const { vsPercent, ncPercent, trPercent } = useMemo(() => { + const total = nc + vs + tr; + return { + vsPercent: Math.round((vs / total) * 100) || 0, + ncPercent: Math.round((nc / total) * 100) || 0, + trPercent: Math.round((tr / total) * 100) || 0, + }; + }, [vs, nc, tr]); + + return ( +
+ + + +
+ ); +}; + +const Bar = (props: { percent: number; color: string }) => ( +
+   +
+); diff --git a/app/components/faction-bar.css.ts b/app/components/faction-bar.css.ts index ef2609c..e3055f6 100644 --- a/app/components/faction-bar.css.ts +++ b/app/components/faction-bar.css.ts @@ -1,5 +1,6 @@ import type { ComplexStyleRule } from "@vanilla-extract/css"; import { style } from "@vanilla-extract/css"; +import { edge, ncFaction, trFaction, vsFaction } from "../utils/theme"; export const bar = style({ display: "flex", @@ -8,7 +9,7 @@ export const bar = style({ flexDirection: "row", overflow: "hidden", borderRadius: "0.4rem", - border: "2px solid #4d4d4d", + border: `2px solid ${edge}`, }); export const tinyBar = style({ @@ -27,16 +28,16 @@ const shared: ComplexStyleRule = { export const left = style({ ...shared, - backgroundColor: "#991cba", + backgroundColor: vsFaction, }); export const center = style({ ...shared, - backgroundColor: "#1564cc", - borderLeft: "1px solid #4d4d4d", - borderRight: "2px solid #4d4d4d", + backgroundColor: ncFaction, + borderLeft: `1px solid ${edge}`, + borderRight: `2px solid ${edge}`, boxShadow: "inset 0 0 0.5rem rgb(180 180 180 / 10%)", }); export const right = style({ ...shared, - backgroundColor: "#d30101", + backgroundColor: trFaction, }); diff --git a/app/components/faction-bar.tsx b/app/components/faction-bar.tsx index 727c65c..1eb7d2a 100644 --- a/app/components/faction-bar.tsx +++ b/app/components/faction-bar.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import type { Population } from "~/utils/saerro"; +import type { Population } from "../utils/saerro"; import * as styles from "./faction-bar.css"; export const FactionBar = ({ diff --git a/app/components/faction-pie.tsx b/app/components/faction-pie.tsx index 2d9ed32..4298994 100644 --- a/app/components/faction-pie.tsx +++ b/app/components/faction-pie.tsx @@ -1,4 +1,5 @@ -import type { Population } from "~/utils/saerro"; +import { background200, ncFaction, trFaction, vsFaction } from "../utils/theme"; +import type { Population } from "../utils/saerro"; import { pieRoot } from "./faction-pie.css"; export const FactionPie = ({ @@ -24,10 +25,11 @@ export const FactionPie = ({ style={ { fontSize: size || "1em", + backgroundColor: background200, backgroundImage: `conic-gradient( - #d30101 0% ${trPct}%, - #991cba ${trPct}% ${trPct + vsPct}%, - #1564cc ${trPct + vsPct}% 100% + ${trFaction} 0% ${trPct}%, + ${vsFaction} ${trPct}% ${trPct + vsPct}%, + ${ncFaction} ${trPct + vsPct}% 100% )`, "--inner-margin": innerMargin ? `${innerMargin}px` : "0", "--inner-bg": innerBackground || "none", diff --git a/app/components/footer.css.ts b/app/components/footer.css.ts index f652d2e..35177b7 100644 --- a/app/components/footer.css.ts +++ b/app/components/footer.css.ts @@ -1,5 +1,5 @@ import { style } from "@vanilla-extract/css"; -import footer from "~/images/footer.jpg"; +import footer from "../images/footer.jpg"; export const root = style({ height: 300, diff --git a/app/components/index-world-container.tsx b/app/components/index-world-container.tsx index 44ae6f0..32d6b11 100644 --- a/app/components/index-world-container.tsx +++ b/app/components/index-world-container.tsx @@ -1,7 +1,7 @@ import { IndexWorld } from "./index-world"; import * as styles from "./index-world-container.css"; -import type { MetagameWorld } from "~/utils/metagame"; -import type { PopulationWorld } from "~/utils/population"; +import type { MetagameWorld } from "../utils/metagame"; +import type { PopulationWorld } from "../utils/population"; export const WorldContainer = ({ metagame, diff --git a/app/components/index-world.tsx b/app/components/index-world.tsx index 31a61f3..54c2548 100644 --- a/app/components/index-world.tsx +++ b/app/components/index-world.tsx @@ -1,19 +1,13 @@ import { Link } from "@remix-run/react"; -import { - humanTimeAgo, - snakeCaseToTitleCase, - worlds, - zones, -} from "~/utils/strings"; +import { snakeCaseToTitleCase, worlds, zones } from "../utils/strings"; import * as styles from "./index-world.css"; -import vsLogo from "~/images/vs-100.png"; -import ncLogo from "~/images/nc-100.png"; -import trLogo from "~/images/tr-100.png"; +import vsLogo from "../images/vs-100.png"; +import ncLogo from "../images/nc-100.png"; +import trLogo from "../images/tr-100.png"; import { FactionBar } from "./faction-bar"; -import type { MetagameWorld } from "~/utils/metagame"; -import type { PopulationWorld } from "~/utils/population"; -import { c } from "~/utils/classes"; -import { useEffect, useState } from "react"; +import type { MetagameWorld } from "../utils/metagame"; +import type { PopulationWorld } from "../utils/population"; +import { c } from "../utils/classes"; import { AlertTimer } from "./alert-timer"; export type IndexWorldProps = { diff --git a/app/components/world-zone-container.css.ts b/app/components/world-zone-container.css.ts new file mode 100644 index 0000000..9dd9e8d --- /dev/null +++ b/app/components/world-zone-container.css.ts @@ -0,0 +1,58 @@ +import { style } from "@vanilla-extract/css"; + +export const zoneContainer = style({ + margin: "0.5em 1em", + backgroundColor: "#222", + padding: "1em", + boxSizing: "border-box", +}); + +export const zoneHeader = style({ + display: "flex", +}); + +export const chartTileTotal = style({ + textAlign: "center", + lineHeight: 1, + minWidth: "2em", + maxWidth: "2em", + fontSize: "3rem", + overflowY: "hidden", +}); + +export const chartTilePopLine = style({ + display: "flex", + fontSize: "1.5rem", + fontWeight: "bold", + alignItems: "center", + justifyContent: "center", + lineHeight: 1.1, + margin: 0, +}); + +export const chartTilePopImage = style({ + width: "1em", + // height: "1em", + marginRight: 4, +}); + +export const chartTile = style({ + fontSize: "4rem", + flex: "0 3 33%", + display: "flex", + marginTop: "0.2em", +}); + +export const classesContainer = style({ + display: "flex", + flex: "1 3 100%", + flexWrap: "wrap", + justifyContent: "space-evenly", +}); + +export const chartTileChart = style({ + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", +}); diff --git a/app/components/world-zone-container.tsx b/app/components/world-zone-container.tsx new file mode 100644 index 0000000..ac3c748 --- /dev/null +++ b/app/components/world-zone-container.tsx @@ -0,0 +1,98 @@ +import { + allClasses, + Population, + totalPopulation, + World, + Zone, +} from "../utils/saerro"; +import { headerFont } from "./world.css"; +import { + chartTile, + chartTileChart, + chartTilePopImage, + chartTilePopLine, + chartTileTotal, + classesContainer, + zoneContainer, + zoneHeader, +} from "./world-zone-container.css"; +import { classIconMap } from "../utils/class-icons"; +import { FactionBarSxS } from "./faction-bar-sxs"; +import { c } from "../utils/classes"; +import vsLogo from "../images/vs-100.png"; +import ncLogo from "../images/nc-100.png"; +import trLogo from "../images/tr-100.png"; + +export type WZCProps = { + world: World; + zone: Zone; +}; + +export const WorldZoneContainer = (props: WZCProps) => { + return ( +
+
+

{props.zone.name.toUpperCase()}

+ {/* TODO: metagame */} +
+
+

CLASSES

+
+ +
+   +
+
+   +
+
+
+
+ ); +}; + +const Classes = (props: { classes: Zone["classes"] }) => { + if (props.classes === undefined) { + return null; + } + + return ( +
+ {allClasses.map((name) => ( + )[name]} + /> + ))} +
+ ); +}; + +const ChartTile = (props: { name: string; pop: Population }) => ( +
+
+ + +
+
+
+ {totalPopulation(props.pop)} +
+
+
+ VS{" "} + {props.pop.vs} +
+
+ NC{" "} + {props.pop.nc} +
+
+ TR{" "} + {props.pop.tr} +
+
+
+
+); diff --git a/app/components/world.css.ts b/app/components/world.css.ts index edd9208..31d8909 100644 --- a/app/components/world.css.ts +++ b/app/components/world.css.ts @@ -28,7 +28,6 @@ export const headerSub = style({ export const outer = style({ display: "flex", - justifyContent: "center", minHeight: "100vh", flexDirection: "column", maxWidth: "1920px", diff --git a/app/entry.client.tsx b/app/entry.client.tsx deleted file mode 100644 index 94d5dc0..0000000 --- a/app/entry.client.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/** - * By default, Remix will handle hydrating your app on the client for you. - * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ - * For more information, see https://remix.run/file-conventions/entry.client - */ - -import { RemixBrowser } from "@remix-run/react"; -import { startTransition, StrictMode } from "react"; -import { hydrateRoot } from "react-dom/client"; - -startTransition(() => { - hydrateRoot( - document, - - - - ); -}); diff --git a/app/entry.server.tsx b/app/entry.server.tsx deleted file mode 100644 index 7fd5b88..0000000 --- a/app/entry.server.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/** - * By default, Remix will handle generating the HTTP Response for you. - * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ - * For more information, see https://remix.run/file-conventions/entry.server - */ - -import type { EntryContext } from "@remix-run/cloudflare"; -import { RemixServer } from "@remix-run/react"; -import isbot from "isbot"; -import { renderToReadableStream } from "react-dom/server"; - -export default async function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext -) { - const body = await renderToReadableStream( - , - { - signal: request.signal, - onError(error: unknown) { - console.error(error); - responseStatusCode = 500; - }, - } - ); - - if (isbot(request.headers.get("user-agent"))) { - await body.allReady; - } - - responseHeaders.set("Content-Type", "text/html"); - return new Response(body, { - headers: responseHeaders, - status: responseStatusCode, - }); -} diff --git a/app/images/icon_engi.png b/app/images/icon_engi.png new file mode 100644 index 0000000000000000000000000000000000000000..8a31388cb3c8916c6a4f708af31793cd58fb886f GIT binary patch literal 1065 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE5m!8OOlkTgZF5$(++PBVCUu;0Rht;lE!KEL zZ9{)+Q|j6%mPs8UYKJVg7eBPAvbnd%^7*~*>%RBO{m_?rez*GH{>t}v-y12q+^o75 z6&A;7)xIhwpJB;M&5!oWK5U-EUT`P*j&$&kxs#X+I85|j7&cx%BIm&M#bw2DgK2`+ z3`Q@d7tFcPFEedw7jwgs(-nOT4^&r4G|b_4x#Y%Rx}bXj>!bC*#IC&BwNKQ3@e!tL zp~=h*T+2T4%RP?U`l#GOLF*dVfyfIchbv|Umd#>Jk}5W2Ye@d1t+ATXvOCp_?I*)qKgxM?Uf{o5&Qa>U>s?<%tk z)vJ%~TA|Q(fm^a6xu!GVNB)Hc%L;Ftsr~XU*WAx8C=1Y6 zQ@;Ioe#AYl6~2NG^mN|yE57%OWvFC*e7$q~s+I!B$9_c)PZl$M5V>-~XO;89_w}xy zWiNK@Y~xxGZ#Ywz@g&R9#R6%azYen)ESTtF`C=MV%k>4h6?>zvRI%qUDO_F5@KAci zz7U2f$@251<81_zcpYAGys&0Ipry5q;ZMVr$NIC=8*Ut4b6lc9r})V8ebRA;3??@? z9&A{xT_wH6^WnrF-Yge#lpEsauUx0X>|pzt&mv8^p^vGUk-funn=!+jO?bBz~fbyb;5k?OR1Pz6n*gB{O|D<>h!T&g`&#Ekjzz=0oZZ*B$1I zhIi=Q=1TCH!I{lyW5D9DPA`9}pu>Cv@y8Y}+qfL28%S?naCaMTTV52$!5sWj^DWY zXGKXB>-XJberv$+i1qT0&E|%ijt0KxKF-pydY#q%W#J9xfk%cSDbbc*d?u^ zVSVh99k=ovUZK-3R`xL+;BFEM%$xYpZ*lSV6F;PuGZf@cl1gouklb`uknu&LguG~r z?DIRhMU#)5s&C;qWN7ETraH)A+pX0qR~+ZCi&*@he$ill;u*zr33~)oqg6j)KTKt zdDWQEIXW4a)gE1Pi`in>T=3XU=@);IFjrl`7H)CRn4*VXL4J}QJ2p)9{@&E3pv&Z2 z@YIZhYq3Cc^kwTWauRJnt%}d>G`{xx(B|rMXMX=VcRlsoofgAsf6jYxH2gm(?ijq_ z=z&>>;;gtD{CAvvv@$oI!|vhw=CX*It<8I{axIWAIrB*O-tD)$7eGdaHhyULB@tQmPXOW1=1xa3-+&nyo8sP!S1BSi#f@k%N@&Q_ST$n?@~z8 zf8DlkdKW{)RUV_{j1BBx7;B{$XiswxULe09e}Q55_f-Y1``GUqRHU2iT|bT0Af2;b z>!-c6!k)mM`I3j~r|wY~<0vj=FxdO*UgnC`=iaE=d@lN^tr+@4-86Z9MBGGK)`C@; zMb5=9H!~e5;eR}N2E&Ui|A(^L90}(is57uMFdX4ch<_++_`_Y~-`~v)4l#?WbXP3> zz}Ivy%Vfoe?Mn=g?p(wuF#Cnrf_4Q)1&{`ThF^>^hvijv{c7Ttect-?t8Q~Ww=JjK z5v~ z$^79y)YNf$+nux*rU$1jJs5cKw6m<_9(SfSEd8cePhZGxn(p}c&HFZuP}!son~?oa z%}!jHeE89EIf-l1>Koeo7-pS(hUR*P zy|EQ*&g#F{sb2W9b|)9Zo@+CztfMoQ-jH6+oqgJrx z{b$~M<_Y-$X0|)_HOsN^)XP^3yxzVb{LH4p+oekTyrZ{Ee-VF?+3>;N=-C@2G-}&m@nl0;P9R9t|H$U`Y_pJEVt$&J5%sAp6o@O|q o=(G95f7yB8c;m$m*e|r7*dwq_H&1jv0|Nttr>mdKI;Vst0RIhkEdT%j literal 0 HcmV?d00001 diff --git a/app/images/icon_infil.png b/app/images/icon_infil.png new file mode 100644 index 0000000000000000000000000000000000000000..ccd14f39555f55559ecaaa1bfae734b03c35e718 GIT binary patch literal 1008 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hEi$s*?9ogcP?CQF-)#J7OgM)0Z!ZVgQ=?9rCnm+GH*CKJjFH6$7#YhM`>3CYGkZsJIJB7EpN%$}JlGRHk5l-Tzy zf0Fa{aM-8J2kW~_v^h1ZI5f7gZkf=~^toQ-7SpT<2Ymz1J1k*dEEDeEW$HNj{;4y0e)3#%t2NWMHR_;?^`cc94#WnIkUBb1G{TrAf?l7CVdOA!`dKAI* zFF?=Lk)iVZem0MP%}ougu8b|^4%>AZlO22&U$!5JOh|6%KPY=Z(Vkwz~ram{6u?R9rGPRUuG9{w(u@vS;HvC z`u~Udg!~IZ7OXMTJ!Y7yKG>-8bFSDorpb27f}ss>_MW%?X6C$TVgRF21XGH0{i#oR z2CmajER1KJS|3zo8ngV2ON#X#$scMHHh700;Obv~o@L407gw11xLEF77d3cwF~jzR zin8Rc)OBZ#{?7E9eV$clS1F5)0LOt957Ks;ZRU8}>E@`pqJfWZ{f_1snWH*wb>CjR zU%i2;jfH8AY?4NS7Ji-zqs?!=Q=vSwIENBL&gs6#`KqBLFLo??;)$+jF?l8x|f}L)Dk*r*Y3B?6ZuXiz7#&Q zc%$&7uU|S`8LzX{{_3nWIp)@QqQvybPMfxd%ahuRZXS8L;Z5iYHW^MUCMy;HxXKr^ zcPTjPx#<~eB&GevnY+*A(P@nq`zZ)V*LW?WiR_N=Y@!@N%R;fn&N SOYRH|3=E#GelF{r5}E+Ihr1{M literal 0 HcmV?d00001 diff --git a/app/images/icon_light.png b/app/images/icon_light.png new file mode 100644 index 0000000000000000000000000000000000000000..2ea42e36086de6b008c10816302d906a8f6ed5f2 GIT binary patch literal 1278 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE zPK)JE!BpH?|z_)!i{T|Z?f4`<5f3iZ+ge!%6Yid_*IX&%>SF4Iv?5pgyj{R&qxxTZUez;A1Yg)?m zqZipW%$~aYLcM9I(0!Srl`ryM26g0rI^pyuPn##RD0oZkqnL(!Z|!fXINRRpxn07~ zxZOXY*h%@FUV&kys7J-t)o-}eJ)bqFUJ2Z{j6q!SpLZvNzKELsM24Ro*EglyQD4bb zFfDm1he?lB_Vt>*rjPDV=J+6xlb3NM*S)5Vb9U$n&qxMlQ=y}WS6tR~UG!b?pJ^w< z^@C|HhR;ME2~N8+(Nsf!ZSzF-rUbQ*H4z(LoI7`PQcIWY5##A+l^i9XJou6FLgKiL zqQjMi42qo$itS7;oQ(IJ_zY!xpBDZR;@HrBY^$Ca%SnBUo~w>Ln$4djpIy57Ak?YB zw%z-BheJb0v&B*-yR_A*da`}9CLb5rsPyO9#Fd_V+y$1q8@s7}(T(Ed3!EIM!z2-* zcCA~0Rq)^BeS4LoUHF6fs~_Iiy4iZ}a}WcwdFQ#(_Cxtk ze&245p3k^lWjm|DLrup67WLl2J%;HYgFkvIpZg(E@M*2nk%${QLN3Qdk{@i}Tj)C9 zaM5|!EjK=`by?P*)6_tDexgP(RdeR^D1^eMU_b}DU5q8WbRI%f7bN&lm{|h@7a{! s++UtEvEn0#-5eek4i<|$On+D{W_4)C)=6nHFfcH9y85}Sb4q9e05MfXmH+?% literal 0 HcmV?d00001 diff --git a/app/images/icon_max.png b/app/images/icon_max.png new file mode 100644 index 0000000000000000000000000000000000000000..8cfebc3776932252608e9f4ca6eb1e9ca1fd1798 GIT binary patch literal 902 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE~)oxL$z#8Kqf zdoD+o*(FUb7b7aRSF<^D9d%1`u@Ko}c;r!6^PJJkr=btSsj(xIf^V4%{^B9yliquTJngX~O_&63Sl|2z=h*y}xrJ$V}-Lw60)=dUC z=3K+;EwS?%3LH2@`|rojXPCmeu$S|J(xHUSS%=b9ICNj_WDsKsRSR%-n9bA6+%loc z?1qFhhabbMNsrDm9hxPe=x{^Os+)7cWS(vFxTFNqnAlp5-(cVq2|tt0@q$62QNZ9@ z%L#UdPvQ$Z41^7`nO>%7NHyPXtTB6G%`)v}!IMJYglhQ%k-|*e9BcGlG!$7_jT`^= zPIxVPxGmt6w&8T`HreZv2UdnOwD){pcUDEcZ};?#{}s00&kScMpUFKVS8avDokG?P z3Uf1ZdlYzdvcn)hbh4mSdiTod;ho_&^NpEpEj&?+b64Df3^=FA9u?{LkL?-S|<<_qpMY#ZA_Exv~fDp9{?5 zIM47Wf9_j>neoaJ+L7-K%vu;PT6oT0F3!08_;OV zL+NAZF3p|OcxwmOv=E<#dX2&x4py?Z_A(~<@a=m(X}jvf%y;VLn$PDbO=nh&+>1zE&sF~!eVc6k}+1^qe8su&R9 z72AvOc_;d~8IOtHuQ}83S2SV8laAM-vI#IH5gd_Rw|N+0Tn2^!WrrWyMtPl#3=9dy v3=i|W-xo44Fetp+een60`Eh+r3==L(_nNd=-QOa75v0r0)z4*}Q$iB}`EgxB literal 0 HcmV?d00001 diff --git a/app/images/vehicles/ant.png b/app/images/vehicles/ant.png new file mode 100644 index 0000000000000000000000000000000000000000..8e2126f8aaf1d867e82f6897635d0ec30ac0222c GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}>pWc?Ln>~)ogT<{M1jL) z=gn_vXU#d z#^;=3&{;k4XOgHx4udL#nqdIbf?w(Wj~3Xp8O57fPWpGk$$&v2Rm`hlxn#gRPRZr| zU*5c6n&aCLe3eOE*!gPH#`S(G-_Ljk^e08_Fj5U?*m7(mmx9>=wiWl-Ll`RN@|}6d k$9cu7xDCUU5Cjz`(%Z>FVdQ&MBb@05up_jsO4v literal 0 HcmV?d00001 diff --git a/app/images/vehicles/chimera.png b/app/images/vehicles/chimera.png new file mode 100644 index 0000000000000000000000000000000000000000..22989f56f0bd70eb9996a8ae990d83478f73f761 GIT binary patch literal 2188 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_SHf<0XvLn>~)y&Ih)>dL_O zz-gz(mOD-QSs6;x!x&Aagr_76Y6e`f3pgsEtfCaC^^r@hqf^OESXh}wBj}(L>!aq( zq?(IiF3NEZc^B0kcqh$J5Lad~U^-f;KDTr7%QJ7z#GU(g=HKK$jyAiEcU#AAKmBgb zO(Q1{$w~JgC@?VaF)=8B+{DDdfnY3|eM>>SW7mcM_J=1o+bE~lXMg5M@S3pSl>KSE z;$_=+H5;!yF`VFR5On-E<<)|Ec{YZh3>Vg{ zOqXT&!Jkkv;q$DjbF7kl40X&AqL=QmF?{R-vppv}(1oE}=k@$th7HFytu?J=zHpz(arYL#5C&aA z#w{ug4bPn0&z!~Lbrrmu6lI2VNOa(FDB`Q<$8n{$ec4R07edX86II z5a1zwox$O~ueTt>msIh(!c%Sx9_qC+^ORPdDm-20cQoU1?Y6lL6O@`(E~$26ohPot zULzy)s?zd=xQ_MB`>}4?7oT&VXUj~uefaYFBYn%wi&@TYbGV?X^S|-S&9H}?`MS^L ze|WN^($q6VqN!hpaZ5XoX!eH}Gb+u!Z#c8DtZsR_&3k^X`a|dAnR| z_`_guOz40F$afzE9A>IE7%_ZMe&iysT!wLuh+Q^Y!=-mMhu*#4%`hR|D3`6_(#e#g zy>s~78CE@KoXohOgmH^CQwDp(C11rSmJPfOm-EyX4(fo!aNd^^mhj)Lpo#rXps^{)nv|{@uL5HPjzhC@iDHJ-f zocH%4rw@7zI~XI@MYNUwOT6@XcSXC&S2flLD_9!R)+g4-F*Uqkn3g^5A;T(FhSjF_ ze?6HNr1r8j{JZ^c&(oO<3_6T=@*>*i|N70z@QPKU=%%pXfxN?JXM`B0Y@A;btjW+I z-O!Xc)AkataMi(Y-Dx-1H}M4K95>6{xwb1+tEp!F%J1oyY#)}^Cz-3>{kwz1hmGNH z);Hgvx%yI8@!z#9q#0xm`uYAn@5zv7{D+IFp@ktQ{L0RQ8$ZV~e7Jo`izQo?VOg*Z zg9<~#zc2m4S8i(RrdM&Q%)K{tA4k&sEqs5i=O5bZ)cjz{mUGLw7{2ISU}gBo=o4JC zEi!odpIsMek_t(hTUPEWFVV8$__&#!Nm&NXT%**|&4zWv{= zGw0rY^1u8y--R{ntJpslzHAKSuJ(#JdrW5vvxCuG{d@1l8DDtakamrht)Ff#bxi$3 zzO3&Aww@5?hJU*oA4zYSFH$7_($Xfn?#5xp9#aMmYg7Fcj;P4z`aF^MzOWUEGW0pH z1t{3wpLXthZGs#FPm=R4J#N9%R+&EoyO{Hs4?NRJn0;(QnOX*ez)@kJtF;Z?I=eSJ z1mrMS>%L>@QqEf~a{5E~{;O?QN*hY*e~10~_VKIY?Q-3hKi}SER(yL!YMyt9Sb(>6g+;$w`d-p+3HO*bI-4xb>6zAn)@t60{a8M!$vX;e?L1N zn_J&jx`&fNmzQCJ4MX39oVL04u{XpRG?*IlJvpu?PV+vm>GTX}guCnJLk!v|Bt`oq`FN(C9Os9rF0 z-Qc`~;|Po3G=?_SD?j%LN6CDW^LBN5d-?c%Q-**&l}y#epn^c*JVQ~v;0;!WW0#&< zH~duO_{6iITUP!(d+Rx4g%jEhK34YH3>^E!8yFZ+YHese?(y-HyrHV~Lzfwo7lVxR MboFyt=akR{0Fob_-2eap literal 0 HcmV?d00001 diff --git a/app/images/vehicles/corsair.png b/app/images/vehicles/corsair.png new file mode 100644 index 0000000000000000000000000000000000000000..d0c0609ff7b89b262d1acf7dda7ff523897d3274 GIT binary patch literal 375 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNfcAhSdAr-fhfBgS%&pb0i zvZJ19qY`VHfkD6mOYT<pAG|VR$;f;gkP`!;FU<{;*~# zu3YaJ`9Wn$txy(UBws~G$_q9j*$rZy|0hWC&G1PQ@mX|)u_vI0`{V>AAt#2tC*4HZ zSth(-kk7JUu$1`f*}?QB?6XRdEW@Ey4tqp9etPgQMjQ@pzE*nh|HWMm_xLg#HmzXn em$)dw@WuJ;PKJMH)EF2T7(8A5T-G@yGywqMx{jj& literal 0 HcmV?d00001 diff --git a/app/images/vehicles/dervish.png b/app/images/vehicles/dervish.png new file mode 100644 index 0000000000000000000000000000000000000000..8dbcee03aa7b6f1459f2dfdd295a592ae87d2a61 GIT binary patch literal 2841 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_Ty9(cMqhE&{oJC}PySg7dn zol+`4J$y803T}G1Xz8RYJi*3Vg(sOD)ws{*rDVEfO`2AkdL^-@*Y_ub-hyQDqn<@8 ze7q_%Z&fw>01J&vWzFmVSGEXZN);^$ibes@Lzm z{{GG8^L2CW7%sS9tzJ5H0YiqW27?x>!%~J7A`DZR76dVba5Z=_252%|z2Mpzre1aU z&)w9xwVm74qo!ueVG?lj)!cgc;k8$c4O`Bh`*C>*!w+T!=|2Jt4IiBuKkytdIk6qY zc4z#sq%h#V6C=YhebxgT+tjLbgBUKX=htA!+se|VtHO|RUW4JCiT1^RCm0=edoylW zzftXn8DmBHqR(gMn=#&){i25Tz`T3&UxY4@WM`;px6g4QFN!@6XLyp2o^WpkJuF7LG!X^7-+r7`jlhMQE49k8VhF~ckCPkM2c@vIltNl34eQDRC6zzu8oG=DH z_5^QFhA@H8RnuqwI5_jb+La6Lrz*U3_epp4WZ0&`(C&XuMuX8I@Unko(l!4NyOX_b zxU!zctYRoJ-=3Nqq;Ow<(}Um40ejvrT&d1nu(*}0#=zi!{q}&im$tGDZ$9Mszf4`c zX`A&$foB4rb(h^-|J@>k`JT|m#?H9y0n?@jACf$M_SpPW%njN}Va?Ky-<_R3b_rYg`p>(}7#m`AzV(ckJj14~JtEZy zx1?QUYB*RgcjqRH8pHOeQ{{Y{<!|EA@tN%pv*4%pfTem^z&!TyS-4{D~;}%*RjOYE=!|dqYyt`&f;Keh_PxrKB zFr26kd&F`~HSLaqwbizJJgUDsf`fzM${>jgg zUZ48o*F2YOiA??TGylwzGiKQF(Xcw=lZfNebE!W*a&u%$OYXa;@A2R2*omY9hI!|A z{x0X>+B^9oR}K%)f%lT-_0tvpo}05J>?qfPrZc9>-XU+UbTNF0UNk>^+KI<=e4_Y| za2;6t;hd{d(C*$B$KNwCZ)kauKJ#hj&8r^fN4XAsXJ*)-WWdlZ(%@W{|7=#EDpxJ< z#s1^W%pZgo%}>p1>zujeU8iV6PH{!khR4liY8U%k8yR=>zjz*b=u2V2i`to6=Jkj+ z%(c93tD<4@e6#jvCguY3!`JOOF3o3uAMr>ZBGwpGfFMO6`akzhGBjb+A z4_`}O*t>pWmW8C5#^hs5Zn!heKfANcM39N+-}9MucjP1*%$Dts*thq{gZ`5{e_ub! z#}IYzl%$NJ&aY4GVsDh0?3b~#J+Nk)f8LDQ!!?<^xv0*3WuyK@h7EU1sy@}3AK}U0 zCcSkg*UIgyuYVJF&AA=QBJZ!qu<$HT?RnG8tGhYRnS71nWRP-nxO-`@R0g-Zlhjjb z*K96@1sy_vUNEe1{J`pXYez$p_=T{YHHHQZQ|wOXE%xVr^~fmc!(x^NY5vaN=h!~I zxr2jG*L*R9ft>-v<4%T#pPLsym^l4jfw7apDM?)h)5Cw??l2Gve;<=$a`g+V+4GqF z(;pmvae%=`;m3^c_x9bKQoZWs9bcoxtvg;lfA18(h`k|Ogu#v_&pbg)^4V`kMFzb! z;Yb<7USlNmnrK4Uul zv8X7TcFJlP_gb_5&v3-b93K+;bG8Au(b}2 z3op0NW07U!vp0SHYjL3Ov%JE>Id8r%_-%;Z5`3sMduHTkn{3rTAwqO2hIdft4 zm%DD;+C2Vqe|j(cM|J6ahwnx?or%jgpAM@0@Ixj>;FGg_<9A+%ZJ{&R9`f|ZpPXl3 zu!T`^N@A^yu!7h@28Uke|H5ki&VLW9J2^C@Yt0o_`k=_@@w()Noxr8`hw~0Iwzyw4 zp7-ZqQN2mQU-yqce+bGcEMVEcY_g4mfa`~3#slmu;>Le>ZZT)ES7Ye%+qWT6KK+-` zrG@(DZXwSD83U|8xfaD#E$*DW@PM^XWy|6R`t1DjE$fUJnq?Ue{QZ_US((AFd(FPZ z%nYTqEDRp|zFa&}u6n{Q46gZ*|lstquaT#Yp?!sNHH)lFnGH9xvX?qI{ z&aLPe)Wt90tGZ~3hx7-=AT~#pt{G}!n!33;tWz7NH6?hR6_)AHpPU-+iZBp6+tQ!pMCM!#T^V#+EErpCsoBFfj{Bvv_esR#^f{ki`>*2eN#2Q{qR(fb-q$0W-;;oVONKM>}AUp@@_0(<9)J6_V9_K z=@b1Mx4ABO8=Md*8T{F>=pM_Z+XAU;C%Z53XZkC^WGnQdRp7-W2lEz(Zk8xTmRBkP z{E7jJjb0p$LYz~aI3$D&{xj7(CHU@K`oCez0poS~F@8t4$jeWkcK!XNO?~~BQ&0ae z{q$LLnV5!l`6T^&Q;buuhAzm_Gz*lQ%KbFr=3>zs?(bqMogS;)YutQbWl@K&MoPtj jM^Enu^=;q^Yvf<#J$K_~k2Tf|3=9mOu6{1-oD!M}Powncmu!G2P z=F&+H2lM)(q6)aIwklYZox5>1R^Su6MaV%%_LZ$l-||1OTkI+=5|&ZQyDQ2iy1igu z^ZSR&OU|lJk71Lq*jg5V>lWx=dc2OKQR?BQ`?rs%eq5~h!8b-A`b~|v=~k9?9UT4B z=G-)&)%#fS{7yBqPQ9#quQ+TJf66Y-JRx;H=ZaVdmq&Dg6}( zzA65*=3gUd+}mdSWYyppq4 zi+9f_GX`amybMO(1KlqZG>~j4m zl{|lubj_#o);S_sOrHvK-!(~zu6xNZB5=U$y<8E8!h%1hKd^WU zg5r*TsMxrdTW*2Vi91S5j&@Jj!}L?-S6axDyV;`upWgn+|L?d=|B0?b6$SBXIr&PQK9YWN>uCEanIsgQ4$g|BQtLyDkL!^h%`w+*rmHuCMv)0jSr zMOibjXcbs2=$ezEaGcS5Tv+5VZ62$j zokKAD8HPvu|8q8LZYf|87MLx-!8&7(qU|My(5Cr9>>n6Dimx~--#BCTJiZN0QD>N+ z7CZl(_JxO0;)m0DjSmcuI6VRzcoG5)L|65x@!eqD`bJ)0v*SnI6?0T3C6`?|#pvb1 z$MEo^!yD!!OEVTOba{R1g}l@<4eLp-8O$k(j%$1gE~mdMoZ&qBf8m#m>p52#RT3F=KGj9eQIF4L QU|?YIboFyt=akR{0Dy{(r~m)} literal 0 HcmV?d00001 diff --git a/app/images/vehicles/liberator.png b/app/images/vehicles/liberator.png new file mode 100644 index 0000000000000000000000000000000000000000..1d07c52bb5854bb77793807ea6c77d2243b7787e GIT binary patch literal 505 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNf2RvOILn?0Foxb1uu!G2P z=1WV{1k?*Z*k3qmWg-4%!9V6p>u&AjlWm#ix730oHtTGFY!^qG#+F!@6+r?k#SU() zclh7fBWZm?(D@|S;RU8x#g9=aOiGm*~}sB`GrHb!1YMyWr6N^A?_nv`wnP}Jr|mt zVB5fZKwMu?--_w`5$TT%k1}5U2sm`;e8cg%>l!5faFvKo-`l-e+}V49y6J62JrSn# zBip9De#x-LM+OE422WQ% Jmvv4FO#nN5(E9)Y literal 0 HcmV?d00001 diff --git a/app/images/vehicles/lightning.png b/app/images/vehicles/lightning.png new file mode 100644 index 0000000000000000000000000000000000000000..1d07c52bb5854bb77793807ea6c77d2243b7787e GIT binary patch literal 505 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNf2RvOILn?0Foxb1uu!G2P z=1WV{1k?*Z*k3qmWg-4%!9V6p>u&AjlWm#ix730oHtTGFY!^qG#+F!@6+r?k#SU() zclh7fBWZm?(D@|S;RU8x#g9=aOiGm*~}sB`GrHb!1YMyWr6N^A?_nv`wnP}Jr|mt zVB5fZKwMu?--_w`5$TT%k1}5U2sm`;e8cg%>l!5faFvKo-`l-e+}V49y6J62JrSn# zBip9De#x-LM+OE422WQ% Jmvv4FO#nN5(E9)Y literal 0 HcmV?d00001 diff --git a/app/images/vehicles/magrider.png b/app/images/vehicles/magrider.png new file mode 100644 index 0000000000000000000000000000000000000000..814498971a6094ba5c3bb996d1d2fab14d43e19b GIT binary patch literal 585 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANM!0-i38Ar-fdPPg@Db`)sS zUBmk_c|*t4pA3#Hp0fl@k|rKIB%#gSsW3}x>VJpXCS9#Jeg>+=9x!Rr)X{BGV5x7Y zcR09a^9Q-U(@*rOw?8`IJ?Un7`ux4kC*Ms8%6es=nw&Q=Nccz42P;+!g_$fnr+aCszEGy;; zrL~(~PoJ=4p7EXWLZQ#AjWzt1PniwUnKuY9Y;cm=^hdzbzF`foLn0G{aTCKQGX_<8 zhP}6E8H63aVbL&&_rR?gO24k&ndH~9G5FHl^RJ_~GI~tX^LWp6K=EZ@gu`sRk0QhwH+_#NBgp4fMJ_e0w* r_MekJSnZ!9ro+Hh&-g+87hBNTsD0*afqNMk7#KWV{an^LB{Ts5Xj%A7 literal 0 HcmV?d00001 diff --git a/app/images/vehicles/mosquito.png b/app/images/vehicles/mosquito.png new file mode 100644 index 0000000000000000000000000000000000000000..0f6248256384c56cf6e1d7fbfcc5283c086123b9 GIT binary patch literal 429 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNfC7v#hAr-gYPT%Oo>?qI{ z&aLPe)Wt90tGZ~3hx7-=AT~#pt{G}!n!33;tWz7NH6?hR6_)AHpPU-+iZBp6+tQ!pMCM!#T^V#+EErpCsoBFfj{Bvv_esR#^f{ki`>*2eN#2Q{qR(fb-q$0W-;;oVONKM>}AUp@@_0(<9)J6_V9_K z=@b1Mx4ABO8=Md*8T{F>=pM_Z+XAU;C%Z53XZkC^WGnQdRp7-W2lEz(Zk8xTmRBkP z{E7jJjb0p$LYz~aI3$D&{xj7(CHU@K`oCez0poS~F@8t4$jeWkcK!XNO?~~BQ&0ae z{q$LLnV5!l`6T^&Q;buuhAzm_Gz*lQ%KbFr=3>zs?(bqMogS;)YutQbWl@K&MoPtj jM^Enu^=;q^Yvf<#J$K_~k2Tf|3=9mOu6{1-oD!MEAD{!hrEUQ3Bvk}lSP_Zx4Ri8D6{B@UOeUG>cZSKK}l1H`=G)Og^imxh`D~@ z)!8cGT43A!|Iot;+`?*|Rv!#^r>8%wnR$k1_q4|rVS-sK(>&MgSDK)(EkHlS`2p9- z(7i#Pjg?80I*h|QIkJu}OK=v@4SjInr>105`@%_f)>W#jwG9qf9p~j(A#_DBS)PrF*9vFfIK0h9Ey+cA21D-+%YIgtHeZe<5tsZMrS(@k z2zkDdVJh3nGHJpC;|G4*Xa5o0I772Bu;iD5*iFmze2WZj&DnhLyujz#VkaKhoer>G z7{?)_v~&JfQ>6{s-*!j;QdDA<`Lcgan}hkf(EFCb-5)qk7IDgKs^02QU100YvPx5+ z!R07x+YKoTQ_X~j3PE!o99VhUnAy8>+5RW475+WKu?~9|Toc+WP}to&qv~5iki%5{ zW2}>ISPI>!n5MD(fOuV|g?p31yuD8ZB@9&sbQJ`23q;yWSY~xfKkBUzZD|lGe!S_g zb)kESfZSdF$!U9X3idqcRIN`cym?Rg!atMA72OKA|K~O4XU64RYE&}Poo4TS*g>NG zu~G*gSJwlDrny%poKy>2+fm0bH@2nWMO=8H!1J=u{f+he8k+;ACoL7(-+WDxb-I;g zedGOu-W*%!ofLh;%`blM-re7KmQFvyb@;-i6p5H7?zrr;$$tYCy4Kfm$S{U;>^LJS zyh5GBW~Rsw2G#&p{sYXp!A!|9l_~`sd>R5rS8z5q2?RXs{Pv$)X7i0z0ukxE^PLvl zb6qfP*#WT`-A`=-O*|D|nw@F#m2G6(!FawYj`d!J%j!9c7iudk78O3xTs=v=a{74| z$y-d%6Z+?D2n}RYUZV3UaDK4iJD>0bUZ3Od>b19RDpy*3Ha6g~+CeX=J z+CiRZQNH57ll_+}6(_tt9{#aKK*C(pK;?&uk$D7@eo6sL*McMB!3zIc7>iZEdHNn; zv)jQsH+~PVwanvUK3&G&?MjY4d5Nz%d>XmJ9~NhKKUz3h_-Lx%*WkxmbF?>}Ghfhr W;_J@bT1f^51_n=8KbLh*2~7aAy}>2` literal 0 HcmV?d00001 diff --git a/app/images/vehicles/scythe.png b/app/images/vehicles/scythe.png new file mode 100644 index 0000000000000000000000000000000000000000..1e0148bac31aa505f439d6c1eed00467ded663a0 GIT binary patch literal 535 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNf_dH!3Ln>|^oo?&J>?m?P zydyJ(bK^#D;e&3%p6V>GJH*O38yhD1`8;JUVmzp#P}QMZ)G4E|=;$N1Jq}D-H&Sbx z^B-8A$$6&zrn1>egFlZ^`V)*imDvQX3EwDDy2<)!w=<*_Kx2nhSQ=Bvo$zB@hA-Y!SaQ%iaoaJ9fyNo zP>giL`?^IN<$6zU<~Wesvv1K+wcif@j1w{#RJa=EaXQGEhziMY=9aP~SgF4a{^HxZ zZO_ZFGfcna7=EpKWYr#cl-c3vQmz;3~hy!qvEd_60TkOkS|oP&mib|62Gt54^UW_4sby&m6{fQlW pFS2%&bj|)J%RbA~dGeh7TdkjNi{pI1l!1YP!PC{xWt~$(698*X+;0E? literal 0 HcmV?d00001 diff --git a/app/images/vehicles/sunderer.png b/app/images/vehicles/sunderer.png new file mode 100644 index 0000000000000000000000000000000000000000..644c78362680428d4ee89a91e1fef7b1b3692399 GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNf$2?seLn>~)oxVGZ$x)snRy?eqd+JAW*PK~v8ypU< z^RX-Gv&drPcl{;Et7b8+^3OeatK*yZvY$KnPeR~*g22XgpXbbB+S<)-z!<6)kowu` zTyZIvP0YFU4;-_67My%m8e`w*BGWkWq1tN!`31=xl7G2(2yk><)n^Giu~p21QCFae z=_4msPTcXAHt9^Se(p=P=9%em;@S3&Ql$ybjz>ie`xOn`*PJ_jlY?bVfOexp_fytw zOx6MFVrv3tS1{+A?RQvn&N`2=F{$*=^g`Am9se8im|{;H@4K=?eUpzuw`Hfq{HKv~ zndZM;SDT++Y>f|Au%6;$Ii+{6gGEm7)#DW3 zl#biBMiI9a!gl;I5X^C3b#ii}X?L-nf!_x&yE5Gcs#-^mGHMmh)~;i)O7*Eu@|w)d Qz`(%Z>FVdQ&MBb@0J+xC&;S4c literal 0 HcmV?d00001 diff --git a/app/images/vehicles/valkyrie.png b/app/images/vehicles/valkyrie.png new file mode 100644 index 0000000000000000000000000000000000000000..4a64a150af32ffbf9dbe5ca77c07292dffcc6c9d GIT binary patch literal 555 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNfpFCY0Ln>}voo489*g>Q% zxKL0e@Q!SjD2uENm#)*@oYEIptaiLk;0|)U%X09j-ZUlG02!9plA?lw;FS-aB^7Sy z+WcV6)+7HP*jt?2qGsy(@xkLuU!LyOpMLr&&t((dc9yz>fp(AgbIUYOF5}E;)-K@v zRL51)Bs+=Ka)bA-zXB40D(4Qes=9nET+ga@Kz+tVH-+Pm;(I>&Bna6Qh&)NY5y~Pb z`FQ04v4{kHwTHS9te@Z5igIYTmNMRK-#f2man}Jsi%^!fz4Ka<75o~-y*eJP7nxD? zOYH{l8|L2+m~)z>Kc@?GU$W}|?qL6ObAv|Ra*<8S3b!UpGsQ*~sxvK$HF7`A6v&m* zS;156%d*V!kLCuq#udShe7uEu3_2VA3x$@=uwl9+;+ws(_tQ-=jz*!1=ds6rJUzX6 zM~>jSF6|z}O5aY)C?NsyYg-cgKE9i-%i{9U*wta%EVabw1(zHg%wi8{xp9Te;ouNE zD6LUcI8nM+E+F;Mf}oU++Np*`kCoQhvYbyid@}li_nLp*=7Jt3Ge0)x)pGOn_2>oU zsD`g*YHMwHIK5#P^E%=CoJx+0w+FHUZ2fzMRIas zw8Dv{ThHX~P~d#$8DtrsmUF?kF>=v-dxf&cv9-Lh3*R!`U;A*)pfi@Ln?0Foxb1uu!G2P zW-oQ||LhB$#h3G~+qJ|`)%%6g(mYo6|ID(X6B?R1SnG3}riux&9E{&Fm9I7D=+dP- ztXT~AHNSuO*dzH&tj3cE&VA+j?`w-Z+cqCE*b)?^;OC&2E9>pNK;&QFB^{PkJ`1e4 z84oIN*d!LA5?1i^qxb|B^Qi}{3cdFvuQnIvFqvSjvTMb}s0ST4(%(mMR2)cSGV3;v z_$QYoAv$64IhI#Cc`*x)a$UQz$J3_a*_1h9dMvl3nBMBJs2MU%)?vvLY1H4)dVfv# z&Yt4N*D+i_GC~rWM32;5W?Ft^wo-wINN1(V7eoD|UhX|Y;#N$Lp2>aW&FZi|(qJm3 zkf!MV?E5Bxf?{@phU3*r$j_OAT sS`IWd^}5ww+tEKGVm5PUU# [ { rel: "preconnect", - href: "https://fonts.gstatic.com", - crossOrigin: "anonymous", - }, - { - rel: "preconnect", - href: "ttps://fonts.googleapis.com", - crossOrigin: "anonymous", + href: "https://fonts.bunny.net", }, { rel: "stylesheet", - href: "https://fonts.googleapis.com/css2?family=Unbounded:wght@700&display=swap", + href: "https://fonts.bunny.net/css?family=unbounded:700", }, ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), @@ -43,7 +37,6 @@ export default function App() { - ); diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 24db3ce..c57cdbd 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,10 +1,10 @@ -import { json, type V2_MetaFunction } from "@remix-run/cloudflare"; +import { json, type MetaFunction } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; -import { Footer } from "~/components/footer"; -import { WorldContainer } from "~/components/index-world-container"; -import { outer } from "~/components/index.css"; -import { fetchMetagameWorlds } from "~/utils/metagame"; -import { fetchPopulationWorlds } from "~/utils/population"; +import { Footer } from "../components/footer"; +import { WorldContainer } from "../components/index-world-container"; +import { outer } from "../components/index.css"; +import { fetchMetagameWorlds } from "../utils/metagame"; +import { fetchPopulationWorlds } from "../utils/population"; export const loader = async () => { const [metagame, population] = await Promise.all([ @@ -15,7 +15,7 @@ export const loader = async () => { return json({ metagame: metagame.sort((a, b) => a.id - b.id), population }); }; -export const meta: V2_MetaFunction = () => { +export const meta: MetaFunction = () => { return [ { title: "PS2.LIVE" }, { diff --git a/app/routes/about.tsx b/app/routes/about.tsx index 1b0f3d7..5bcd385 100644 --- a/app/routes/about.tsx +++ b/app/routes/about.tsx @@ -1,4 +1,4 @@ -import { Footer } from "~/components/footer"; +import { Footer } from "../components/footer"; import { header, item, @@ -8,7 +8,7 @@ import { link, love, outer, -} from "~/components/about.css"; +} from "../components/about.css"; export default function About() { return ( diff --git a/app/routes/debug.components.tsx b/app/routes/debug.components.tsx index 5d7b186..3bb0a83 100644 --- a/app/routes/debug.components.tsx +++ b/app/routes/debug.components.tsx @@ -1,7 +1,8 @@ import { useState } from "react"; -import { FactionBar } from "~/components/faction-bar"; -import { FactionPie } from "~/components/faction-pie"; -import type { Population } from "~/utils/saerro"; +import { FactionBar } from "../components/faction-bar"; +import { FactionPie } from "../components/faction-pie"; +import type { Population } from "../utils/saerro"; +import { FactionBarSxS } from "../components/faction-bar-sxs"; export default function DebugComponents() { const [population, setPopulation] = useState({ @@ -66,6 +67,10 @@ export default function DebugComponents() { value={innerColor} onChange={(e) => setInnerColor(e.target.value)} /> +

Vertical Side-by-Side Bar Chart

+
+ +
); diff --git a/app/routes/worlds.$id.tsx b/app/routes/worlds.$id.tsx index 6476747..97996c4 100644 --- a/app/routes/worlds.$id.tsx +++ b/app/routes/worlds.$id.tsx @@ -1,32 +1,33 @@ -import type { LoaderArgs, V2_MetaFunction } from "@remix-run/cloudflare"; -import { json } from "@remix-run/cloudflare"; +import type { MetaFunction } from "@remix-run/node"; +import { json } from "@remix-run/node"; import { useLoaderData } from "@remix-run/react"; -import { Footer } from "~/components/footer"; -import type { MetagameWorld } from "~/utils/metagame"; -import { fetchSingleMetagameWorld } from "~/utils/metagame"; -import type { WorldResponse, Zone } from "~/utils/saerro"; +import { Footer } from "../components/footer"; +import type { MetagameWorld } from "../utils/metagame"; +import { fetchSingleMetagameWorld } from "../utils/metagame"; +import type { WorldResponse, Zone } from "../utils/saerro"; import { allClasses, allVehicles, totalPopulation, worldQuery, -} from "~/utils/saerro"; +} from "../utils/saerro"; import { pascalCaseToTitleCase, toTitleCase, worlds, zones, -} from "~/utils/strings"; -import * as styles from "~/components/world.css"; -import { c } from "~/utils/classes"; -import { FactionBar } from "~/components/faction-bar"; -import { popImage } from "~/components/index-world.css"; -import vsLogo from "~/images/vs-100.png"; -import ncLogo from "~/images/nc-100.png"; -import trLogo from "~/images/tr-100.png"; -import { FactionPie } from "~/components/faction-pie"; -import { AlertTimer } from "~/components/alert-timer"; -import { contPrioritySort } from "~/utils/sorting"; +} from "../utils/strings"; +import * as styles from "../components/world.css"; +import { c } from "../utils/classes"; +import { FactionBar } from "../components/faction-bar"; +import { popImage } from "../components/index-world.css"; +import vsLogo from "../images/vs-100.png"; +import ncLogo from "../images/nc-100.png"; +import trLogo from "../images/tr-100.png"; +import { FactionPie } from "../components/faction-pie"; +import { AlertTimer } from "../components/alert-timer"; +import { contPrioritySort, zonePopulationSort } from "../utils/sorting"; +import { WorldZoneContainer } from "../components/world-zone-container"; type LoaderData = { saerro: WorldResponse; @@ -34,7 +35,7 @@ type LoaderData = { id: string; }; -export async function loader({ params }: LoaderArgs) { +export async function loader({ params }) { const [saerro, metagame] = await Promise.all([ worldQuery(params.id as string), fetchSingleMetagameWorld(params.id as string), @@ -42,7 +43,7 @@ export async function loader({ params }: LoaderArgs) { return json({ saerro, metagame, id: params.id } as LoaderData); } -export const meta: V2_MetaFunction = ({ data }) => { +export const meta: MetaFunction = ({ data }) => { const { saerro, id } = data as LoaderData; const date = new Date(); const worldInfo = worlds[String(id || "default")]; @@ -168,10 +169,13 @@ export default function World() {
-

Continents

- {world.zones.all.map((zone) => ( - - ))} +

WARZONES

+
+ {world.zones.all.sort(zonePopulationSort).map((zone) => ( + + // + ))} +
@@ -183,8 +187,8 @@ export default function World() { const ZoneInfo = ({ zone }: { zone: Zone }) => { const zoneInfo = zones[String(zone.id)]; return ( -
-

{zoneInfo.name}

+
+

{zoneInfo.name.toUpperCase()}

{totalPopulation(zone.population)} players ({zone.population.vs} VS,{" "} {zone.population.nc} NC, {zone.population.tr} TR) diff --git a/app/utils/class-icons.ts b/app/utils/class-icons.ts new file mode 100644 index 0000000..345014f --- /dev/null +++ b/app/utils/class-icons.ts @@ -0,0 +1,17 @@ +import combatMedic from "../images/icon_medic.png"; +import engineer from "../images/icon_engi.png"; +import heavyAssault from "../images/icon_heavy.png"; +import infiltrator from "../images/icon_infil.png"; +import lightAssault from "../images/icon_light.png"; +import max from "../images/icon_max.png"; + +export { combatMedic, engineer, heavyAssault, infiltrator, lightAssault, max }; + +export const classIconMap = { + combatMedic, + engineer, + heavyAssault, + infiltrator, + lightAssault, + max, +}; diff --git a/app/utils/saerro.ts b/app/utils/saerro.ts index 6760946..210d90d 100644 --- a/app/utils/saerro.ts +++ b/app/utils/saerro.ts @@ -1,11 +1,6 @@ export const saerroFetch = async (query: string): Promise => { const response = await fetch( - `https://saerro.ps2.live/graphql?query=${query}`, - { - cf: { - cacheTtl: 60, - }, - } + `https://saerro.ps2.live/graphql?query=${query}` ); const json: { data: T } = await response.json(); return json.data; @@ -22,10 +17,10 @@ export type Zone = { id: string; name: string; population: Population; - vehicles?: Record & { + vehicles?: Record<(typeof allVehicles)[number], Population> & { total: number; }; - classes?: Record; + classes?: Record<(typeof allClasses)[number], Population>; }; export type World = { @@ -83,6 +78,7 @@ export const worldQuery = async (worldID: string): Promise => { zones { all { id + name classes { ${allClasses.map((cls) => `${cls} { total nc tr vs }`).join(" ")} } diff --git a/app/utils/sorting.ts b/app/utils/sorting.ts index 89d2318..0849152 100644 --- a/app/utils/sorting.ts +++ b/app/utils/sorting.ts @@ -1,4 +1,5 @@ import type { MetagameWorld } from "./metagame"; +import { totalPopulation, type Zone } from "./saerro"; export const contPrioritySort = ( a: MetagameWorld["zones"][number], @@ -41,3 +42,24 @@ export const contPrioritySort = ( return 0; }; + +export const zonePopulationSort = (a: Zone, b: Zone): number => { + const total = ({ nc, vs, tr }: { nc: number; vs: number; tr: number }) => + nc + vs + tr; + const ap = total(a.population); + const bp = total(b.population); + + if (ap < bp) { + return 1; + } + + if (ap > bp) { + return -1; + } + + if (ap === bp) { + return a.id < b.id ? 1 : -1; + } + + return 0; +}; diff --git a/app/utils/theme.ts b/app/utils/theme.ts new file mode 100644 index 0000000..a0fd6d0 --- /dev/null +++ b/app/utils/theme.ts @@ -0,0 +1,8 @@ +export const trFaction = "#d30101"; +export const ncFaction = "#1564cc"; +export const vsFaction = "#991cba"; + +export const background100 = "#222"; +export const background200 = "#444"; + +export const edge = "#4d4d4d"; diff --git a/app/utils/vehicle-icons.ts b/app/utils/vehicle-icons.ts new file mode 100644 index 0000000..c89cf47 --- /dev/null +++ b/app/utils/vehicle-icons.ts @@ -0,0 +1,60 @@ +import ant from "../images/vehicles/ant.png"; +import chimera from "../images/vehicles/chimera.png"; +import corsair from "../images/vehicles/corsair.png"; +import dervish from "../images/vehicles/dervish.png"; +import flash from "../images/vehicles/flash.png"; +import galaxy from "../images/vehicles/galaxy.png"; +import harasser from "../images/vehicles/harasser.png"; +import javelin from "../images/vehicles/javelin.png"; +import liberator from "../images/vehicles/liberator.png"; +import lightning from "../images/vehicles/lightning.png"; +import magrider from "../images/vehicles/magrider.png"; +import mosquito from "../images/vehicles/mosquito.png"; +import prowler from "../images/vehicles/prowler.png"; +import reaver from "../images/vehicles/reaver.png"; +import scythe from "../images/vehicles/scythe.png"; +import sunderer from "../images/vehicles/sunderer.png"; +import valkyrie from "../images/vehicles/valkyrie.png"; +import vanguard from "../images/vehicles/vanguard.png"; + +export { + ant, + chimera, + corsair, + dervish, + flash, + galaxy, + harasser, + javelin, + liberator, + lightning, + magrider, + mosquito, + prowler, + reaver, + scythe, + sunderer, + valkyrie, + vanguard, +}; + +export const vehicleIconMap = { + ant, + chimera, + corsair, + dervish, + flash, + galaxy, + harasser, + javelin, + liberator, + lightning, + magrider, + mosquito, + prowler, + reaver, + scythe, + sunderer, + valkyrie, + vanguard, +}; diff --git a/build/index.js b/build/index.js new file mode 100644 index 0000000..ffe2fa0 --- /dev/null +++ b/build/index.js @@ -0,0 +1,1904 @@ +var __defProp = Object.defineProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: !0 }); +}; + +// node_modules/@remix-run/dev/dist/config/defaults/entry.server.node.tsx +var entry_server_node_exports = {}; +__export(entry_server_node_exports, { + default: () => handleRequest +}); +import { PassThrough } from "node:stream"; +import { createReadableStreamFromReadable } from "@remix-run/node"; +import { RemixServer } from "@remix-run/react"; +import * as isbotModule from "isbot"; +import { renderToPipeableStream } from "react-dom/server"; +import { jsxDEV } from "react/jsx-dev-runtime"; +var ABORT_DELAY = 5e3; +function handleRequest(request, responseStatusCode, responseHeaders, remixContext, loadContext) { + return isBotRequest(request.headers.get("user-agent")) || remixContext.isSpaMode ? handleBotRequest( + request, + responseStatusCode, + responseHeaders, + remixContext + ) : handleBrowserRequest( + request, + responseStatusCode, + responseHeaders, + remixContext + ); +} +function isBotRequest(userAgent) { + return userAgent ? "isbot" in isbotModule && typeof isbotModule.isbot == "function" ? isbotModule.isbot(userAgent) : "default" in isbotModule && typeof isbotModule.default == "function" ? isbotModule.default(userAgent) : !1 : !1; +} +function handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) { + return new Promise((resolve, reject) => { + let shellRendered = !1, { pipe, abort } = renderToPipeableStream( + /* @__PURE__ */ jsxDEV( + RemixServer, + { + context: remixContext, + url: request.url, + abortDelay: ABORT_DELAY + }, + void 0, + !1, + { + fileName: "node_modules/@remix-run/dev/dist/config/defaults/entry.server.node.tsx", + lineNumber: 66, + columnNumber: 7 + }, + this + ), + { + onAllReady() { + shellRendered = !0; + let body = new PassThrough(), stream = createReadableStreamFromReadable(body); + responseHeaders.set("Content-Type", "text/html"), resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode + }) + ), pipe(body); + }, + onShellError(error) { + reject(error); + }, + onError(error) { + responseStatusCode = 500, shellRendered && console.error(error); + } + } + ); + setTimeout(abort, ABORT_DELAY); + }); +} +function handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext) { + return new Promise((resolve, reject) => { + let shellRendered = !1, { pipe, abort } = renderToPipeableStream( + /* @__PURE__ */ jsxDEV( + RemixServer, + { + context: remixContext, + url: request.url, + abortDelay: ABORT_DELAY + }, + void 0, + !1, + { + fileName: "node_modules/@remix-run/dev/dist/config/defaults/entry.server.node.tsx", + lineNumber: 116, + columnNumber: 7 + }, + this + ), + { + onShellReady() { + shellRendered = !0; + let body = new PassThrough(), stream = createReadableStreamFromReadable(body); + responseHeaders.set("Content-Type", "text/html"), resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode + }) + ), pipe(body); + }, + onShellError(error) { + reject(error); + }, + onError(error) { + responseStatusCode = 500, shellRendered && console.error(error); + } + } + ); + setTimeout(abort, ABORT_DELAY); + }); +} + +// app/root.tsx +var root_exports = {}; +__export(root_exports, { + default: () => App, + links: () => links +}); + +// css-bundle-plugin-ns:@remix-run/css-bundle +var cssBundleHref = "/build/css-bundle-RYSASN3C.css"; + +// app/root.tsx +import { + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration +} from "@remix-run/react"; + +// app/root.css.ts +var root = "root_root__o19f1u0"; + +// app/root.tsx +import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime"; +var links = () => [ + { + rel: "preconnect", + href: "https://fonts.gstatic.com", + crossOrigin: "anonymous" + }, + { + rel: "preconnect", + href: "ttps://fonts.googleapis.com", + crossOrigin: "anonymous" + }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Unbounded:wght@700&display=swap" + }, + ...cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : [] +]; +function App() { + return /* @__PURE__ */ jsxDEV2("html", { lang: "en", children: [ + /* @__PURE__ */ jsxDEV2("head", { children: [ + /* @__PURE__ */ jsxDEV2("meta", { charSet: "utf-8" }, void 0, !1, { + fileName: "app/root.tsx", + lineNumber: 37, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV2("meta", { name: "viewport", content: "width=device-width,initial-scale=1" }, void 0, !1, { + fileName: "app/root.tsx", + lineNumber: 38, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV2(Meta, {}, void 0, !1, { + fileName: "app/root.tsx", + lineNumber: 39, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV2(Links, {}, void 0, !1, { + fileName: "app/root.tsx", + lineNumber: 40, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/root.tsx", + lineNumber: 36, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV2("body", { className: root, children: [ + /* @__PURE__ */ jsxDEV2(Outlet, {}, void 0, !1, { + fileName: "app/root.tsx", + lineNumber: 43, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV2(ScrollRestoration, {}, void 0, !1, { + fileName: "app/root.tsx", + lineNumber: 44, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV2(Scripts, {}, void 0, !1, { + fileName: "app/root.tsx", + lineNumber: 45, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV2(LiveReload, {}, void 0, !1, { + fileName: "app/root.tsx", + lineNumber: 46, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/root.tsx", + lineNumber: 42, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/root.tsx", + lineNumber: 35, + columnNumber: 5 + }, this); +} + +// app/routes/debug.components.tsx +var debug_components_exports = {}; +__export(debug_components_exports, { + default: () => DebugComponents +}); +import { useState } from "react"; + +// app/components/faction-bar.tsx +import { useMemo } from "react"; + +// app/components/faction-bar.css.ts +var bar = "faction-bar_bar__prieg10", tinyBar = "faction-bar_tinyBar__prieg11", left = "faction-bar_left__prieg12", center = "faction-bar_center__prieg13", right = "faction-bar_right__prieg14"; + +// app/components/faction-bar.tsx +import { Fragment, jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime"; +var FactionBar = ({ + population: { vs, nc, tr }, + tiny +}) => { + let { vsPercent, ncPercent, trPercent } = useMemo(() => { + let total = nc + vs + tr; + return { + vsPercent: Math.round(vs / total * 100) || 0, + ncPercent: Math.round(nc / total * 100) || 0, + trPercent: Math.round(tr / total * 100) || 0 + }; + }, [vs, nc, tr]); + return /* @__PURE__ */ jsxDEV3("div", { className: tiny ? tinyBar : bar, children: [ + /* @__PURE__ */ jsxDEV3("div", { className: left, style: { flexGrow: vs + 1 }, children: tiny ? /* @__PURE__ */ jsxDEV3(Fragment, { children: "\xA0" }, void 0, !1, { + fileName: "app/components/faction-bar.tsx", + lineNumber: 23, + columnNumber: 17 + }, this) : `${vsPercent}%` }, void 0, !1, { + fileName: "app/components/faction-bar.tsx", + lineNumber: 22, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV3("div", { className: center, style: { flexGrow: nc + 1 }, children: tiny ? /* @__PURE__ */ jsxDEV3(Fragment, { children: "\xA0" }, void 0, !1, { + fileName: "app/components/faction-bar.tsx", + lineNumber: 26, + columnNumber: 17 + }, this) : `${ncPercent}%` }, void 0, !1, { + fileName: "app/components/faction-bar.tsx", + lineNumber: 25, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV3("div", { className: right, style: { flexGrow: tr + 1 }, children: tiny ? /* @__PURE__ */ jsxDEV3(Fragment, { children: "\xA0" }, void 0, !1, { + fileName: "app/components/faction-bar.tsx", + lineNumber: 29, + columnNumber: 17 + }, this) : `${trPercent}%` }, void 0, !1, { + fileName: "app/components/faction-bar.tsx", + lineNumber: 28, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/components/faction-bar.tsx", + lineNumber: 21, + columnNumber: 5 + }, this); +}; + +// app/components/faction-pie.css.ts +var pieRoot = "faction-pie_pieRoot__15tar860"; + +// app/components/faction-pie.tsx +import { jsxDEV as jsxDEV4 } from "react/jsx-dev-runtime"; +var FactionPie = ({ + population: population3, + innerMargin, + innerBackground, + size +}) => { + let { nc, tr, vs } = population3, total = nc + tr + vs, trPct = tr / total * 100, vsPct = vs / total * 100; + return /* @__PURE__ */ jsxDEV4( + "div", + { + className: pieRoot, + style: { + fontSize: size || "1em", + backgroundImage: `conic-gradient( + #d30101 0% ${trPct}%, + #991cba ${trPct}% ${trPct + vsPct}%, + #1564cc ${trPct + vsPct}% 100% + )`, + "--inner-margin": innerMargin ? `${innerMargin}px` : "0", + "--inner-bg": innerBackground || "none" + }, + children: "\xA0" + }, + void 0, + !1, + { + fileName: "app/components/faction-pie.tsx", + lineNumber: 22, + columnNumber: 5 + }, + this + ); +}; + +// app/routes/debug.components.tsx +import { jsxDEV as jsxDEV5 } from "react/jsx-dev-runtime"; +function DebugComponents() { + let [population3, setPopulation] = useState({ + nc: 33, + tr: 33, + vs: 33 + }), [innerMargin, setInnerMargin] = useState(10), [innerColor, setInnerColor] = useState("black"); + return /* @__PURE__ */ jsxDEV5("div", { children: [ + /* @__PURE__ */ jsxDEV5("h1", { children: "Debug Components" }, void 0, !1, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 17, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV5("h2", { children: "Faction Viz" }, void 0, !1, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 18, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV5("div", { children: [ + "NC", + " ", + /* @__PURE__ */ jsxDEV5( + "input", + { + type: "number", + value: population3.nc, + onChange: (e) => setPopulation((p) => ({ ...p, nc: Number(e.target.value) })) + }, + void 0, + !1, + { + fileName: "app/routes/debug.components.tsx", + lineNumber: 21, + columnNumber: 9 + }, + this + ), + " ", + "|| TR", + " ", + /* @__PURE__ */ jsxDEV5( + "input", + { + type: "number", + value: population3.tr, + onChange: (e) => setPopulation((p) => ({ ...p, tr: Number(e.target.value) })) + }, + void 0, + !1, + { + fileName: "app/routes/debug.components.tsx", + lineNumber: 29, + columnNumber: 9 + }, + this + ), + " ", + "|| VS", + " ", + /* @__PURE__ */ jsxDEV5( + "input", + { + type: "number", + value: population3.vs, + onChange: (e) => setPopulation((p) => ({ ...p, vs: Number(e.target.value) })) + }, + void 0, + !1, + { + fileName: "app/routes/debug.components.tsx", + lineNumber: 37, + columnNumber: 9 + }, + this + ) + ] }, void 0, !0, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 19, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV5("div", { children: [ + /* @__PURE__ */ jsxDEV5("h3", { children: "Horizontal Stacked Bar Chart" }, void 0, !1, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 46, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV5(FactionBar, { population: population3 }, void 0, !1, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 47, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV5("h3", { children: "Pie Chart" }, void 0, !1, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 48, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV5("div", { style: { fontSize: "5rem" }, children: [ + /* @__PURE__ */ jsxDEV5(FactionPie, { population: population3 }, void 0, !1, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 50, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV5( + FactionPie, + { + population: population3, + innerBackground: innerColor, + innerMargin + }, + void 0, + !1, + { + fileName: "app/routes/debug.components.tsx", + lineNumber: 51, + columnNumber: 11 + }, + this + ) + ] }, void 0, !0, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 49, + columnNumber: 9 + }, this), + "Inner margin", + " ", + /* @__PURE__ */ jsxDEV5( + "input", + { + type: "number", + value: innerMargin, + onChange: (e) => setInnerMargin(Number(e.target.value)) + }, + void 0, + !1, + { + fileName: "app/routes/debug.components.tsx", + lineNumber: 58, + columnNumber: 9 + }, + this + ), + "Inner color", + " ", + /* @__PURE__ */ jsxDEV5( + "input", + { + type: "color", + value: innerColor, + onChange: (e) => setInnerColor(e.target.value) + }, + void 0, + !1, + { + fileName: "app/routes/debug.components.tsx", + lineNumber: 64, + columnNumber: 9 + }, + this + ) + ] }, void 0, !0, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 45, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/debug.components.tsx", + lineNumber: 16, + columnNumber: 5 + }, this); +} + +// app/routes/worlds.$id.tsx +var worlds_id_exports = {}; +__export(worlds_id_exports, { + default: () => World, + loader: () => loader, + meta: () => meta +}); +import { json } from "@remix-run/cloudflare"; +import { useLoaderData } from "@remix-run/react"; + +// app/components/footer.tsx +import { Link } from "@remix-run/react"; + +// app/components/footer.css.ts +var root2 = "footer_root__1a7dndp0", background = "footer_background__1a7dndp1", logo = "footer_logo__1a7dndp2"; +var logoLive = "footer_logoLive__1a7dndp4", logoDot = "footer_logoDot__1a7dndp5", lowerLogo = "footer_lowerLogo__1a7dndp6", link = "footer_link__1a7dndp7"; + +// app/components/footer.tsx +import { jsxDEV as jsxDEV6 } from "react/jsx-dev-runtime"; +var Footer = ({ isMainPage }) => /* @__PURE__ */ jsxDEV6("footer", { children: /* @__PURE__ */ jsxDEV6("div", { className: root2, children: [ + /* @__PURE__ */ jsxDEV6("div", { className: background }, void 0, !1, { + fileName: "app/components/footer.tsx", + lineNumber: 7, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV6("div", { className: logo, children: [ + "PS2", + /* @__PURE__ */ jsxDEV6("div", { className: logoDot }, void 0, !1, { + fileName: "app/components/footer.tsx", + lineNumber: 10, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV6("span", { className: logoLive, children: "LIVE" }, void 0, !1, { + fileName: "app/components/footer.tsx", + lineNumber: 11, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV6("div", { className: lowerLogo, children: [ + /* @__PURE__ */ jsxDEV6("div", { children: isMainPage ? /* @__PURE__ */ jsxDEV6(Link, { className: link, to: "/about", children: "more stuff \xBB" }, void 0, !1, { + fileName: "app/components/footer.tsx", + lineNumber: 15, + columnNumber: 11 + }, this) : /* @__PURE__ */ jsxDEV6(Link, { className: link, to: "/", children: "less stuff \xBB" }, void 0, !1, { + fileName: "app/components/footer.tsx", + lineNumber: 19, + columnNumber: 11 + }, this) }, void 0, !1, { + fileName: "app/components/footer.tsx", + lineNumber: 13, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV6("div", { children: [ + "\xA9 ", + (/* @__PURE__ */ new Date()).getFullYear() + ] }, void 0, !0, { + fileName: "app/components/footer.tsx", + lineNumber: 24, + columnNumber: 11 + }, this) + ] }, void 0, !0, { + fileName: "app/components/footer.tsx", + lineNumber: 12, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/components/footer.tsx", + lineNumber: 8, + columnNumber: 7 + }, this) +] }, void 0, !0, { + fileName: "app/components/footer.tsx", + lineNumber: 6, + columnNumber: 5 +}, this) }, void 0, !1, { + fileName: "app/components/footer.tsx", + lineNumber: 5, + columnNumber: 1 +}, this); + +// app/utils/metagame.ts +var fetchMetagameWorlds = async () => await (await fetch("https://metagame.ps2.live/all")).json(), fetchSingleMetagameWorld = async (id) => await (await fetch(`https://metagame.ps2.live/${id}`)).json(); + +// app/utils/saerro.ts +var saerroFetch = async (query) => (await (await fetch( + `https://saerro.ps2.live/graphql?query=${query}`, + { + cf: { + cacheTtl: 60 + } + } +)).json()).data, allVehicles = [ + "flash", + "sunderer", + "lightning", + "scythe", + "vanguard", + "prowler", + "reaver", + "mosquito", + "galaxy", + "valkyrie", + "liberator", + "ant", + "harasser", + "dervish", + "chimera", + "javelin", + "corsair", + "magrider" +], allClasses = [ + "infiltrator", + "lightAssault", + "combatMedic", + "engineer", + "heavyAssault", + "max" +], worldQuery = async (worldID) => { + let query = `{ + world(by: {id: ${Number(worldID)}}) { + id + population { + nc + tr + vs + } + zones { + all { + id + classes { + ${allClasses.map((cls) => `${cls} { total nc tr vs }`).join(" ")} + } + vehicles { + total + ${allVehicles.map((vehicle) => `${vehicle} { total nc tr vs }`).join(" ")} + } + population { + nc + tr + vs + } + } + } + } + }`; + return await saerroFetch(query); +}, totalPopulation = ({ nc, vs, tr }) => nc + vs + tr; + +// app/utils/strings.ts +var toTitleCase = (str) => str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()), pascalCaseToTitleCase = (str) => toTitleCase(str.replace(/([A-Z])/g, " $1")), snakeCaseToTitleCase = (str) => toTitleCase(str.replace(/_/g, " ")), humanTimeAgo = (ms, full) => { + let millis = Math.floor(ms % 1e3), seconds = Math.floor(ms / 1e3), minutes = Math.floor(seconds / 60), hours = Math.floor(minutes / 60); + return hours > 0 ? full ? `${hours}h ${minutes % 60}m ${seconds % 60}s` : `${hours}h` : minutes > 0 ? full ? `${minutes}m ${seconds % 60}s` : `${minutes}m` : seconds > 0 ? `${seconds}s` : `${millis}ms`; +}, worlds = { + 1: { + name: "Connery", + timeZone: "America/Los_Angeles", + locale: "en-US", + location: "US-W", + platform: "PC" + }, + 10: { + name: "Miller", + timeZone: "UTC", + locale: "en-GB", + location: "EU", + platform: "PC" + }, + 13: { + name: "Cobalt", + timeZone: "UTC", + locale: "en-GB", + location: "EU", + platform: "PC" + }, + 17: { + name: "Emerald", + timeZone: "America/New_York", + locale: "en-US", + location: "US-E", + platform: "PC" + }, + 19: { + name: "Jaeger", + timeZone: "America/New_York", + locale: "en-US", + location: "US-E", + platform: "PC" + }, + 40: { + name: "SolTech", + timeZone: "Asia/Tokyo", + locale: "en-GB", + location: "JP", + platform: "PC" + }, + 1e3: { + name: "Genudine", + timeZone: "America/New_York", + locale: "en-US", + location: "US-E", + platform: "PS4" + }, + 2e3: { + name: "Ceres", + timeZone: "UTC", + locale: "en-GB", + location: "EU", + platform: "PS4" + }, + default: { + name: "Unknown", + timeZone: "UTC", + locale: "en-US", + location: "???", + platform: "???" + } +}, zones = { + 2: { + name: "Indar", + colors: ["#edb96b", "#964c2f"] + }, + 4: { + name: "Hossin", + colors: ["#47570d", "#7b9c05"] + }, + 6: { + name: "Amerish", + colors: ["#87a12a", "#5f634f"] + }, + 8: { + name: "Esamir", + colors: ["#d5f3f5", "#a1c7e6"] + }, + 344: { + name: "Oshur", + colors: ["#00c2bf", "#174185"] + }, + default: { + name: "Unknown", + colors: ["#111111", "#cccccc"] + } +}; + +// app/components/world.css.ts +var headerFont = "world_headerFont__cpwhdm0", header = "world_header__cpwhdm1", headerName = "world_headerName__cpwhdm2", headerSub = "world_headerSub__cpwhdm3", outer = "world_outer__cpwhdm4", population = "world_population__cpwhdm5", populationHead = "world_populationHead__cpwhdm6", popNumbers = "world_popNumbers__cpwhdm7", popItem = "world_popItem__cpwhdm8", totalPop = "world_totalPop__cpwhdm9", headerConts = "world_headerConts__cpwhdma"; +var cont = "world_cont__cpwhdmc", contSub = "world_contSub__cpwhdmd"; + +// app/utils/classes.ts +var c = (...args) => args.filter((x) => !!x).join(" "); + +// app/components/index-world.css.ts +var container = "index-world_container__1e3z7iu0", header2 = "index-world_header__1e3z7iu1", headerName2 = "index-world_headerName__1e3z7iu2", headerDetailsLink = "index-world_headerDetailsLink__1e3z7iu3", headerMarkers = "index-world_headerMarkers__1e3z7iu4"; +var details = "index-world_details__1e3z7iu6", population2 = "index-world_population__1e3z7iu7", popFaction = "index-world_popFaction__1e3z7iu8", popImage = "index-world_popImage__1e3z7iu9", totalPop2 = "index-world_totalPop__1e3z7iua", continent = "index-world_continent__1e3z7iub", contBars = "index-world_contBars__1e3z7iuc", contBarTitle = "index-world_contBarTitle__1e3z7iud", barSeparator = "index-world_barSeparator__1e3z7iue", contCircle = "index-world_contCircle__1e3z7iuf", contName = "index-world_contName__1e3z7iug", jaegerConts = "index-world_jaegerConts__1e3z7iuh"; +var nextCont = "index-world_nextCont__1e3z7ium", nextContText = "index-world_nextContText__1e3z7iun", oopsies = "index-world_oopsies__1e3z7iuo", oopsiesSpin = "index-world_oopsiesSpin__1e3z7iuq"; + +// app/images/vs-100.png +var vs_100_default = "/build/_assets/vs-100-5TO3XQQ2.png"; + +// app/images/nc-100.png +var nc_100_default = "/build/_assets/nc-100-22JVQEND.png"; + +// app/images/tr-100.png +var tr_100_default = "/build/_assets/tr-100-MTGS5ODT.png"; + +// app/components/alert-timer.tsx +import { useEffect, useState as useState2 } from "react"; + +// app/components/alert-timer.css.ts +var alertDot = "alert-timer_alertDot__eh4xj51", timer = "alert-timer_timer__eh4xj52"; + +// app/components/alert-timer.tsx +import { Fragment as Fragment2, jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime"; +var endTime = (alert) => { + let alertDurationMins = 90; + switch (alert.alert_type) { + case "air": + alertDurationMins = 30; + break; + case "sudden_death": + case "max": + alertDurationMins = 15; + break; + default: + break; + } + return new Date(alert.start_time).getTime() + alertDurationMins * 60 * 1e3; +}, timeLeftString = (alert) => { + if (alert) { + let time = endTime(alert) - Date.now(); + if (time < 2e3) + return /* @__PURE__ */ jsxDEV7(Fragment2, { children: "JUST ENDED" }, void 0, !1, { + fileName: "app/components/alert-timer.tsx", + lineNumber: 30, + columnNumber: 14 + }, this); + let speed = time < 1e3 * 60 * 15 ? "1s" : "4s"; + return /* @__PURE__ */ jsxDEV7(Fragment2, { children: [ + humanTimeAgo(time, !0).toUpperCase(), + " LEFT", + " ", + /* @__PURE__ */ jsxDEV7( + "div", + { + className: alertDot, + style: { "--speed": speed } + }, + void 0, + !1, + { + fileName: "app/components/alert-timer.tsx", + lineNumber: 38, + columnNumber: 9 + }, + this + ) + ] }, void 0, !0, { + fileName: "app/components/alert-timer.tsx", + lineNumber: 36, + columnNumber: 7 + }, this); + } else + return /* @__PURE__ */ jsxDEV7(Fragment2, {}, void 0, !1, { + fileName: "app/components/alert-timer.tsx", + lineNumber: 45, + columnNumber: 12 + }, this); +}, AlertTimer = ({ + alert +}) => { + let [timeLeft, setTimeLeft] = useState2(timeLeftString(alert)); + return useEffect(() => { + if (alert) { + let interval = setInterval(() => { + setTimeLeft(timeLeftString(alert)); + }, 1e3); + return () => clearInterval(interval); + } + }, [alert]), /* @__PURE__ */ jsxDEV7("div", { className: timer, children: timeLeft }, void 0, !1, { + fileName: "app/components/alert-timer.tsx", + lineNumber: 65, + columnNumber: 10 + }, this); +}; + +// app/utils/sorting.ts +var contPrioritySort = (a, b) => a.locked && !b.locked ? 1 : !a.locked && b.locked ? -1 : a.alert && b.alert ? Date.parse(a.alert.start_time) - Date.parse(b.alert.start_time) : a.alert ? -1 : b.alert ? 1 : a.locked_since && b.locked_since ? Date.parse(a.locked_since) - Date.parse(b.locked_since) : a.locked_since ? -1 : b.locked_since ? 1 : 0; + +// app/routes/worlds.$id.tsx +import { Fragment as Fragment3, jsxDEV as jsxDEV8 } from "react/jsx-dev-runtime"; +async function loader({ params }) { + let [saerro, metagame] = await Promise.all( + [ + worldQuery(params.id), + fetchSingleMetagameWorld(params.id) + ] + ); + return json({ saerro, metagame, id: params.id }); +} +var meta = ({ data }) => { + let { saerro, id } = data, date = /* @__PURE__ */ new Date(), worldInfo = worlds[String(id || "default")], datetimeHumanFriendly = date.toLocaleString(worldInfo.locale, { + timeZone: worldInfo.timeZone, + dateStyle: "medium", + timeStyle: "short" + }); + return [ + { + title: `${worldInfo.name || "Unknown world"} | PlanetSide 2 Live Population Stats` + }, + { + name: "description", + content: `${worldInfo.name} currently has ${totalPopulation( + saerro.world.population + )} players online as of ${datetimeHumanFriendly} ${worldInfo.name} time. VS: ${saerro.world.population.vs}, NC: ${saerro.world.population.nc}, TR: ${saerro.world.population.tr} -- See more detailed stats on ps2.live.` + } + ]; +}; +function World() { + let { + saerro: { world }, + id, + metagame + } = useLoaderData(), worldInfo = worlds[String(id || "default")], nextZoneID = metagame.zones.length !== 0 ? metagame.zones.sort( + (a, b) => new Date(a.locked_since ?? Date.now()).getTime() - new Date(b.locked_since ?? Date.now()).getTime() + )[0].id : 0; + return /* @__PURE__ */ jsxDEV8(Fragment3, { children: [ + /* @__PURE__ */ jsxDEV8("div", { className: outer, children: /* @__PURE__ */ jsxDEV8("div", { children: [ + /* @__PURE__ */ jsxDEV8("div", { className: header, children: [ + /* @__PURE__ */ jsxDEV8("div", { className: c(headerName, headerFont), children: [ + /* @__PURE__ */ jsxDEV8("div", { children: worldInfo.name.toUpperCase() }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 98, + columnNumber: 15 + }, this), + /* @__PURE__ */ jsxDEV8("div", { className: headerSub, children: [ + "[", + worldInfo.location, + "] [", + worldInfo.platform, + "]" + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 99, + columnNumber: 15 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 97, + columnNumber: 13 + }, this), + /* @__PURE__ */ jsxDEV8("div", { className: populationHead, children: [ + /* @__PURE__ */ jsxDEV8("div", { className: headerFont, children: [ + /* @__PURE__ */ jsxDEV8("div", { className: totalPop, children: totalPopulation(world.population).toLocaleString() }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 105, + columnNumber: 17 + }, this), + "PLAYERS" + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 104, + columnNumber: 15 + }, this), + /* @__PURE__ */ jsxDEV8("div", { className: population, children: [ + /* @__PURE__ */ jsxDEV8("div", { className: popNumbers, children: [ + /* @__PURE__ */ jsxDEV8( + "div", + { + className: popItem, + style: { flex: world.population.vs + 1 }, + children: [ + /* @__PURE__ */ jsxDEV8("img", { className: popImage, src: vs_100_default, alt: "VS" }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 116, + columnNumber: 21 + }, this), + " ", + world.population.vs + ] + }, + void 0, + !0, + { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 112, + columnNumber: 19 + }, + this + ), + /* @__PURE__ */ jsxDEV8( + "div", + { + className: popItem, + style: { flex: world.population.nc + 1 }, + children: [ + /* @__PURE__ */ jsxDEV8("img", { className: popImage, src: nc_100_default, alt: "NC" }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 123, + columnNumber: 21 + }, this), + " ", + world.population.nc + ] + }, + void 0, + !0, + { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 119, + columnNumber: 19 + }, + this + ), + /* @__PURE__ */ jsxDEV8( + "div", + { + className: popItem, + style: { flex: world.population.tr + 1 }, + children: [ + /* @__PURE__ */ jsxDEV8("img", { className: popImage, src: tr_100_default, alt: "TR" }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 130, + columnNumber: 21 + }, this), + " ", + world.population.tr + ] + }, + void 0, + !0, + { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 126, + columnNumber: 19 + }, + this + ) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 111, + columnNumber: 17 + }, this), + /* @__PURE__ */ jsxDEV8(FactionBar, { population: world.population }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 134, + columnNumber: 17 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 110, + columnNumber: 15 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 103, + columnNumber: 13 + }, this), + /* @__PURE__ */ jsxDEV8("div", { className: headerConts, children: [ + /* @__PURE__ */ jsxDEV8("div", { className: headerSub, children: "CONTINENT CONTROL" }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 138, + columnNumber: 15 + }, this), + metagame.zones.sort(contPrioritySort).map((zone, idx) => { + let zoneInfo = zones[String(zone.id)]; + return /* @__PURE__ */ jsxDEV8("div", { className: cont, children: [ + /* @__PURE__ */ jsxDEV8("div", { style: { flex: 0 }, children: zoneInfo.name.toUpperCase() }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 143, + columnNumber: 21 + }, this), + /* @__PURE__ */ jsxDEV8("div", { style: { flex: 1 }, children: /* @__PURE__ */ jsxDEV8( + FactionPie, + { + size: "4rem", + population: zone.alert?.percentages ?? zone.territory, + innerBackground: `linear-gradient(45deg, ${zoneInfo.colors[0]}, ${zoneInfo.colors[1]})`, + innerMargin: 10 + }, + void 0, + !1, + { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 145, + columnNumber: 23 + }, + this + ) }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 144, + columnNumber: 21 + }, this), + /* @__PURE__ */ jsxDEV8("div", { className: contSub, children: zone.alert ? /* @__PURE__ */ jsxDEV8(AlertTimer, { alert: zone.alert }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 154, + columnNumber: 23 + }, this) : zone.locked ? nextZoneID == zone.id ? /* @__PURE__ */ jsxDEV8(Fragment3, { children: "NEXT UP \xBB" }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 157, + columnNumber: 23 + }, this) : /* @__PURE__ */ jsxDEV8(Fragment3, { children: "LOCKED" }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 159, + columnNumber: 23 + }, this) : /* @__PURE__ */ jsxDEV8(Fragment3, { children: "UNLOCKED" }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 162, + columnNumber: 23 + }, this) }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 152, + columnNumber: 21 + }, this) + ] }, idx, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 142, + columnNumber: 19 + }, this); + }) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 137, + columnNumber: 13 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 96, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV8("div", { children: [ + /* @__PURE__ */ jsxDEV8("h2", { children: "Continents" }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 171, + columnNumber: 13 + }, this), + world.zones.all.map( + (zone) => /* @__PURE__ */ jsxDEV8(ZoneInfo, { zone }, zone.id, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 173, + columnNumber: 13 + }, this) + ) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 170, + columnNumber: 11 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 95, + columnNumber: 9 + }, this) }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 94, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV8(Footer, { isMainPage: !0 }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 178, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 93, + columnNumber: 5 + }, this); +} +var ZoneInfo = ({ zone }) => { + let zoneInfo = zones[String(zone.id)]; + return /* @__PURE__ */ jsxDEV8("section", { children: [ + /* @__PURE__ */ jsxDEV8("h3", { children: zoneInfo.name }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 187, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV8("p", { children: [ + totalPopulation(zone.population), + " players (", + zone.population.vs, + " VS,", + " ", + zone.population.nc, + " NC, ", + zone.population.tr, + " TR)" + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 188, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV8("p", { children: /* @__PURE__ */ jsxDEV8("ul", { children: allClasses.map( + (cls, idx) => /* @__PURE__ */ jsxDEV8("li", { children: [ + /* @__PURE__ */ jsxDEV8("b", { children: pascalCaseToTitleCase(cls) }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 196, + columnNumber: 15 + }, this), + ": ", + zone.classes?.[cls].total, + " ", + "total, ", + zone.classes?.[cls].vs, + " VS, ", + zone.classes?.[cls].nc, + " NC,", + " ", + zone.classes?.[cls].tr, + " TR" + ] }, idx, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 195, + columnNumber: 11 + }, this) + ) }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 193, + columnNumber: 9 + }, this) }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 192, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV8("p", { children: [ + totalPopulation(zone.vehicles), + " vehicles...", + /* @__PURE__ */ jsxDEV8("ul", { children: allVehicles.map( + (vehicle, idx) => /* @__PURE__ */ jsxDEV8("li", { children: [ + /* @__PURE__ */ jsxDEV8("b", { children: toTitleCase(vehicle) }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 208, + columnNumber: 15 + }, this), + ":", + " ", + totalPopulation(zone.vehicles?.[vehicle]), + " total,", + " ", + zone.vehicles?.[vehicle].vs, + " VS, ", + zone.vehicles?.[vehicle].nc, + " ", + "NC, ", + zone.vehicles?.[vehicle].tr, + " TR" + ] }, idx, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 207, + columnNumber: 11 + }, this) + ) }, void 0, !1, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 205, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 203, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/worlds.$id.tsx", + lineNumber: 186, + columnNumber: 5 + }, this); +}; + +// app/routes/_index.tsx +var index_exports = {}; +__export(index_exports, { + default: () => Index, + loader: () => loader2, + meta: () => meta2 +}); +import { json as json2 } from "@remix-run/cloudflare"; +import { useLoaderData as useLoaderData2 } from "@remix-run/react"; + +// app/components/index-world.tsx +import { Link as Link2 } from "@remix-run/react"; +import { Fragment as Fragment4, jsxDEV as jsxDEV9 } from "react/jsx-dev-runtime"; +var IndexWorld = ({ metagame, population: population3 }) => { + let worldId = metagame.id, { platform, location, name } = worlds[String(worldId || "default")]; + if (metagame.zones.length === 0) + return /* @__PURE__ */ jsxDEV9(BrokenWorld, { worldId }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 29, + columnNumber: 12 + }, this); + let nextZone = metagame.zones.sort( + (a, b) => new Date(a.locked_since ?? Date.now()).getTime() - new Date(b.locked_since ?? Date.now()).getTime() + )[0], nextZoneStrings = zones[nextZone.id]; + return /* @__PURE__ */ jsxDEV9("div", { className: container, children: [ + /* @__PURE__ */ jsxDEV9(Link2, { to: `/worlds/${worldId}`, className: header2, children: [ + /* @__PURE__ */ jsxDEV9("div", { className: headerName2, children: name }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 42, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: headerMarkers, children: [ + "[", + location, + "] [", + platform, + "]", + " " + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 43, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: headerDetailsLink, children: "DETAILS \u21E8" }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 46, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 41, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: details, children: [ + /* @__PURE__ */ jsxDEV9("div", { className: population2, children: [ + /* @__PURE__ */ jsxDEV9("div", { className: totalPop2, children: population3.factions.vs + population3.factions.nc + population3.factions.tr }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 50, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: popFaction, children: [ + /* @__PURE__ */ jsxDEV9("img", { className: popImage, src: vs_100_default, alt: "VS" }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 56, + columnNumber: 13 + }, this), + " ", + population3.factions.vs + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 55, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: popFaction, children: [ + /* @__PURE__ */ jsxDEV9("img", { className: popImage, src: nc_100_default, alt: "NC" }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 60, + columnNumber: 13 + }, this), + " ", + population3.factions.nc + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 59, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: popFaction, children: [ + /* @__PURE__ */ jsxDEV9("img", { className: popImage, src: tr_100_default, alt: "TR" }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 64, + columnNumber: 13 + }, this), + " ", + population3.factions.tr + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 63, + columnNumber: 11 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 49, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV9(FactionBar, { population: population3.factions }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 68, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 48, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: c(worldId === 19 && jaegerConts), children: [ + metagame.zones.filter((zone) => !zone.locked).sort((a, b) => a.alert && !b.alert ? -1 : b.alert && !a.alert ? 1 : 0).map((zone) => worldId !== 19 ? /* @__PURE__ */ jsxDEV9(Continent, { zone }, zone.id, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 78, + columnNumber: 11 + }, this) : /* @__PURE__ */ jsxDEV9(JaegerContinent, { zone }, zone.id, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 80, + columnNumber: 11 + }, this)), + worldId !== 19 && /* @__PURE__ */ jsxDEV9("div", { className: nextCont, children: [ + /* @__PURE__ */ jsxDEV9("div", { className: nextContText, children: "Next continent \xBB" }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 85, + columnNumber: 13 + }, this), + " ", + /* @__PURE__ */ jsxDEV9("div", { className: contName, children: [ + /* @__PURE__ */ jsxDEV9( + "div", + { + className: contCircle, + style: { + "--upper-color": nextZoneStrings.colors[0], + "--lower-color": nextZoneStrings.colors[1] + } + }, + void 0, + !1, + { + fileName: "app/components/index-world.tsx", + lineNumber: 87, + columnNumber: 15 + }, + this + ), + /* @__PURE__ */ jsxDEV9("div", { children: nextZoneStrings.name }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 96, + columnNumber: 15 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 86, + columnNumber: 13 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 84, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 70, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 40, + columnNumber: 5 + }, this); +}, JaegerContinent = ({ zone }) => { + let { + name, + colors: [upper, lower] + } = zones[zone.id]; + return /* @__PURE__ */ jsxDEV9("div", { className: contName, children: [ + /* @__PURE__ */ jsxDEV9( + "div", + { + className: contCircle, + style: { + "--upper-color": upper, + "--lower-color": lower + } + }, + void 0, + !1, + { + fileName: "app/components/index-world.tsx", + lineNumber: 112, + columnNumber: 7 + }, + this + ), + /* @__PURE__ */ jsxDEV9("div", { children: name }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 121, + columnNumber: 7 + }, this) + ] }, zone.id, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 111, + columnNumber: 5 + }, this); +}, Continent = ({ zone }) => { + let { + name, + colors: [upper, lower] + } = zones[zone.id]; + return /* @__PURE__ */ jsxDEV9("div", { className: c(continent), children: [ + /* @__PURE__ */ jsxDEV9("div", { className: contName, children: [ + /* @__PURE__ */ jsxDEV9( + "div", + { + className: contCircle, + style: { + "--upper-color": upper, + "--lower-color": lower + } + }, + void 0, + !1, + { + fileName: "app/components/index-world.tsx", + lineNumber: 135, + columnNumber: 9 + }, + this + ), + /* @__PURE__ */ jsxDEV9("div", { children: name }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 144, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 134, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: contBars, children: [ + /* @__PURE__ */ jsxDEV9("div", { children: [ + /* @__PURE__ */ jsxDEV9("div", { className: contBarTitle, children: "TERRITORY CONTROL" }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 148, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV9(FactionBar, { population: zone.territory }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 149, + columnNumber: 11 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 147, + columnNumber: 9 + }, this), + zone.alert && /* @__PURE__ */ jsxDEV9(Fragment4, { children: [ + /* @__PURE__ */ jsxDEV9("div", { className: barSeparator }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 153, + columnNumber: 13 + }, this), + /* @__PURE__ */ jsxDEV9("div", { children: [ + /* @__PURE__ */ jsxDEV9("div", { className: contBarTitle, children: [ + /* @__PURE__ */ jsxDEV9("div", { children: [ + snakeCaseToTitleCase(zone.alert.alert_type).toUpperCase(), + " ", + "ALERT PROGRESS" + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 156, + columnNumber: 17 + }, this), + " ", + /* @__PURE__ */ jsxDEV9("div", { children: [ + /* @__PURE__ */ jsxDEV9(AlertTimer, { alert: zone.alert }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 161, + columnNumber: 19 + }, this), + " " + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 160, + columnNumber: 17 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 155, + columnNumber: 15 + }, this), + /* @__PURE__ */ jsxDEV9(FactionBar, { population: zone.alert.percentages }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 164, + columnNumber: 15 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 154, + columnNumber: 13 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 152, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 146, + columnNumber: 7 + }, this) + ] }, zone.id, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 133, + columnNumber: 5 + }, this); +}, BrokenWorld = ({ worldId }) => { + let { platform, location, name } = worlds[String(worldId || "default")]; + return /* @__PURE__ */ jsxDEV9("div", { className: container, children: [ + /* @__PURE__ */ jsxDEV9(Link2, { to: `/worlds/${worldId}`, className: header2, children: [ + /* @__PURE__ */ jsxDEV9("div", { className: headerName2, children: name }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 179, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: headerMarkers, children: [ + "[", + location, + "] [", + platform, + "]", + " " + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 180, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: headerDetailsLink, children: "DETAILS \u21E8" }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 183, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 178, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: details, children: /* @__PURE__ */ jsxDEV9("div", { className: oopsies, children: [ + "Daybreak made an oopsie.", + /* @__PURE__ */ jsxDEV9("br", {}, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 188, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV9("div", { className: oopsiesSpin, children: "\u{1F642}" }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 189, + columnNumber: 11 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 186, + columnNumber: 9 + }, this) }, void 0, !1, { + fileName: "app/components/index-world.tsx", + lineNumber: 185, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/components/index-world.tsx", + lineNumber: 177, + columnNumber: 5 + }, this); +}; + +// app/components/index-world-container.css.ts +var container2 = "index-world-container_container__1ib388g0"; + +// app/components/index-world-container.tsx +import { jsxDEV as jsxDEV10 } from "react/jsx-dev-runtime"; +var WorldContainer = ({ + metagame, + population: population3 +}) => /* @__PURE__ */ jsxDEV10("div", { className: container2, children: metagame.map( + (world) => /* @__PURE__ */ jsxDEV10( + IndexWorld, + { + metagame: world, + population: population3.find((p) => p.id === world.id) + }, + world.id, + !1, + { + fileName: "app/components/index-world-container.tsx", + lineNumber: 15, + columnNumber: 3 + }, + this + ) +) }, void 0, !1, { + fileName: "app/components/index-world-container.tsx", + lineNumber: 13, + columnNumber: 1 +}, this); + +// app/components/index.css.ts +var outer2 = "components_outer__8hh0bp0"; + +// app/utils/population.ts +var fetchPopulationWorlds = async () => (await (await fetch("https://agg.ps2.live/population/all")).json()).map(({ id, average, factions }) => ({ id, average, factions })); + +// app/routes/_index.tsx +import { jsxDEV as jsxDEV11 } from "react/jsx-dev-runtime"; +var loader2 = async () => { + let [metagame, population3] = await Promise.all( + [ + fetchMetagameWorlds(), + fetchPopulationWorlds() + ] + ); + return json2({ metagame: metagame.sort((a, b) => a.id - b.id), population: population3 }); +}, meta2 = () => [ + { title: "PS2.LIVE" }, + { + name: "description", + content: "PlanetSide 2 Live Population Stats" + } +]; +function Index() { + let data = useLoaderData2(); + return /* @__PURE__ */ jsxDEV11("div", { children: [ + /* @__PURE__ */ jsxDEV11("div", { className: outer2, children: /* @__PURE__ */ jsxDEV11(WorldContainer, { metagame: data.metagame, population: data.population }, void 0, !1, { + fileName: "app/routes/_index.tsx", + lineNumber: 33, + columnNumber: 9 + }, this) }, void 0, !1, { + fileName: "app/routes/_index.tsx", + lineNumber: 32, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV11(Footer, { isMainPage: !0 }, void 0, !1, { + fileName: "app/routes/_index.tsx", + lineNumber: 35, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/_index.tsx", + lineNumber: 31, + columnNumber: 5 + }, this); +} + +// app/routes/about.tsx +var about_exports = {}; +__export(about_exports, { + default: () => About +}); + +// app/components/about.css.ts +var header3 = "about_header__wg0hcp0", outer3 = "about_outer__wg0hcp1", link2 = "about_link__wg0hcp2", itemContainer = "about_itemContainer__wg0hcp3", item = "about_item__wg0hcp4", itemLink = "about_itemLink__wg0hcp5", itemGithubLink = "about_itemGithubLink__wg0hcp6", love = "about_love__wg0hcp7"; + +// app/routes/about.tsx +import { jsxDEV as jsxDEV12 } from "react/jsx-dev-runtime"; +function About() { + return /* @__PURE__ */ jsxDEV12("div", { children: [ + /* @__PURE__ */ jsxDEV12("div", { className: outer3, children: [ + /* @__PURE__ */ jsxDEV12("div", { children: [ + /* @__PURE__ */ jsxDEV12("p", { className: header3, children: [ + /* @__PURE__ */ jsxDEV12("b", { children: "PS2.LIVE" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 19, + columnNumber: 13 + }, this), + " is a network of services that report on the ongoing war on Auraxis." + ] }, void 0, !0, { + fileName: "app/routes/about.tsx", + lineNumber: 18, + columnNumber: 11 + }, this), + /* @__PURE__ */ jsxDEV12("p", { style: { fontStyle: "italic" }, children: [ + "hat tips:", + " ", + /* @__PURE__ */ jsxDEV12("a", { className: link2, href: "https://ps2.fisu.pw/", children: "fisu" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 24, + columnNumber: 13 + }, this), + ",", + " ", + /* @__PURE__ */ jsxDEV12("a", { className: link2, href: "https://wt.honu.pw/", children: "honu & varunda" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 28, + columnNumber: 13 + }, this), + ",", + " ", + /* @__PURE__ */ jsxDEV12("a", { className: link2, href: "https://voidwell.com/", children: "Voidwell & Lampjaw" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 32, + columnNumber: 13 + }, this), + ",", + " ", + /* @__PURE__ */ jsxDEV12("a", { className: link2, href: "https://census.lithafalcon.cc/", children: "Sanctuary & Falcon" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 36, + columnNumber: 13 + }, this), + ",", + " ", + /* @__PURE__ */ jsxDEV12("a", { className: link2, href: "https://ps2alerts.com/", children: "PS2Alerts team" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 40, + columnNumber: 13 + }, this), + ",", + " ", + /* @__PURE__ */ jsxDEV12("a", { className: link2, href: "https://discord.gg/yVzGEg3RKV", children: "PS2devs Discord" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 44, + columnNumber: 13 + }, this), + ", Daybreak Census Team \u{1F496}" + ] }, void 0, !0, { + fileName: "app/routes/about.tsx", + lineNumber: 22, + columnNumber: 11 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/about.tsx", + lineNumber: 17, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV12("div", { children: /* @__PURE__ */ jsxDEV12("ul", { className: itemContainer, children: [ + { + name: "Saerro", + url: "https://saerro.ps2.live", + github: "https://github.com/genudine/saerro", + description: "Population GraphQL API focussing on deep granularity." + }, + { + name: "Metagame API", + url: "https://metagame.ps2.live", + github: "https://github.com/genudine/metagame", + description: "World states, contininent locks, alerts, etc." + }, + { + name: "Population API", + url: "https://agg.ps2.live/population", + github: "https://github.com/genudine/agg-population", + description: "Population as seen by many services, averaged." + }, + { + name: "Census Playground", + url: "https://try.ps2.live", + github: "https://github.com/genudine/try.ps2.live", + description: "Explore and share the Census API." + }, + { + name: "ps2.live", + url: "https://ps2.live", + github: "https://github.com/genudine/ps2.live", + description: "This website. It's pretty cool." + }, + { + name: "Medkit", + url: "https://github.com/genudine/medkit2", + github: "https://github.com/genudine/medkit2", + description: "PS2 Discord bot for population/continents in channel names." + } + ].map( + ({ name, url, github, description }, i) => /* @__PURE__ */ jsxDEV12("li", { className: item, children: [ + /* @__PURE__ */ jsxDEV12("a", { href: url, className: itemLink, children: name }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 93, + columnNumber: 17 + }, this), + /* @__PURE__ */ jsxDEV12("div", { children: [ + description, + " " + ] }, void 0, !0, { + fileName: "app/routes/about.tsx", + lineNumber: 96, + columnNumber: 17 + }, this), + /* @__PURE__ */ jsxDEV12("a", { href: github, className: itemGithubLink, children: "github" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 97, + columnNumber: 17 + }, this) + ] }, i, !0, { + fileName: "app/routes/about.tsx", + lineNumber: 92, + columnNumber: 13 + }, this) + ) }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 51, + columnNumber: 11 + }, this) }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 50, + columnNumber: 9 + }, this), + /* @__PURE__ */ jsxDEV12("p", { className: love, children: "Built with \u{1F496} by Doll" }, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 104, + columnNumber: 9 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/about.tsx", + lineNumber: 16, + columnNumber: 7 + }, this), + /* @__PURE__ */ jsxDEV12(Footer, {}, void 0, !1, { + fileName: "app/routes/about.tsx", + lineNumber: 106, + columnNumber: 7 + }, this) + ] }, void 0, !0, { + fileName: "app/routes/about.tsx", + lineNumber: 15, + columnNumber: 5 + }, this); +} + +// server-assets-manifest:@remix-run/dev/assets-manifest +var assets_manifest_default = { entry: { module: "/build/entry.client-ZTY5EYA2.js", imports: ["/build/_shared/chunk-O4BRYNJ4.js", "/build/_shared/chunk-Q3VAUTNA.js", "/build/_shared/chunk-U4FRFQSK.js", "/build/_shared/chunk-D6I44YFN.js", "/build/_shared/chunk-UWV35TSL.js", "/build/_shared/chunk-XGOTYLZ5.js", "/build/_shared/chunk-7M6SC7J5.js", "/build/_shared/chunk-PNG5AS42.js"] }, routes: { root: { id: "root", parentId: void 0, path: "", index: void 0, caseSensitive: void 0, module: "/build/root-LK675L7D.js", imports: void 0, hasAction: !1, hasLoader: !1, hasClientAction: !1, hasClientLoader: !1, hasErrorBoundary: !1 }, "routes/_index": { id: "routes/_index", parentId: "root", path: void 0, index: !0, caseSensitive: void 0, module: "/build/routes/_index-QVFHENLU.js", imports: ["/build/_shared/chunk-RPE3GBUS.js", "/build/_shared/chunk-UFMODTTP.js", "/build/_shared/chunk-AKBCVZVU.js"], hasAction: !1, hasLoader: !0, hasClientAction: !1, hasClientLoader: !1, hasErrorBoundary: !1 }, "routes/about": { id: "routes/about", parentId: "root", path: "about", index: void 0, caseSensitive: void 0, module: "/build/routes/about-NZRSS7UU.js", imports: ["/build/_shared/chunk-AKBCVZVU.js"], hasAction: !1, hasLoader: !1, hasClientAction: !1, hasClientLoader: !1, hasErrorBoundary: !1 }, "routes/debug.components": { id: "routes/debug.components", parentId: "root", path: "debug/components", index: void 0, caseSensitive: void 0, module: "/build/routes/debug.components-JC3GMEKW.js", imports: ["/build/_shared/chunk-YKZC3E7O.js", "/build/_shared/chunk-UFMODTTP.js"], hasAction: !1, hasLoader: !1, hasClientAction: !1, hasClientLoader: !1, hasErrorBoundary: !1 }, "routes/worlds.$id": { id: "routes/worlds.$id", parentId: "root", path: "worlds/:id", index: void 0, caseSensitive: void 0, module: "/build/routes/worlds.$id-S4Z76SWU.js", imports: ["/build/_shared/chunk-YKZC3E7O.js", "/build/_shared/chunk-RPE3GBUS.js", "/build/_shared/chunk-UFMODTTP.js", "/build/_shared/chunk-AKBCVZVU.js"], hasAction: !1, hasLoader: !0, hasClientAction: !1, hasClientLoader: !1, hasErrorBoundary: !1 } }, version: "918ff520", hmr: { runtime: "/build/_shared/chunk-D6I44YFN.js", timestamp: 1718514722630 }, url: "/build/manifest-918FF520.js" }; + +// server-entry-module:@remix-run/dev/server-build +var mode = "development", assetsBuildDirectory = "public/build", future = { v3_fetcherPersist: !1, v3_relativeSplatPath: !1, v3_throwAbortReason: !1, unstable_singleFetch: !1 }, publicPath = "/build/", entry = { module: entry_server_node_exports }, routes = { + root: { + id: "root", + parentId: void 0, + path: "", + index: void 0, + caseSensitive: void 0, + module: root_exports + }, + "routes/debug.components": { + id: "routes/debug.components", + parentId: "root", + path: "debug/components", + index: void 0, + caseSensitive: void 0, + module: debug_components_exports + }, + "routes/worlds.$id": { + id: "routes/worlds.$id", + parentId: "root", + path: "worlds/:id", + index: void 0, + caseSensitive: void 0, + module: worlds_id_exports + }, + "routes/_index": { + id: "routes/_index", + parentId: "root", + path: void 0, + index: !0, + caseSensitive: void 0, + module: index_exports + }, + "routes/about": { + id: "routes/about", + parentId: "root", + path: "about", + index: void 0, + caseSensitive: void 0, + module: about_exports + } +}; +export { + assets_manifest_default as assets, + assetsBuildDirectory, + entry, + future, + mode, + publicPath, + routes +}; +//# sourceMappingURL=index.js.map diff --git a/build/index.js.map b/build/index.js.map new file mode 100644 index 0000000..15a29a2 --- /dev/null +++ b/build/index.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../node_modules/@remix-run/dev/dist/config/defaults/entry.server.node.tsx", "../app/root.tsx", "css-bundle-plugin-ns:@remix-run/css-bundle", "../app/root.css.ts", "../app/routes/debug.components.tsx", "../app/components/faction-bar.tsx", "../app/components/faction-bar.css.ts", "../app/components/faction-pie.css.ts", "../app/components/faction-pie.tsx", "../app/routes/worlds.$id.tsx", "../app/components/footer.tsx", "../app/components/footer.css.ts", "../app/utils/metagame.ts", "../app/utils/saerro.ts", "../app/utils/strings.ts", "../app/components/world.css.ts", "../app/utils/classes.ts", "../app/components/index-world.css.ts", "../app/components/alert-timer.tsx", "../app/components/alert-timer.css.ts", "../app/utils/sorting.ts", "../app/routes/_index.tsx", "../app/components/index-world.tsx", "../app/components/index-world-container.css.ts", "../app/components/index-world-container.tsx", "../app/components/index.css.ts", "../app/utils/population.ts", "../app/routes/about.tsx", "../app/components/about.css.ts", "server-assets-manifest:@remix-run/dev/assets-manifest", "server-entry-module:@remix-run/dev/server-build"], + "sourcesContent": ["import { PassThrough } from \"node:stream\";\n\nimport type { AppLoadContext, EntryContext } from \"@remix-run/node\";\nimport { createReadableStreamFromReadable } from \"@remix-run/node\";\nimport { RemixServer } from \"@remix-run/react\";\nimport * as isbotModule from \"isbot\";\nimport { renderToPipeableStream } from \"react-dom/server\";\n\nconst ABORT_DELAY = 5_000;\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext,\n loadContext: AppLoadContext\n) {\n let prohibitOutOfOrderStreaming =\n isBotRequest(request.headers.get(\"user-agent\")) || remixContext.isSpaMode;\n\n return prohibitOutOfOrderStreaming\n ? handleBotRequest(\n request,\n responseStatusCode,\n responseHeaders,\n remixContext\n )\n : handleBrowserRequest(\n request,\n responseStatusCode,\n responseHeaders,\n remixContext\n );\n}\n\n// We have some Remix apps in the wild already running with isbot@3 so we need\n// to maintain backwards compatibility even though we want new apps to use\n// isbot@4. That way, we can ship this as a minor Semver update to @remix-run/dev.\nfunction isBotRequest(userAgent: string | null) {\n if (!userAgent) {\n return false;\n }\n\n // isbot >= 3.8.0, >4\n if (\"isbot\" in isbotModule && typeof isbotModule.isbot === \"function\") {\n return isbotModule.isbot(userAgent);\n }\n\n // isbot < 3.8.0\n if (\"default\" in isbotModule && typeof isbotModule.default === \"function\") {\n return isbotModule.default(userAgent);\n }\n\n return false;\n}\n\nfunction handleBotRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n return new Promise((resolve, reject) => {\n let shellRendered = false;\n const { pipe, abort } = renderToPipeableStream(\n ,\n {\n onAllReady() {\n shellRendered = true;\n const body = new PassThrough();\n const stream = createReadableStreamFromReadable(body);\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n\n resolve(\n new Response(stream, {\n headers: responseHeaders,\n status: responseStatusCode,\n })\n );\n\n pipe(body);\n },\n onShellError(error: unknown) {\n reject(error);\n },\n onError(error: unknown) {\n responseStatusCode = 500;\n // Log streaming rendering errors from inside the shell. Don't log\n // errors encountered during initial shell rendering since they'll\n // reject and get logged in handleDocumentRequest.\n if (shellRendered) {\n console.error(error);\n }\n },\n }\n );\n\n setTimeout(abort, ABORT_DELAY);\n });\n}\n\nfunction handleBrowserRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n return new Promise((resolve, reject) => {\n let shellRendered = false;\n const { pipe, abort } = renderToPipeableStream(\n ,\n {\n onShellReady() {\n shellRendered = true;\n const body = new PassThrough();\n const stream = createReadableStreamFromReadable(body);\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n\n resolve(\n new Response(stream, {\n headers: responseHeaders,\n status: responseStatusCode,\n })\n );\n\n pipe(body);\n },\n onShellError(error: unknown) {\n reject(error);\n },\n onError(error: unknown) {\n responseStatusCode = 500;\n // Log streaming rendering errors from inside the shell. Don't log\n // errors encountered during initial shell rendering since they'll\n // reject and get logged in handleDocumentRequest.\n if (shellRendered) {\n console.error(error);\n }\n },\n }\n );\n\n setTimeout(abort, ABORT_DELAY);\n });\n}\n", "import type { LinksFunction } from \"@remix-run/node\";\nimport { cssBundleHref } from \"@remix-run/css-bundle\";\nimport {\n Links,\n LiveReload,\n Meta,\n Outlet,\n Scripts,\n ScrollRestoration } from\n\"@remix-run/react\";\nimport * as styles from \"./root.css\";\nimport \"./reset.css?__remix_sideEffect__\";\n\nexport const links: LinksFunction = () => [\n{\n rel: \"preconnect\",\n href: \"https://fonts.gstatic.com\",\n crossOrigin: \"anonymous\"\n},\n{\n rel: \"preconnect\",\n href: \"ttps://fonts.googleapis.com\",\n crossOrigin: \"anonymous\"\n},\n{\n rel: \"stylesheet\",\n href: \"https://fonts.googleapis.com/css2?family=Unbounded:wght@700&display=swap\"\n},\n\n...(cssBundleHref ? [{ rel: \"stylesheet\", href: cssBundleHref }] : [])];\n\n\nexport default function App() {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n\n}", "export const cssBundleHref = \"/build/css-bundle-RYSASN3C.css\";", "export var root = 'root_root__o19f1u0';", "import { useState } from \"react\";\nimport { FactionBar } from \"~/components/faction-bar\";\nimport { FactionPie } from \"~/components/faction-pie\";\nimport type { Population } from \"~/utils/saerro\";\n\nexport default function DebugComponents() {\n const [population, setPopulation] = useState({\n nc: 33,\n tr: 33,\n vs: 33,\n });\n\n const [innerMargin, setInnerMargin] = useState(10);\n const [innerColor, setInnerColor] = useState(\"black\");\n return (\n

\n

Debug Components

\n

Faction Viz

\n
\n NC{\" \"}\n \n setPopulation((p) => ({ ...p, nc: Number(e.target.value) }))\n }\n />{\" \"}\n || TR{\" \"}\n \n setPopulation((p) => ({ ...p, tr: Number(e.target.value) }))\n }\n />{\" \"}\n || VS{\" \"}\n \n setPopulation((p) => ({ ...p, vs: Number(e.target.value) }))\n }\n />\n
\n
\n

Horizontal Stacked Bar Chart

\n \n

Pie Chart

\n
\n \n \n
\n Inner margin{\" \"}\n setInnerMargin(Number(e.target.value))}\n />\n Inner color{\" \"}\n setInnerColor(e.target.value)}\n />\n
\n
\n );\n}\n", "import { useMemo } from \"react\";\nimport type { Population } from \"~/utils/saerro\";\nimport * as styles from \"./faction-bar.css\";\n\nexport const FactionBar = ({\n population: { vs, nc, tr },\n tiny\n\n\n\n}: {population: Population;tiny?: boolean;}) => {\n const { vsPercent, ncPercent, trPercent } = useMemo(() => {\n const total = nc + vs + tr;\n return {\n vsPercent: Math.round(vs / total * 100) || 0,\n ncPercent: Math.round(nc / total * 100) || 0,\n trPercent: Math.round(tr / total * 100) || 0\n };\n }, [vs, nc, tr]);\n return (\n
\n
\n {tiny ? <>  : `${vsPercent}%`}\n
\n
\n {tiny ? <>  : `${ncPercent}%`}\n
\n
\n {tiny ? <>  : `${trPercent}%`}\n
\n
);\n\n};", "export var bar = 'faction-bar_bar__prieg10';\nexport var tinyBar = 'faction-bar_tinyBar__prieg11';\nexport var left = 'faction-bar_left__prieg12';\nexport var center = 'faction-bar_center__prieg13';\nexport var right = 'faction-bar_right__prieg14';", "export var pieRoot = 'faction-pie_pieRoot__15tar860';", "import type { Population } from \"~/utils/saerro\";\nimport { pieRoot } from \"./faction-pie.css\";\n\nexport const FactionPie = ({\n population,\n innerMargin,\n innerBackground,\n size\n\n\n\n\n\n}: {population: Population;innerMargin?: number;innerBackground?: string;size?: string;}) => {\n const { nc, tr, vs } = population;\n const total = nc + tr + vs;\n\n const trPct = tr / total * 100;\n const vsPct = vs / total * 100;\n\n return (\n \n\n\n  \n );\n\n};", "import type { LoaderArgs, V2_MetaFunction } from \"@remix-run/cloudflare\";\nimport { json } from \"@remix-run/cloudflare\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport { Footer } from \"~/components/footer\";\nimport type { MetagameWorld } from \"~/utils/metagame\";\nimport { fetchSingleMetagameWorld } from \"~/utils/metagame\";\nimport type { WorldResponse, Zone } from \"~/utils/saerro\";\nimport {\n allClasses,\n allVehicles,\n totalPopulation,\n worldQuery } from\n\"~/utils/saerro\";\nimport {\n pascalCaseToTitleCase,\n toTitleCase,\n worlds,\n zones } from\n\"~/utils/strings\";\nimport * as styles from \"~/components/world.css\";\nimport { c } from \"~/utils/classes\";\nimport { FactionBar } from \"~/components/faction-bar\";\nimport { popImage } from \"~/components/index-world.css\";\nimport vsLogo from \"~/images/vs-100.png\";\nimport ncLogo from \"~/images/nc-100.png\";\nimport trLogo from \"~/images/tr-100.png\";\nimport { FactionPie } from \"~/components/faction-pie\";\nimport { AlertTimer } from \"~/components/alert-timer\";\nimport { contPrioritySort } from \"~/utils/sorting\";\n\ntype LoaderData = {\n saerro: WorldResponse;\n metagame: MetagameWorld;\n id: string;\n};\n\nexport async function loader({ params }: LoaderArgs) {\n const [saerro, metagame] = await Promise.all([\n worldQuery((params.id as string)),\n fetchSingleMetagameWorld((params.id as string))]\n );\n return json(({ saerro, metagame, id: params.id } as LoaderData));\n}\n\nexport const meta: V2_MetaFunction = ({ data }) => {\n const { saerro, id } = (data as LoaderData);\n const date = new Date();\n const worldInfo = worlds[String(id || \"default\")];\n const datetimeHumanFriendly = date.toLocaleString(worldInfo.locale, {\n timeZone: worldInfo.timeZone,\n dateStyle: \"medium\",\n timeStyle: \"short\"\n });\n return [\n {\n title: `${\n worldInfo.name || \"Unknown world\"} | PlanetSide 2 Live Population Stats`\n\n },\n {\n name: \"description\",\n content: `${worldInfo.name} currently has ${totalPopulation(\n saerro.world.population\n )} players online as of ${datetimeHumanFriendly} ${\n worldInfo.name} time. VS: ${\n saerro.world.population.vs}, NC: ${\n saerro.world.population.nc}, TR: ${\n\n saerro.world.population.tr} -- See more detailed stats on ps2.live.`\n\n }];\n\n};\n\nexport default function World() {\n const {\n saerro: { world },\n id,\n metagame\n } = useLoaderData();\n\n const worldInfo = worlds[String(id || \"default\")];\n const nextZoneID =\n metagame.zones.length !== 0 ?\n metagame.zones.sort(\n (a, b) =>\n new Date(a.locked_since ?? Date.now()).getTime() -\n new Date(b.locked_since ?? Date.now()).getTime()\n )[0].id :\n 0;\n\n return (\n <>\n
\n
\n
\n
\n
{worldInfo.name.toUpperCase()}
\n
\n [{worldInfo.location}] [{worldInfo.platform}]\n
\n
\n
\n
\n
\n {totalPopulation(world.population).toLocaleString()}\n
\n PLAYERS\n
\n
\n
\n \n\n \"VS\"{\" \"}\n {world.population.vs}\n
\n \n\n \"NC\"{\" \"}\n {world.population.nc}\n
\n \n\n \"TR\"{\" \"}\n {world.population.tr}\n
\n
\n \n
\n
\n
\n
CONTINENT CONTROL
\n {metagame.zones.sort(contPrioritySort).map((zone, idx) => {\n const zoneInfo = zones[String(zone.id)];\n return (\n
\n
{zoneInfo.name.toUpperCase()}
\n
\n \n\n
\n
\n {zone.alert ?\n :\n zone.locked ?\n nextZoneID == zone.id ?\n <>NEXT UP \u00BB :\n\n <>LOCKED :\n\n\n <>UNLOCKED}\n\n
\n
);\n\n })}\n
\n \n
\n

Continents

\n {world.zones.all.map((zone) =>\n \n )}\n
\n \n \n