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 0000000..8a31388 Binary files /dev/null and b/app/images/icon_engi.png differ diff --git a/app/images/icon_heavy.png b/app/images/icon_heavy.png new file mode 100644 index 0000000..6860505 Binary files /dev/null and b/app/images/icon_heavy.png differ diff --git a/app/images/icon_infil.png b/app/images/icon_infil.png new file mode 100644 index 0000000..ccd14f3 Binary files /dev/null and b/app/images/icon_infil.png differ diff --git a/app/images/icon_light.png b/app/images/icon_light.png new file mode 100644 index 0000000..2ea42e3 Binary files /dev/null and b/app/images/icon_light.png differ diff --git a/app/images/icon_max.png b/app/images/icon_max.png new file mode 100644 index 0000000..8cfebc3 Binary files /dev/null and b/app/images/icon_max.png differ diff --git a/app/images/icon_medic.png b/app/images/icon_medic.png new file mode 100644 index 0000000..f44a6c3 Binary files /dev/null and b/app/images/icon_medic.png differ diff --git a/app/images/vehicles/ant.png b/app/images/vehicles/ant.png new file mode 100644 index 0000000..8e2126f Binary files /dev/null and b/app/images/vehicles/ant.png differ diff --git a/app/images/vehicles/chimera.png b/app/images/vehicles/chimera.png new file mode 100644 index 0000000..22989f5 Binary files /dev/null and b/app/images/vehicles/chimera.png differ diff --git a/app/images/vehicles/corsair.png b/app/images/vehicles/corsair.png new file mode 100644 index 0000000..d0c0609 Binary files /dev/null and b/app/images/vehicles/corsair.png differ diff --git a/app/images/vehicles/dervish.png b/app/images/vehicles/dervish.png new file mode 100644 index 0000000..8dbcee0 Binary files /dev/null and b/app/images/vehicles/dervish.png differ diff --git a/app/images/vehicles/flash.png b/app/images/vehicles/flash.png new file mode 100644 index 0000000..0f62482 Binary files /dev/null and b/app/images/vehicles/flash.png differ diff --git a/app/images/vehicles/galaxy.png b/app/images/vehicles/galaxy.png new file mode 100644 index 0000000..5bfabfe Binary files /dev/null and b/app/images/vehicles/galaxy.png differ diff --git a/app/images/vehicles/harasser.png b/app/images/vehicles/harasser.png new file mode 100644 index 0000000..8c9dd87 Binary files /dev/null and b/app/images/vehicles/harasser.png differ diff --git a/app/images/vehicles/javelin.png b/app/images/vehicles/javelin.png new file mode 100644 index 0000000..ba0068c Binary files /dev/null and b/app/images/vehicles/javelin.png differ diff --git a/app/images/vehicles/liberator.png b/app/images/vehicles/liberator.png new file mode 100644 index 0000000..1d07c52 Binary files /dev/null and b/app/images/vehicles/liberator.png differ diff --git a/app/images/vehicles/lightning.png b/app/images/vehicles/lightning.png new file mode 100644 index 0000000..1d07c52 Binary files /dev/null and b/app/images/vehicles/lightning.png differ diff --git a/app/images/vehicles/magrider.png b/app/images/vehicles/magrider.png new file mode 100644 index 0000000..8144989 Binary files /dev/null and b/app/images/vehicles/magrider.png differ diff --git a/app/images/vehicles/mosquito.png b/app/images/vehicles/mosquito.png new file mode 100644 index 0000000..0f62482 Binary files /dev/null and b/app/images/vehicles/mosquito.png differ diff --git a/app/images/vehicles/prowler.png b/app/images/vehicles/prowler.png new file mode 100644 index 0000000..475e310 Binary files /dev/null and b/app/images/vehicles/prowler.png differ diff --git a/app/images/vehicles/reaver.png b/app/images/vehicles/reaver.png new file mode 100644 index 0000000..847f8c5 Binary files /dev/null and b/app/images/vehicles/reaver.png differ diff --git a/app/images/vehicles/scythe.png b/app/images/vehicles/scythe.png new file mode 100644 index 0000000..1e0148b Binary files /dev/null and b/app/images/vehicles/scythe.png differ diff --git a/app/images/vehicles/sunderer.png b/app/images/vehicles/sunderer.png new file mode 100644 index 0000000..644c783 Binary files /dev/null and b/app/images/vehicles/sunderer.png differ diff --git a/app/images/vehicles/valkyrie.png b/app/images/vehicles/valkyrie.png new file mode 100644 index 0000000..4a64a15 Binary files /dev/null and b/app/images/vehicles/valkyrie.png differ diff --git a/app/images/vehicles/vanguard.png b/app/images/vehicles/vanguard.png new file mode 100644 index 0000000..1d1f1c2 Binary files /dev/null and b/app/images/vehicles/vanguard.png differ diff --git a/app/root.tsx b/app/root.tsx index 95c8baf..438f136 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,4 +1,4 @@ -import type { LinksFunction } from "@remix-run/cloudflare"; +import type { LinksFunction } from "@remix-run/node"; import { cssBundleHref } from "@remix-run/css-bundle"; import { Links, @@ -14,17 +14,11 @@ import "./reset.css"; export const links: LinksFunction = () => [ { 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