diff --git a/app/components/faction-bar.css.ts b/app/components/faction-bar.css.ts
index 925e43e..0be8c07 100644
--- a/app/components/faction-bar.css.ts
+++ b/app/components/faction-bar.css.ts
@@ -11,6 +11,15 @@ export const bar = style({
border: "2px solid #2d2d2d",
});
+export const tinyBar = style({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ flexDirection: "row",
+ overflow: "hidden",
+ fontSize: 5,
+});
+
const shared: ComplexStyleRule = {
textAlign: "center",
};
diff --git a/app/components/faction-bar.tsx b/app/components/faction-bar.tsx
index 8924b74..727c65c 100644
--- a/app/components/faction-bar.tsx
+++ b/app/components/faction-bar.tsx
@@ -1,15 +1,16 @@
import { useMemo } from "react";
import type { Population } from "~/utils/saerro";
-import { totalPopulation } from "~/utils/saerro";
import * as styles from "./faction-bar.css";
export const FactionBar = ({
population: { vs, nc, tr },
+ tiny,
}: {
population: Population;
+ tiny?: boolean;
}) => {
const { vsPercent, ncPercent, trPercent } = useMemo(() => {
- const total = totalPopulation({ vs, nc, tr, total: 0 });
+ const total = nc + vs + tr;
return {
vsPercent: Math.round((vs / total) * 100) || 0,
ncPercent: Math.round((nc / total) * 100) || 0,
@@ -17,15 +18,15 @@ export const FactionBar = ({
};
}, [vs, nc, tr]);
return (
-
+
- {vsPercent}%
+ {tiny ? <> > : `${vsPercent}%`}
- {ncPercent}%
+ {tiny ? <> > : `${ncPercent}%`}
- {trPercent}%
+ {tiny ? <> > : `${trPercent}%`}
);
diff --git a/app/components/index-world-container.tsx b/app/components/index-world-container.tsx
index f9501ff..44ae6f0 100644
--- a/app/components/index-world-container.tsx
+++ b/app/components/index-world-container.tsx
@@ -1,21 +1,23 @@
-import { useMemo } from "react";
-import type { Health, World } from "~/utils/saerro";
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";
export const WorldContainer = ({
- worlds,
- health,
+ metagame,
+ population,
}: {
- worlds: World[];
- health: Health;
+ metagame: MetagameWorld[];
+ population: PopulationWorld[];
}) => (
- {worlds.map((world) => (
+ {metagame.map((world) => (
world.name.toLowerCase() === w.name)}
+ metagame={world}
+ population={
+ population.find((p) => p.id === world.id) as PopulationWorld
+ }
/>
))}
diff --git a/app/components/index-world.css.ts b/app/components/index-world.css.ts
index 0f504a4..0e6044c 100644
--- a/app/components/index-world.css.ts
+++ b/app/components/index-world.css.ts
@@ -1,4 +1,4 @@
-import { style } from "@vanilla-extract/css";
+import { keyframes, style } from "@vanilla-extract/css";
export const container = style({
background: "#333",
@@ -7,11 +7,11 @@ export const container = style({
"@media": {
// under 600px
- "screen and (max-width: 600px)": {
+ "screen and (max-width: 800px)": {
flexBasis: "100%",
},
// between 600px and 1000px
- "screen and (min-width: 600px) and (max-width: 1000px)": {
+ "screen and (min-width: 800px) and (max-width: 1300px)": {
flexBasis: "45%",
},
},
@@ -79,3 +79,68 @@ export const totalPop = style({
fontWeight: "bold",
fontSize: "1.2rem",
});
+
+export const continent = style({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ justifyContent: "space-evenly",
+ padding: "1rem",
+ background: "#222",
+ margin: "0.5rem",
+ borderRadius: "0.4rem",
+});
+
+export const contBars = style({
+ flex: 1,
+ paddingLeft: "0.5rem",
+});
+
+export const contBarTitle = style({
+ fontWeight: "bold",
+ fontSize: "0.7rem",
+ padding: "0.2rem 0.5rem",
+});
+
+export const contCircle = style({
+ height: "2rem",
+ width: "2rem",
+ borderRadius: "50%",
+ background: "linear-gradient(45deg, var(--upper-color), var(--lower-color))",
+ boxShadow: "0 0 0.5rem 0.1rem rgb(var(--lower-color) / 15%)",
+});
+
+export const contName = style({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ fontWeight: "bold",
+ flexDirection: "column",
+ minWidth: "4rem",
+ paddingTop: "0.5rem",
+});
+
+export const jaegerConts = style({
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ padding: "1rem",
+ justifyContent: "space-evenly",
+ backgroundColor: "#222",
+ borderRadius: "0.4rem",
+ margin: "0.5rem",
+});
+
+const alertFade = keyframes({
+ from: {
+ borderColor: "#ff2d2d",
+ },
+ to: {
+ borderColor: "#222",
+ },
+});
+
+export const alertCont = style({
+ border: "2px solid #ff2d2d",
+ animation: `${alertFade} 1s ease-in-out 4 alternate`,
+});
diff --git a/app/components/index-world.tsx b/app/components/index-world.tsx
index a1371f7..b00d112 100644
--- a/app/components/index-world.tsx
+++ b/app/components/index-world.tsx
@@ -1,60 +1,126 @@
import { Link } from "@remix-run/react";
-import { Health, totalPopulation, World } from "~/utils/saerro";
-import { humanTimeAgo } from "~/utils/strings";
-import { worlds } from "~/utils/worlds";
+import {
+ humanTimeAgo,
+ 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 { FactionBar } from "./faction-bar";
+import type { MetagameWorld } from "~/utils/metagame";
+import type { PopulationWorld } from "~/utils/population";
+import { c } from "~/utils/classes";
export type IndexWorldProps = {
- world: World;
- health?: Health["worlds"][number];
+ metagame: MetagameWorld;
+ population: PopulationWorld;
};
-export const IndexWorld = ({ world, health }: IndexWorldProps) => {
- const { platform, location } = worlds[String(world.id || "default")];
-
- const timeSinceLastEvent = humanTimeAgo(
- new Date().getTime() - new Date(health?.lastEvent || 0).getTime()
- );
+export const IndexWorld = ({ metagame, population }: IndexWorldProps) => {
+ const worldId = metagame.id;
+ const { platform, location, name } = worlds[String(worldId || "default")];
return (
-
-
{world.name}
+
+
{name}
[{location}] [{platform}]{" "}
-
DETAILS ⇨
- {totalPopulation(world.population)}
+ {population.factions.vs +
+ population.factions.nc +
+ population.factions.tr}

{" "}
- {world.population.vs}
+ {population.factions.vs}

{" "}
- {world.population.nc}
+ {population.factions.nc}

{" "}
- {world.population.tr}
+ {population.factions.tr}
-
+
+
+
+ {metagame.zones
+ .filter((zone) => !zone.locked)
+ .sort((a, b) => {
+ return a.alert && !b.alert ? -1 : b.alert && !a.alert ? 1 : 0;
+ })
+ .map((zone) => {
+ let {
+ name,
+ colors: [upper, lower],
+ } = zones[zone.id];
+ return worldId !== 19 ? (
+
+
+
+
+ {zone.alert && (
+
+
+ {snakeCaseToTitleCase(
+ zone.alert.alert_type
+ ).toUpperCase()}{" "}
+ ALERT PROGRESS | STARTED{" "}
+ {humanTimeAgo(
+ Date.now() -
+ new Date(zone.alert.start_time).getTime(),
+ true
+ ).toUpperCase()}{" "}
+ AGO
+
+
+
+ )}
+
+
+ ) : (
+
+ );
+ })}
);
diff --git a/app/components/index.css.ts b/app/components/index.css.ts
new file mode 100644
index 0000000..6932004
--- /dev/null
+++ b/app/components/index.css.ts
@@ -0,0 +1,8 @@
+import { style } from "@vanilla-extract/css";
+
+export const outer = style({
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ minHeight: "100vh",
+});
diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx
index 79b71b4..f60e7b5 100644
--- a/app/routes/_index.tsx
+++ b/app/routes/_index.tsx
@@ -2,10 +2,18 @@ import { json, type V2_MetaFunction } from "@remix-run/cloudflare";
import { useLoaderData } from "@remix-run/react";
import { IndexWorld } from "~/components/index-world";
import { WorldContainer } from "~/components/index-world-container";
+import { outer } from "~/components/index.css";
+import { fetchMetagameWorlds } from "~/utils/metagame";
+import { fetchPopulationWorlds } from "~/utils/population";
import { indexQuery } from "~/utils/saerro";
export const loader = async () => {
- return json(await indexQuery());
+ const [metagame, population] = await Promise.all([
+ fetchMetagameWorlds(),
+ fetchPopulationWorlds(),
+ ]);
+
+ return json({ metagame, population });
};
export const meta: V2_MetaFunction = () => {
@@ -21,10 +29,8 @@ export const meta: V2_MetaFunction = () => {
export default function Index() {
const data = useLoaderData
();
return (
-
-
PS2.LIVE
-
Worlds
-
+
+
);
}
diff --git a/app/routes/worlds.$id.tsx b/app/routes/worlds.$id.tsx
index 11b0c97..8877705 100644
--- a/app/routes/worlds.$id.tsx
+++ b/app/routes/worlds.$id.tsx
@@ -4,8 +4,7 @@ import { useLoaderData } from "@remix-run/react";
import type { Zone } from "~/utils/saerro";
import { totalPopulation } from "~/utils/saerro";
import { allClasses, allVehicles, worldQuery } from "~/utils/saerro";
-import { pascalCaseToTitleCase, toTitleCase } from "~/utils/strings";
-import { worlds } from "~/utils/worlds";
+import { pascalCaseToTitleCase, toTitleCase, worlds } from "~/utils/strings";
export const loader = async ({ params }: LoaderArgs) => {
return json(await worldQuery(params.id as string));
diff --git a/app/utils/classes.ts b/app/utils/classes.ts
new file mode 100644
index 0000000..46c5047
--- /dev/null
+++ b/app/utils/classes.ts
@@ -0,0 +1 @@
+export const c = (...args: any[]) => args.filter((x) => !!x).join(" ");
diff --git a/app/utils/metagame.ts b/app/utils/metagame.ts
new file mode 100644
index 0000000..f20acd8
--- /dev/null
+++ b/app/utils/metagame.ts
@@ -0,0 +1,31 @@
+export type MetagameWorld = {
+ id: number;
+ zones: {
+ id: number;
+ locked: boolean;
+ alert?: {
+ id: number;
+ zone: number;
+ start_time: string;
+ end_time: string;
+ ps2alerts: string;
+ alert_type: string;
+ percentages: {
+ nc: number;
+ tr: number;
+ vs: number;
+ };
+ };
+ territory: {
+ nc: number;
+ tr: number;
+ vs: number;
+ };
+ }[];
+};
+
+export const fetchMetagameWorlds = async (): Promise
=> {
+ const response = await fetch("https://metagame.ps2.live/all");
+ const data: MetagameWorld[] = await response.json();
+ return data;
+};
diff --git a/app/utils/population.ts b/app/utils/population.ts
new file mode 100644
index 0000000..1497429
--- /dev/null
+++ b/app/utils/population.ts
@@ -0,0 +1,15 @@
+export type PopulationWorld = {
+ id: number;
+ average: number;
+ factions: {
+ nc: number;
+ tr: number;
+ vs: number;
+ };
+};
+
+export const fetchPopulationWorlds = async (): Promise => {
+ const response = await fetch("https://agg.ps2.live/population/all");
+ const data: PopulationWorld[] = await response.json();
+ return data.map(({ id, average, factions }) => ({ id, average, factions }));
+};
diff --git a/app/utils/saerro.ts b/app/utils/saerro.ts
index b8da249..b0773f4 100644
--- a/app/utils/saerro.ts
+++ b/app/utils/saerro.ts
@@ -12,7 +12,7 @@ export const saerroFetch = async (query: string): Promise => {
};
export type Population = {
- total: number;
+ total?: number;
nc: number;
tr: number;
vs: number;
@@ -22,10 +22,10 @@ export type Zone = {
id: string;
name: string;
population: Population;
- vehicles?: Record<(typeof allVehicles)[number], Population> & {
+ vehicles?: Record & {
total: number;
};
- classes?: Record<(typeof allClasses)[number], Population>;
+ classes?: Record;
};
export type World = {
diff --git a/app/utils/strings.ts b/app/utils/strings.ts
index 94b54d5..6198263 100644
--- a/app/utils/strings.ts
+++ b/app/utils/strings.ts
@@ -8,14 +8,18 @@ export const pascalCaseToTitleCase = (str: string) => {
return toTitleCase(str.replace(/([A-Z])/g, " $1"));
};
-export const humanTimeAgo = (ms: number) => {
+export const snakeCaseToTitleCase = (str: string) => {
+ return toTitleCase(str.replace(/_/g, " "));
+};
+
+export const humanTimeAgo = (ms: number, full?: boolean) => {
const millis = Math.floor(ms % 1000);
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (hours > 0) {
- return `${hours}h`;
+ return full ? `${hours}h ${minutes % 60}m` : `${hours}h`;
}
if (minutes > 0) {
@@ -28,3 +32,106 @@ export const humanTimeAgo = (ms: number) => {
return `${millis}ms`;
};
+
+export const worlds: Record<
+ string,
+ {
+ timeZone: string;
+ locale: string;
+ location: string;
+ platform: string;
+ name: string;
+ }
+> = {
+ "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",
+ },
+ "1000": {
+ name: "Genudine",
+ timeZone: "America/New_York",
+ locale: "en-US",
+ location: "US-E",
+ platform: "PS4",
+ },
+ "2000": {
+ name: "Ceres",
+ timeZone: "UTC",
+ locale: "en-GB",
+ location: "EU",
+ platform: "PS4",
+ },
+ default: {
+ name: "Unknown",
+ timeZone: "UTC",
+ locale: "en-US",
+ location: "???",
+ platform: "???",
+ },
+};
+
+export const zones: Record =
+ {
+ "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: ["#000000", "#000000"],
+ },
+ };
diff --git a/app/utils/worlds.ts b/app/utils/worlds.ts
deleted file mode 100644
index ed6dcd9..0000000
--- a/app/utils/worlds.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-export const worlds: Record<
- string,
- { timeZone: string; locale: string; location: string; platform: string }
-> = {
- "1": {
- timeZone: "America/Los_Angeles",
- locale: "en-US",
- location: "US-W",
- platform: "PC",
- },
- "10": {
- timeZone: "UTC",
- locale: "en-GB",
- location: "EU",
- platform: "PC",
- },
- "13": {
- timeZone: "UTC",
- locale: "en-GB",
- location: "EU",
- platform: "PC",
- },
- "17": {
- timeZone: "America/New_York",
- locale: "en-US",
- location: "US-E",
- platform: "PC",
- },
- "19": {
- timeZone: "America/New_York",
- locale: "en-US",
- location: "US-E",
- platform: "PC",
- },
- "40": {
- timeZone: "Asia/Tokyo",
- locale: "en-GB",
- location: "JP",
- platform: "PC",
- },
- "1000": {
- timeZone: "America/New_York",
- locale: "en-US",
- location: "US-E",
- platform: "PS4",
- },
- "2000": {
- timeZone: "UTC",
- locale: "en-GB",
- location: "EU",
- platform: "PS4",
- },
- default: {
- timeZone: "UTC",
- locale: "en-US",
- location: "???",
- platform: "???",
- },
-};