Compare commits
No commits in common. "main" and "reset" have entirely different histories.
3
.gitignore
vendored
|
@ -5,6 +5,3 @@ node_modules
|
||||||
/functions/\[\[path\]\].js.map
|
/functions/\[\[path\]\].js.map
|
||||||
/public/build
|
/public/build
|
||||||
.env
|
.env
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
**/.DS_Store
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
20.2.0
|
16.13.0
|
|
@ -1,78 +0,0 @@
|
||||||
import { style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
export const header = style({
|
|
||||||
fontSize: "2rem",
|
|
||||||
padding: "1rem",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const outer = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
minHeight: "calc(100vh - 300px)",
|
|
||||||
textAlign: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const link = style({
|
|
||||||
color: "#9e9e9e",
|
|
||||||
textDecoration: "none",
|
|
||||||
transition: "color 0.2s ease-in-out",
|
|
||||||
":hover": {
|
|
||||||
color: "#d53875",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const itemContainer = style({
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const item = style({
|
|
||||||
listStyle: "none",
|
|
||||||
display: "flex",
|
|
||||||
padding: "1.5rem 1rem",
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
backgroundColor: "#222",
|
|
||||||
margin: "1rem",
|
|
||||||
borderRadius: "0.4rem",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const itemLink = style({
|
|
||||||
display: "block",
|
|
||||||
marginRight: "0.75rem",
|
|
||||||
paddingRight: "0.75rem",
|
|
||||||
borderRight: "1px solid #666",
|
|
||||||
color: "#d53875",
|
|
||||||
textDecoration: "none",
|
|
||||||
transition: "color 0.2s ease-in-out",
|
|
||||||
|
|
||||||
":hover": {
|
|
||||||
color: "#efefef",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const itemGithubLink = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
marginLeft: "0.75rem",
|
|
||||||
paddingLeft: "0.75rem",
|
|
||||||
borderLeft: "1px solid #666",
|
|
||||||
color: "#ddd",
|
|
||||||
textDecoration: "none",
|
|
||||||
transition: "color 0.2s ease-in-out",
|
|
||||||
fontSize: "1rem",
|
|
||||||
|
|
||||||
":hover": {
|
|
||||||
color: "#efefef",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const love = style({
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
color: "#aaa",
|
|
||||||
});
|
|
|
@ -1,23 +0,0 @@
|
||||||
import { keyframes, style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
const alertDotBlink = keyframes({
|
|
||||||
from: {
|
|
||||||
backgroundColor: "#ff2d2d",
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
backgroundColor: "#662929",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const alertDot = style({
|
|
||||||
display: "inline-block",
|
|
||||||
height: "0.5rem",
|
|
||||||
width: "0.5rem",
|
|
||||||
borderRadius: "50%",
|
|
||||||
background: "#ff2d2d",
|
|
||||||
animation: `${alertDotBlink} var(--speed) ease-in-out infinite alternate`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const timer = style({
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
});
|
|
|
@ -1,66 +0,0 @@
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import type { MetagameWorld } from "~/utils/metagame";
|
|
||||||
import { humanTimeAgo } from "~/utils/strings";
|
|
||||||
import * as styles from "./alert-timer.css";
|
|
||||||
|
|
||||||
const endTime = (alert: Required<MetagameWorld["zones"][0]>["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 * 1000;
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeLeftString = (alert: MetagameWorld["zones"][0]["alert"]) => {
|
|
||||||
if (alert) {
|
|
||||||
const time = endTime(alert) - Date.now();
|
|
||||||
if (time < 2000) {
|
|
||||||
return <>JUST ENDED</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const speed = time < 1000 * 60 * 15 ? "1s" : "4s";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{humanTimeAgo(time, true).toUpperCase()} LEFT{" "}
|
|
||||||
<div
|
|
||||||
className={styles.alertDot}
|
|
||||||
style={{ "--speed": speed } as any}
|
|
||||||
></div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AlertTimer = ({
|
|
||||||
alert,
|
|
||||||
}: {
|
|
||||||
alert: MetagameWorld["zones"][0]["alert"];
|
|
||||||
}) => {
|
|
||||||
const [timeLeft, setTimeLeft] = useState(timeLeftString(alert));
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (alert) {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setTimeLeft(timeLeftString(alert));
|
|
||||||
}, 1000);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}
|
|
||||||
}, [alert]);
|
|
||||||
|
|
||||||
return <div className={styles.timer}>{timeLeft}</div>;
|
|
||||||
};
|
|
|
@ -1,42 +0,0 @@
|
||||||
import type { ComplexStyleRule } from "@vanilla-extract/css";
|
|
||||||
import { style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
export const bar = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "row",
|
|
||||||
overflow: "hidden",
|
|
||||||
borderRadius: "0.4rem",
|
|
||||||
border: "2px solid #4d4d4d",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const tinyBar = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "row",
|
|
||||||
overflow: "hidden",
|
|
||||||
fontSize: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
const shared: ComplexStyleRule = {
|
|
||||||
textAlign: "center",
|
|
||||||
boxShadow: "inset 0 0 0.5rem rgb(0 0 0 / 10%)",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const left = style({
|
|
||||||
...shared,
|
|
||||||
backgroundColor: "#991cba",
|
|
||||||
});
|
|
||||||
export const center = style({
|
|
||||||
...shared,
|
|
||||||
backgroundColor: "#1564cc",
|
|
||||||
borderLeft: "1px solid #4d4d4d",
|
|
||||||
borderRight: "2px solid #4d4d4d",
|
|
||||||
boxShadow: "inset 0 0 0.5rem rgb(180 180 180 / 10%)",
|
|
||||||
});
|
|
||||||
export const right = style({
|
|
||||||
...shared,
|
|
||||||
backgroundColor: "#d30101",
|
|
||||||
});
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { useMemo } from "react";
|
|
||||||
import type { Population } 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 = 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 (
|
|
||||||
<div className={tiny ? styles.tinyBar : styles.bar}>
|
|
||||||
<div className={styles.left} style={{ flexGrow: vs + 1 }}>
|
|
||||||
{tiny ? <> </> : `${vsPercent}%`}
|
|
||||||
</div>
|
|
||||||
<div className={styles.center} style={{ flexGrow: nc + 1 }}>
|
|
||||||
{tiny ? <> </> : `${ncPercent}%`}
|
|
||||||
</div>
|
|
||||||
<div className={styles.right} style={{ flexGrow: tr + 1 }}>
|
|
||||||
{tiny ? <> </> : `${trPercent}%`}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
export const pieRoot = style({
|
|
||||||
width: "1em",
|
|
||||||
height: "1em",
|
|
||||||
borderRadius: "50%",
|
|
||||||
position: "relative",
|
|
||||||
|
|
||||||
"::after": {
|
|
||||||
content: "''",
|
|
||||||
position: "absolute",
|
|
||||||
top: "var(--inner-margin)",
|
|
||||||
left: "var(--inner-margin)",
|
|
||||||
right: "var(--inner-margin)",
|
|
||||||
bottom: "var(--inner-margin)",
|
|
||||||
borderRadius: "50%",
|
|
||||||
background: "var(--inner-bg)",
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,40 +0,0 @@
|
||||||
import type { Population } from "~/utils/saerro";
|
|
||||||
import { pieRoot } from "./faction-pie.css";
|
|
||||||
|
|
||||||
export const FactionPie = ({
|
|
||||||
population,
|
|
||||||
innerMargin,
|
|
||||||
innerBackground,
|
|
||||||
size,
|
|
||||||
}: {
|
|
||||||
population: Population;
|
|
||||||
innerMargin?: number;
|
|
||||||
innerBackground?: string;
|
|
||||||
size?: string;
|
|
||||||
}) => {
|
|
||||||
const { nc, tr, vs } = population;
|
|
||||||
const total = nc + tr + vs;
|
|
||||||
|
|
||||||
const trPct = (tr / total) * 100;
|
|
||||||
const vsPct = (vs / total) * 100;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<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",
|
|
||||||
} as any
|
|
||||||
}
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,76 +0,0 @@
|
||||||
import { style } from "@vanilla-extract/css";
|
|
||||||
import footer from "~/images/footer.jpg";
|
|
||||||
|
|
||||||
export const root = style({
|
|
||||||
height: 300,
|
|
||||||
position: "relative",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
lineHeight: 1,
|
|
||||||
});
|
|
||||||
export const background = style({
|
|
||||||
backgroundImage: `url(${footer})`,
|
|
||||||
backgroundSize: "cover",
|
|
||||||
backgroundPosition: "bottom",
|
|
||||||
maskImage: "linear-gradient(to bottom, transparent 25%, black)",
|
|
||||||
WebkitMaskImage: "linear-gradient(to bottom, transparent 25%, black)",
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const logo = style({
|
|
||||||
fontSize: "3rem",
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontFamily: "Unbounded, monospace",
|
|
||||||
zIndex: 1,
|
|
||||||
textShadow: "0 0 2em black",
|
|
||||||
});
|
|
||||||
export const logoX = style({
|
|
||||||
color: "#c8a714",
|
|
||||||
position: "relative",
|
|
||||||
top: "0.8rem",
|
|
||||||
left: "0.075rem",
|
|
||||||
});
|
|
||||||
export const logoLive = style({
|
|
||||||
color: "#d8d8d8",
|
|
||||||
});
|
|
||||||
export const logoDot = style({
|
|
||||||
display: "inline-block",
|
|
||||||
width: "0.75em",
|
|
||||||
height: "0.75em",
|
|
||||||
borderRadius: "50%",
|
|
||||||
backgroundColor: "#d8d8d8",
|
|
||||||
position: "relative",
|
|
||||||
left: "0.05rem",
|
|
||||||
top: "0.075em",
|
|
||||||
boxShadow: "0 0 2em black",
|
|
||||||
backgroundImage: `conic-gradient(
|
|
||||||
#d30101 0deg 45deg,
|
|
||||||
#991cba 75deg 165deg,
|
|
||||||
#1564cc 195deg 285deg,
|
|
||||||
#d30101 315deg 360deg
|
|
||||||
)`,
|
|
||||||
});
|
|
||||||
export const lowerLogo = style({
|
|
||||||
textAlign: "right",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontFamily: "Helvetica, Arial, sans-serif",
|
|
||||||
padding: "0 0.2rem",
|
|
||||||
color: "#aaa",
|
|
||||||
});
|
|
||||||
export const link = style({
|
|
||||||
color: "#aaa",
|
|
||||||
textDecoration: "none",
|
|
||||||
transition: "color 0.2s ease-in-out",
|
|
||||||
":hover": {
|
|
||||||
color: "gold",
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { Link } from "@remix-run/react";
|
|
||||||
import * as styles from "./footer.css";
|
|
||||||
|
|
||||||
export const Footer = ({ isMainPage }: { isMainPage?: boolean }) => (
|
|
||||||
<footer>
|
|
||||||
<div className={styles.root}>
|
|
||||||
<div className={styles.background}></div>
|
|
||||||
<div className={styles.logo}>
|
|
||||||
PS2
|
|
||||||
<div className={styles.logoDot}></div>
|
|
||||||
<span className={styles.logoLive}>LIVE</span>
|
|
||||||
<div className={styles.lowerLogo}>
|
|
||||||
<div>
|
|
||||||
{isMainPage ? (
|
|
||||||
<Link className={styles.link} to="/about">
|
|
||||||
more stuff »
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<Link className={styles.link} to="/">
|
|
||||||
less stuff »
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div>© {new Date().getFullYear()}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
);
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
export const container = style({
|
|
||||||
display: "flex",
|
|
||||||
flexBasis: "100%",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
justifyContent: "center",
|
|
||||||
});
|
|
|
@ -1,24 +0,0 @@
|
||||||
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 = ({
|
|
||||||
metagame,
|
|
||||||
population,
|
|
||||||
}: {
|
|
||||||
metagame: MetagameWorld[];
|
|
||||||
population: PopulationWorld[];
|
|
||||||
}) => (
|
|
||||||
<div className={styles.container}>
|
|
||||||
{metagame.map((world) => (
|
|
||||||
<IndexWorld
|
|
||||||
key={world.id}
|
|
||||||
metagame={world}
|
|
||||||
population={
|
|
||||||
population.find((p) => p.id === world.id) as PopulationWorld
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
|
@ -1,212 +0,0 @@
|
||||||
import { keyframes, style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
export const container = style({
|
|
||||||
background: "#333",
|
|
||||||
flexBasis: "30%",
|
|
||||||
margin: "0.5rem",
|
|
||||||
|
|
||||||
"@media": {
|
|
||||||
// under 600px
|
|
||||||
"screen and (max-width: 800px)": {
|
|
||||||
flexBasis: "100%",
|
|
||||||
},
|
|
||||||
// between 600px and 1000px
|
|
||||||
"screen and (min-width: 800px) and (max-width: 1300px)": {
|
|
||||||
flexBasis: "45%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const header = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
color: "inherit",
|
|
||||||
textDecoration: "none",
|
|
||||||
transition: "background-color 0.2s ease-in-out",
|
|
||||||
backgroundColor: "#222",
|
|
||||||
":hover": {
|
|
||||||
backgroundColor: "#383838",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const headerName = style({
|
|
||||||
padding: "0.5rem",
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
});
|
|
||||||
export const headerDetailsLink = style({
|
|
||||||
fontVariant: "small-caps",
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
color: "#aaa",
|
|
||||||
paddingRight: "0.5rem",
|
|
||||||
});
|
|
||||||
export const headerMarkers = style({
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
flex: 1,
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "#aaa",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const circle = style({
|
|
||||||
display: "inline-block",
|
|
||||||
width: "0.4rem",
|
|
||||||
height: "0.4rem",
|
|
||||||
borderRadius: "50%",
|
|
||||||
marginLeft: "0.2rem",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const details = style({
|
|
||||||
padding: "0.5rem",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const population = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const popFaction = style({
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const popImage = style({
|
|
||||||
height: "1.5rem",
|
|
||||||
marginRight: "0.5rem",
|
|
||||||
});
|
|
||||||
|
|
||||||
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.15rem",
|
|
||||||
lineHeight: 0.8,
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const barSeparator = style({
|
|
||||||
height: "0.5rem",
|
|
||||||
width: "100%",
|
|
||||||
});
|
|
||||||
|
|
||||||
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`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const alertDotBlink = keyframes({
|
|
||||||
from: {
|
|
||||||
backgroundColor: "#ff2d2d",
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
backgroundColor: "#662929",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const alertDot = style({
|
|
||||||
display: "inline-block",
|
|
||||||
height: "0.5rem",
|
|
||||||
width: "0.5rem",
|
|
||||||
borderRadius: "50%",
|
|
||||||
background: "#ff2d2d",
|
|
||||||
animation: `${alertDotBlink} var(--speed) ease-in-out infinite alternate`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const nextCont = style({
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "0.5rem",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: "#222",
|
|
||||||
borderRadius: "0.4rem",
|
|
||||||
margin: "0.5rem",
|
|
||||||
color: "#aaa",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const nextContText = style({
|
|
||||||
fontWeight: "bold",
|
|
||||||
textTransform: "uppercase",
|
|
||||||
marginRight: "0.5rem",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const oopsies = style({
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
height: "10rem",
|
|
||||||
});
|
|
||||||
|
|
||||||
const oopsiesSpinAnim = keyframes({
|
|
||||||
from: {
|
|
||||||
transform: "rotate(0deg)",
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
transform: "rotate(360deg)",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const oopsiesSpin = style({
|
|
||||||
animation: `${oopsiesSpinAnim} 2s linear infinite`,
|
|
||||||
});
|
|
|
@ -1,194 +0,0 @@
|
||||||
import { Link } from "@remix-run/react";
|
|
||||||
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";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { AlertTimer } from "./alert-timer";
|
|
||||||
|
|
||||||
export type IndexWorldProps = {
|
|
||||||
metagame: MetagameWorld;
|
|
||||||
population: PopulationWorld;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const IndexWorld = ({ metagame, population }: IndexWorldProps) => {
|
|
||||||
const worldId = metagame.id;
|
|
||||||
const { platform, location, name } = worlds[String(worldId || "default")];
|
|
||||||
|
|
||||||
if (metagame.zones.length === 0) {
|
|
||||||
return <BrokenWorld worldId={worldId} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextZone = metagame.zones.sort(
|
|
||||||
(a, b) =>
|
|
||||||
new Date(a.locked_since ?? Date.now()).getTime() -
|
|
||||||
new Date(b.locked_since ?? Date.now()).getTime()
|
|
||||||
)[0];
|
|
||||||
const nextZoneStrings = zones[nextZone.id];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<Link to={`/worlds/${worldId}`} className={styles.header}>
|
|
||||||
<div className={styles.headerName}>{name}</div>
|
|
||||||
<div className={styles.headerMarkers}>
|
|
||||||
[{location}] [{platform}]{" "}
|
|
||||||
</div>
|
|
||||||
<div className={styles.headerDetailsLink}>DETAILS ⇨</div>
|
|
||||||
</Link>
|
|
||||||
<div className={styles.details}>
|
|
||||||
<div className={styles.population}>
|
|
||||||
<div className={styles.totalPop}>
|
|
||||||
{population.factions.vs +
|
|
||||||
population.factions.nc +
|
|
||||||
population.factions.tr}
|
|
||||||
</div>
|
|
||||||
<div className={styles.popFaction}>
|
|
||||||
<img className={styles.popImage} src={vsLogo} alt="VS" />{" "}
|
|
||||||
{population.factions.vs}
|
|
||||||
</div>
|
|
||||||
<div className={styles.popFaction}>
|
|
||||||
<img className={styles.popImage} src={ncLogo} alt="NC" />{" "}
|
|
||||||
{population.factions.nc}
|
|
||||||
</div>
|
|
||||||
<div className={styles.popFaction}>
|
|
||||||
<img className={styles.popImage} src={trLogo} alt="TR" />{" "}
|
|
||||||
{population.factions.tr}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FactionBar population={population.factions} />
|
|
||||||
</div>
|
|
||||||
<div className={c(worldId === 19 && styles.jaegerConts)}>
|
|
||||||
{metagame.zones
|
|
||||||
.filter((zone) => !zone.locked)
|
|
||||||
.sort((a, b) => {
|
|
||||||
return a.alert && !b.alert ? -1 : b.alert && !a.alert ? 1 : 0;
|
|
||||||
})
|
|
||||||
.map((zone) => {
|
|
||||||
return worldId !== 19 ? (
|
|
||||||
<Continent key={zone.id} zone={zone} />
|
|
||||||
) : (
|
|
||||||
<JaegerContinent key={zone.id} zone={zone} />
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
{worldId !== 19 && (
|
|
||||||
<div className={styles.nextCont}>
|
|
||||||
<div className={styles.nextContText}>Next continent »</div>{" "}
|
|
||||||
<div className={styles.contName}>
|
|
||||||
<div
|
|
||||||
className={styles.contCircle}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"--upper-color": nextZoneStrings.colors[0],
|
|
||||||
"--lower-color": nextZoneStrings.colors[1],
|
|
||||||
} as any
|
|
||||||
}
|
|
||||||
></div>
|
|
||||||
<div>{nextZoneStrings.name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const JaegerContinent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
colors: [upper, lower],
|
|
||||||
} = zones[zone.id];
|
|
||||||
return (
|
|
||||||
<div key={zone.id} className={styles.contName}>
|
|
||||||
<div
|
|
||||||
className={styles.contCircle}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"--upper-color": upper,
|
|
||||||
"--lower-color": lower,
|
|
||||||
} as any
|
|
||||||
}
|
|
||||||
></div>
|
|
||||||
<div>{name}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Continent = ({ zone }: { zone: MetagameWorld["zones"][0] }) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
colors: [upper, lower],
|
|
||||||
} = zones[zone.id];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={zone.id} className={c(styles.continent)}>
|
|
||||||
<div className={styles.contName}>
|
|
||||||
<div
|
|
||||||
className={styles.contCircle}
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"--upper-color": upper,
|
|
||||||
"--lower-color": lower,
|
|
||||||
} as any
|
|
||||||
}
|
|
||||||
></div>
|
|
||||||
<div>{name}</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.contBars}>
|
|
||||||
<div>
|
|
||||||
<div className={styles.contBarTitle}>TERRITORY CONTROL</div>
|
|
||||||
<FactionBar population={zone.territory} />
|
|
||||||
</div>
|
|
||||||
{zone.alert && (
|
|
||||||
<>
|
|
||||||
<div className={styles.barSeparator}></div>
|
|
||||||
<div>
|
|
||||||
<div className={styles.contBarTitle}>
|
|
||||||
<div>
|
|
||||||
{snakeCaseToTitleCase(zone.alert.alert_type).toUpperCase()}{" "}
|
|
||||||
ALERT PROGRESS
|
|
||||||
</div>{" "}
|
|
||||||
<div>
|
|
||||||
<AlertTimer alert={zone.alert} />{" "}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FactionBar population={zone.alert.percentages} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BrokenWorld = ({ worldId }: { worldId: number }) => {
|
|
||||||
const { platform, location, name } = worlds[String(worldId || "default")];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<Link to={`/worlds/${worldId}`} className={styles.header}>
|
|
||||||
<div className={styles.headerName}>{name}</div>
|
|
||||||
<div className={styles.headerMarkers}>
|
|
||||||
[{location}] [{platform}]{" "}
|
|
||||||
</div>
|
|
||||||
<div className={styles.headerDetailsLink}>DETAILS ⇨</div>
|
|
||||||
</Link>
|
|
||||||
<div className={styles.details}>
|
|
||||||
<div className={styles.oopsies}>
|
|
||||||
Daybreak made an oopsie.
|
|
||||||
<br />
|
|
||||||
<div className={styles.oopsiesSpin}>🙂</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
export const outer = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
minHeight: "100vh",
|
|
||||||
});
|
|
|
@ -1,92 +0,0 @@
|
||||||
import { style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
export const headerFont = style({
|
|
||||||
fontFamily: "Unbounded, Impact, monospace",
|
|
||||||
fontWeight: "bold",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const header = style({
|
|
||||||
backgroundColor: "#222",
|
|
||||||
padding: "2em",
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
fontFamily: "Unbounded, Impact, monospace",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const headerName = style({
|
|
||||||
fontSize: "4rem",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
flexBasis: "100%",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const headerSub = style({
|
|
||||||
fontSize: "1rem",
|
|
||||||
color: "#ccc",
|
|
||||||
marginLeft: "1em",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const outer = style({
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
minHeight: "100vh",
|
|
||||||
flexDirection: "column",
|
|
||||||
maxWidth: "1920px",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const population = style({
|
|
||||||
display: "flex",
|
|
||||||
flexWrap: "wrap",
|
|
||||||
width: "100%",
|
|
||||||
flexDirection: "column",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
});
|
|
||||||
export const populationHead = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
flexBasis: "100%",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const popNumbers = style({
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const popItem = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const totalPop = style({
|
|
||||||
fontSize: "2rem",
|
|
||||||
display: "block",
|
|
||||||
width: "4em",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const headerConts = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
flexBasis: "100%",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const contChart = style({
|
|
||||||
fontSize: "4rem",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const cont = style({
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
});
|
|
||||||
|
|
||||||
export const contSub = style({
|
|
||||||
width: "10rem",
|
|
||||||
fontSize: "0.8rem",
|
|
||||||
textAlign: "center",
|
|
||||||
color: "#ccc",
|
|
||||||
paddingTop: "0.5rem",
|
|
||||||
});
|
|
Before Width: | Height: | Size: 282 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 796 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 457 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.5 MiB |
|
@ -1,47 +0,0 @@
|
||||||
*,*::before,*::after {
|
|
||||||
box-sizing: border-box
|
|
||||||
}
|
|
||||||
|
|
||||||
body,h1,h2,h3,h4,p,figure,blockquote,dl,dd {
|
|
||||||
margin: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
ul[role="list"],ol[role="list"] {
|
|
||||||
list-style: none
|
|
||||||
}
|
|
||||||
|
|
||||||
html:focus-within {
|
|
||||||
scroll-behavior: smooth
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
text-rendering: optimizeSpeed;
|
|
||||||
line-height: 1.5
|
|
||||||
}
|
|
||||||
|
|
||||||
a:not([class]) {
|
|
||||||
text-decoration-skip-ink: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
img,picture {
|
|
||||||
max-width: 100%;
|
|
||||||
display: block
|
|
||||||
}
|
|
||||||
|
|
||||||
input,button,textarea,select {
|
|
||||||
font: inherit
|
|
||||||
}
|
|
||||||
|
|
||||||
@media(prefers-reduced-motion:reduce) {
|
|
||||||
html:focus-within {
|
|
||||||
scroll-behavior: auto
|
|
||||||
}
|
|
||||||
|
|
||||||
*,*::before,*::after {
|
|
||||||
animation-duration: .01ms !important;
|
|
||||||
animation-iteration-count: 1 !important;
|
|
||||||
transition-duration: .01ms !important;
|
|
||||||
scroll-behavior: auto !important
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { style } from "@vanilla-extract/css";
|
|
||||||
|
|
||||||
export const root = style({
|
|
||||||
fontFamily: "Arial, Helvetica, sans-serif",
|
|
||||||
background: "#101010",
|
|
||||||
color: "#efefef",
|
|
||||||
lineHeight: 1.6,
|
|
||||||
});
|
|
19
app/root.tsx
|
@ -8,25 +8,8 @@ import {
|
||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
} from "@remix-run/react";
|
} from "@remix-run/react";
|
||||||
import * as styles from "./root.css";
|
|
||||||
import "./reset.css";
|
|
||||||
|
|
||||||
export const links: LinksFunction = () => [
|
export const links: LinksFunction = () => [
|
||||||
{
|
|
||||||
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 }] : []),
|
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -39,7 +22,7 @@ export default function App() {
|
||||||
<Meta />
|
<Meta />
|
||||||
<Links />
|
<Links />
|
||||||
</head>
|
</head>
|
||||||
<body className={styles.root}>
|
<body>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
|
|
@ -1,38 +1,38 @@
|
||||||
import { json, type V2_MetaFunction } from "@remix-run/cloudflare";
|
import type { V2_MetaFunction } from "@remix-run/cloudflare";
|
||||||
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";
|
|
||||||
|
|
||||||
export const loader = async () => {
|
|
||||||
const [metagame, population] = await Promise.all([
|
|
||||||
fetchMetagameWorlds(),
|
|
||||||
fetchPopulationWorlds(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return json({ metagame: metagame.sort((a, b) => a.id - b.id), population });
|
|
||||||
};
|
|
||||||
|
|
||||||
export const meta: V2_MetaFunction = () => {
|
export const meta: V2_MetaFunction = () => {
|
||||||
return [
|
return [{ title: "New Remix App" }];
|
||||||
{ title: "PS2.LIVE" },
|
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
content: "PlanetSide 2 Live Population Stats",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const data = useLoaderData<typeof loader>();
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.4" }}>
|
||||||
<div className={outer}>
|
<h1>Welcome to Remix</h1>
|
||||||
<WorldContainer metagame={data.metagame} population={data.population} />
|
<ul>
|
||||||
</div>
|
<li>
|
||||||
<Footer isMainPage />
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://remix.run/tutorials/blog"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
15m Quickstart Blog Tutorial
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://remix.run/tutorials/jokes"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Deep Dive Jokes App Tutorial
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
|
||||||
|
Remix Docs
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
import { Footer } from "~/components/footer";
|
|
||||||
import {
|
|
||||||
header,
|
|
||||||
item,
|
|
||||||
itemContainer,
|
|
||||||
itemGithubLink,
|
|
||||||
itemLink,
|
|
||||||
link,
|
|
||||||
love,
|
|
||||||
outer,
|
|
||||||
} from "~/components/about.css";
|
|
||||||
|
|
||||||
export default function About() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={outer}>
|
|
||||||
<div>
|
|
||||||
<p className={header}>
|
|
||||||
<b>PS2.LIVE</b> is a network of services that report on the ongoing
|
|
||||||
war on Auraxis.
|
|
||||||
</p>
|
|
||||||
<p style={{ fontStyle: "italic" }}>
|
|
||||||
hat tips:{" "}
|
|
||||||
<a className={link} href="https://ps2.fisu.pw/">
|
|
||||||
fisu
|
|
||||||
</a>
|
|
||||||
,{" "}
|
|
||||||
<a className={link} href="https://wt.honu.pw/">
|
|
||||||
honu & varunda
|
|
||||||
</a>
|
|
||||||
,{" "}
|
|
||||||
<a className={link} href="https://voidwell.com/">
|
|
||||||
Voidwell & Lampjaw
|
|
||||||
</a>
|
|
||||||
,{" "}
|
|
||||||
<a className={link} href="https://census.lithafalcon.cc/">
|
|
||||||
Sanctuary & Falcon
|
|
||||||
</a>
|
|
||||||
,{" "}
|
|
||||||
<a className={link} href="https://ps2alerts.com/">
|
|
||||||
PS2Alerts team
|
|
||||||
</a>
|
|
||||||
,{" "}
|
|
||||||
<a className={link} href="https://discord.gg/yVzGEg3RKV">
|
|
||||||
PS2devs Discord
|
|
||||||
</a>
|
|
||||||
, Daybreak Census Team 💖
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ul className={itemContainer}>
|
|
||||||
{[
|
|
||||||
{
|
|
||||||
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) => (
|
|
||||||
<li className={item} key={i}>
|
|
||||||
<a href={url} className={itemLink}>
|
|
||||||
{name}
|
|
||||||
</a>
|
|
||||||
<div>{description} </div>
|
|
||||||
<a href={github} className={itemGithubLink}>
|
|
||||||
github
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<p className={love}>Built with 💖 by Doll</p>
|
|
||||||
</div>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
import { useState } from "react";
|
|
||||||
import { FactionBar } from "~/components/faction-bar";
|
|
||||||
import { FactionPie } from "~/components/faction-pie";
|
|
||||||
import type { Population } from "~/utils/saerro";
|
|
||||||
|
|
||||||
export default function DebugComponents() {
|
|
||||||
const [population, setPopulation] = useState<Population>({
|
|
||||||
nc: 33,
|
|
||||||
tr: 33,
|
|
||||||
vs: 33,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [innerMargin, setInnerMargin] = useState<number>(10);
|
|
||||||
const [innerColor, setInnerColor] = useState<string>("black");
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h1>Debug Components</h1>
|
|
||||||
<h2>Faction Viz</h2>
|
|
||||||
<div>
|
|
||||||
NC{" "}
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={population.nc}
|
|
||||||
onChange={(e) =>
|
|
||||||
setPopulation((p) => ({ ...p, nc: Number(e.target.value) }))
|
|
||||||
}
|
|
||||||
/>{" "}
|
|
||||||
|| TR{" "}
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={population.tr}
|
|
||||||
onChange={(e) =>
|
|
||||||
setPopulation((p) => ({ ...p, tr: Number(e.target.value) }))
|
|
||||||
}
|
|
||||||
/>{" "}
|
|
||||||
|| VS{" "}
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={population.vs}
|
|
||||||
onChange={(e) =>
|
|
||||||
setPopulation((p) => ({ ...p, vs: Number(e.target.value) }))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>Horizontal Stacked Bar Chart</h3>
|
|
||||||
<FactionBar population={population} />
|
|
||||||
<h3>Pie Chart</h3>
|
|
||||||
<div style={{ fontSize: "5rem" }}>
|
|
||||||
<FactionPie population={population} />
|
|
||||||
<FactionPie
|
|
||||||
population={population}
|
|
||||||
innerBackground={innerColor}
|
|
||||||
innerMargin={innerMargin}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
Inner margin{" "}
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
value={innerMargin}
|
|
||||||
onChange={(e) => setInnerMargin(Number(e.target.value))}
|
|
||||||
/>
|
|
||||||
Inner color{" "}
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={innerColor}
|
|
||||||
onChange={(e) => setInnerColor(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,192 +1,46 @@
|
||||||
import type { LoaderArgs, V2_MetaFunction } from "@remix-run/cloudflare";
|
import type { LoaderArgs } from "@remix-run/cloudflare";
|
||||||
import { json } from "@remix-run/cloudflare";
|
import { json } from "@remix-run/cloudflare";
|
||||||
import { useLoaderData } from "@remix-run/react";
|
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 {
|
import {
|
||||||
|
WorldResponse,
|
||||||
|
Zone,
|
||||||
allClasses,
|
allClasses,
|
||||||
allVehicles,
|
allVehicles,
|
||||||
totalPopulation,
|
|
||||||
worldQuery,
|
worldQuery,
|
||||||
} from "~/utils/saerro";
|
} from "~/utils/saerro";
|
||||||
import {
|
import { pascalCaseToTitleCase, toTitleCase } from "~/utils/strings";
|
||||||
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";
|
|
||||||
|
|
||||||
type LoaderData = {
|
export const loader = async ({ params }: LoaderArgs) => {
|
||||||
saerro: WorldResponse;
|
return json(await worldQuery(params.id as string));
|
||||||
metagame: MetagameWorld;
|
|
||||||
id: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function loader({ params }: LoaderArgs) {
|
export default function Index() {
|
||||||
const [saerro, metagame] = await Promise.all([
|
const { world } = useLoaderData<WorldResponse>();
|
||||||
worldQuery(params.id as string),
|
|
||||||
fetchSingleMetagameWorld(params.id as string),
|
|
||||||
]);
|
|
||||||
return json({ saerro, metagame, id: params.id } as LoaderData);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const meta: V2_MetaFunction<typeof loader> = ({ data }) => {
|
|
||||||
const { saerro, id } = data as LoaderData;
|
|
||||||
const date = new Date();
|
|
||||||
const worldInfo = worlds[String(id || "default")];
|
|
||||||
const 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.`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function World() {
|
|
||||||
const {
|
|
||||||
saerro: { world },
|
|
||||||
id,
|
|
||||||
metagame,
|
|
||||||
} = useLoaderData<typeof loader>();
|
|
||||||
|
|
||||||
const worldInfo = worlds[String(id || "default")];
|
|
||||||
const 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 (
|
return (
|
||||||
<>
|
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.6" }}>
|
||||||
<div className={styles.outer}>
|
<h1>{world.name}</h1>
|
||||||
<div>
|
<h2>Total Population</h2>
|
||||||
<div className={styles.header}>
|
<p>
|
||||||
<div className={c(styles.headerName, styles.headerFont)}>
|
{world.population.total} players ({world.population.vs} VS,{" "}
|
||||||
<div>{worldInfo.name.toUpperCase()}</div>
|
{world.population.nc} NC, {world.population.tr} TR)
|
||||||
<div className={styles.headerSub}>
|
</p>
|
||||||
[{worldInfo.location}] [{worldInfo.platform}]
|
<div>
|
||||||
</div>
|
<h2>Continents</h2>
|
||||||
</div>
|
{world.zones.all.map((zone) => (
|
||||||
<div className={styles.populationHead}>
|
<ZoneInfo zone={zone} key={zone.id} />
|
||||||
<div className={styles.headerFont}>
|
))}
|
||||||
<div className={styles.totalPop}>
|
|
||||||
{totalPopulation(world.population).toLocaleString()}
|
|
||||||
</div>
|
|
||||||
PLAYERS
|
|
||||||
</div>
|
|
||||||
<div className={styles.population}>
|
|
||||||
<div className={styles.popNumbers}>
|
|
||||||
<div
|
|
||||||
className={styles.popItem}
|
|
||||||
style={{ flex: world.population.vs + 1 }}
|
|
||||||
>
|
|
||||||
<img className={popImage} src={vsLogo} alt="VS" />{" "}
|
|
||||||
{world.population.vs}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={styles.popItem}
|
|
||||||
style={{ flex: world.population.nc + 1 }}
|
|
||||||
>
|
|
||||||
<img className={popImage} src={ncLogo} alt="NC" />{" "}
|
|
||||||
{world.population.nc}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={styles.popItem}
|
|
||||||
style={{ flex: world.population.tr + 1 }}
|
|
||||||
>
|
|
||||||
<img className={popImage} src={trLogo} alt="TR" />{" "}
|
|
||||||
{world.population.tr}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<FactionBar population={world.population} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.headerConts}>
|
|
||||||
<div className={styles.headerSub}>CONTINENT CONTROL</div>
|
|
||||||
{metagame.zones.sort(contPrioritySort).map((zone, idx) => {
|
|
||||||
const zoneInfo = zones[String(zone.id)];
|
|
||||||
return (
|
|
||||||
<div key={idx} className={styles.cont}>
|
|
||||||
<div style={{ flex: 0 }}>{zoneInfo.name.toUpperCase()}</div>
|
|
||||||
<div style={{ flex: 1 }}>
|
|
||||||
<FactionPie
|
|
||||||
size="4rem"
|
|
||||||
population={zone.alert?.percentages ?? zone.territory}
|
|
||||||
innerBackground={`linear-gradient(45deg, ${zoneInfo.colors[0]}, ${zoneInfo.colors[1]})`}
|
|
||||||
innerMargin={10}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.contSub}>
|
|
||||||
{zone.alert ? (
|
|
||||||
<AlertTimer alert={zone.alert} />
|
|
||||||
) : zone.locked ? (
|
|
||||||
nextZoneID == zone.id ? (
|
|
||||||
<>NEXT UP »</>
|
|
||||||
) : (
|
|
||||||
<>LOCKED</>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<>UNLOCKED</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h2>Continents</h2>
|
|
||||||
{world.zones.all.map((zone) => (
|
|
||||||
<ZoneInfo zone={zone} key={zone.id} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Footer isMainPage />
|
</div>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ZoneInfo = ({ zone }: { zone: Zone }) => {
|
const ZoneInfo = ({ zone }: { zone: Zone }) => {
|
||||||
const zoneInfo = zones[String(zone.id)];
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<h3>{zoneInfo.name}</h3>
|
<h3>{zone.name}</h3>
|
||||||
<p>
|
<p>
|
||||||
{totalPopulation(zone.population)} players ({zone.population.vs} VS,{" "}
|
{zone.population.total} players ({zone.population.vs} VS,{" "}
|
||||||
{zone.population.nc} NC, {zone.population.tr} TR)
|
{zone.population.nc} NC, {zone.population.tr} TR)
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -201,14 +55,13 @@ const ZoneInfo = ({ zone }: { zone: Zone }) => {
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{totalPopulation(zone.vehicles as any)} vehicles...
|
{zone.vehicles?.total} vehicles...
|
||||||
<ul>
|
<ul>
|
||||||
{allVehicles.map((vehicle, idx) => (
|
{allVehicles.map((vehicle, idx) => (
|
||||||
<li key={idx}>
|
<li key={idx}>
|
||||||
<b>{toTitleCase(vehicle)}</b>:{" "}
|
<b>{toTitleCase(vehicle)}</b>: {zone.vehicles?.[vehicle].total}{" "}
|
||||||
{totalPopulation(zone.vehicles?.[vehicle] as any)} total,{" "}
|
total, {zone.vehicles?.[vehicle].vs} VS,{" "}
|
||||||
{zone.vehicles?.[vehicle].vs} VS, {zone.vehicles?.[vehicle].nc}{" "}
|
{zone.vehicles?.[vehicle].nc} NC, {zone.vehicles?.[vehicle].tr} TR
|
||||||
NC, {zone.vehicles?.[vehicle].tr} TR
|
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export const c = (...args: any[]) => args.filter((x) => !!x).join(" ");
|
|
|
@ -1,40 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
||||||
locked_since?: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchMetagameWorlds = async (): Promise<MetagameWorld[]> => {
|
|
||||||
const response = await fetch("https://metagame.ps2.live/all");
|
|
||||||
const data: MetagameWorld[] = await response.json();
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchSingleMetagameWorld = async (
|
|
||||||
id: string | number
|
|
||||||
): Promise<MetagameWorld> => {
|
|
||||||
const response = await fetch(`https://metagame.ps2.live/${id}`);
|
|
||||||
const data: MetagameWorld = await response.json();
|
|
||||||
return data;
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
export type PopulationWorld = {
|
|
||||||
id: number;
|
|
||||||
average: number;
|
|
||||||
factions: {
|
|
||||||
nc: number;
|
|
||||||
tr: number;
|
|
||||||
vs: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchPopulationWorlds = async (): Promise<PopulationWorld[]> => {
|
|
||||||
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 }));
|
|
||||||
};
|
|
|
@ -1,18 +1,17 @@
|
||||||
export const saerroFetch = async <T>(query: string): Promise<T> => {
|
export const saerroFetch = async <T>(query: string): Promise<T> => {
|
||||||
const response = await fetch(
|
const response = await fetch("https://saerro.ps2.live/graphql", {
|
||||||
`https://saerro.ps2.live/graphql?query=${query}`,
|
method: "POST",
|
||||||
{
|
headers: {
|
||||||
cf: {
|
"Content-Type": "application/json",
|
||||||
cacheTtl: 60,
|
},
|
||||||
},
|
body: JSON.stringify({ query }),
|
||||||
}
|
});
|
||||||
);
|
|
||||||
const json: { data: T } = await response.json();
|
const json: { data: T } = await response.json();
|
||||||
return json.data;
|
return json.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Population = {
|
export type Population = {
|
||||||
total?: number;
|
total: number;
|
||||||
nc: number;
|
nc: number;
|
||||||
tr: number;
|
tr: number;
|
||||||
vs: number;
|
vs: number;
|
||||||
|
@ -22,10 +21,10 @@ export type Zone = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
population: Population;
|
population: Population;
|
||||||
vehicles?: Record<typeof allVehicles[number], Population> & {
|
vehicles?: Record<(typeof allVehicles)[number], Population> & {
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
classes?: Record<typeof allClasses[number], Population>;
|
classes?: Record<(typeof allClasses)[number], Population>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type World = {
|
export type World = {
|
||||||
|
@ -37,6 +36,63 @@ export type World = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Health = {
|
||||||
|
ingestReachable: string;
|
||||||
|
ingest: string;
|
||||||
|
database: string;
|
||||||
|
worlds: {
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type IndexResponse = {
|
||||||
|
health: Health;
|
||||||
|
allWorlds: World[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const indexQuery = async (): Promise<IndexResponse> => {
|
||||||
|
const query = `query {
|
||||||
|
health {
|
||||||
|
ingestReachable
|
||||||
|
ingest
|
||||||
|
database
|
||||||
|
worlds {
|
||||||
|
name
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allWorlds {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
population {
|
||||||
|
total
|
||||||
|
nc
|
||||||
|
tr
|
||||||
|
vs
|
||||||
|
}
|
||||||
|
zones {
|
||||||
|
all {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
population {
|
||||||
|
total
|
||||||
|
nc
|
||||||
|
tr
|
||||||
|
vs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const indexData: IndexResponse = await saerroFetch(query);
|
||||||
|
|
||||||
|
indexData.allWorlds.sort((a, b) => a.id - b.id);
|
||||||
|
|
||||||
|
return indexData;
|
||||||
|
};
|
||||||
|
|
||||||
export type WorldResponse = {
|
export type WorldResponse = {
|
||||||
world: World;
|
world: World;
|
||||||
};
|
};
|
||||||
|
@ -72,10 +128,12 @@ export const allClasses = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const worldQuery = async (worldID: string): Promise<WorldResponse> => {
|
export const worldQuery = async (worldID: string): Promise<WorldResponse> => {
|
||||||
const query = `{
|
const query = `query {
|
||||||
world(by: {id: ${Number(worldID)}}) {
|
world(by: {id: ${Number(worldID)}}) {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
population {
|
population {
|
||||||
|
total
|
||||||
nc
|
nc
|
||||||
tr
|
tr
|
||||||
vs
|
vs
|
||||||
|
@ -83,6 +141,7 @@ export const worldQuery = async (worldID: string): Promise<WorldResponse> => {
|
||||||
zones {
|
zones {
|
||||||
all {
|
all {
|
||||||
id
|
id
|
||||||
|
name
|
||||||
classes {
|
classes {
|
||||||
${allClasses.map((cls) => `${cls} { total nc tr vs }`).join(" ")}
|
${allClasses.map((cls) => `${cls} { total nc tr vs }`).join(" ")}
|
||||||
}
|
}
|
||||||
|
@ -93,6 +152,7 @@ export const worldQuery = async (worldID: string): Promise<WorldResponse> => {
|
||||||
.join(" ")}
|
.join(" ")}
|
||||||
}
|
}
|
||||||
population {
|
population {
|
||||||
|
total
|
||||||
nc
|
nc
|
||||||
tr
|
tr
|
||||||
vs
|
vs
|
||||||
|
@ -102,10 +162,9 @@ export const worldQuery = async (worldID: string): Promise<WorldResponse> => {
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
console.log(query);
|
||||||
|
|
||||||
const worldData: WorldResponse = await saerroFetch(query);
|
const worldData: WorldResponse = await saerroFetch(query);
|
||||||
|
|
||||||
return worldData;
|
return worldData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const totalPopulation = ({ nc, vs, tr }: Population): number =>
|
|
||||||
nc + vs + tr;
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
import type { MetagameWorld } from "./metagame";
|
|
||||||
|
|
||||||
export const contPrioritySort = (
|
|
||||||
a: MetagameWorld["zones"][number],
|
|
||||||
b: MetagameWorld["zones"][number]
|
|
||||||
) => {
|
|
||||||
// Sort priority:
|
|
||||||
// 1. oldest alert
|
|
||||||
// 2. unlocked by id
|
|
||||||
// 3. oldest locked since
|
|
||||||
|
|
||||||
if (a.locked && !b.locked) {
|
|
||||||
return 1;
|
|
||||||
} else if (!a.locked && b.locked) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.alert && b.alert) {
|
|
||||||
return Date.parse(a.alert.start_time) - Date.parse(b.alert.start_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.alert) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b.alert) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.locked_since && b.locked_since) {
|
|
||||||
return Date.parse(a.locked_since) - Date.parse(b.locked_since);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.locked_since) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b.locked_since) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
};
|
|
|
@ -7,131 +7,3 @@ export const toTitleCase = (str: string) => {
|
||||||
export const pascalCaseToTitleCase = (str: string) => {
|
export const pascalCaseToTitleCase = (str: string) => {
|
||||||
return toTitleCase(str.replace(/([A-Z])/g, " $1"));
|
return toTitleCase(str.replace(/([A-Z])/g, " $1"));
|
||||||
};
|
};
|
||||||
|
|
||||||
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 full ? `${hours}h ${minutes % 60}m ${seconds % 60}s` : `${hours}h`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minutes > 0) {
|
|
||||||
return full ? `${minutes}m ${seconds % 60}s` : `${minutes}m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (seconds > 0) {
|
|
||||||
return `${seconds}s`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<string, { name: string; colors: [string, string] }> =
|
|
||||||
{
|
|
||||||
"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"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
2767
package-lock.json
generated
34
package.json
|
@ -1,38 +1,36 @@
|
||||||
{
|
{
|
||||||
"name": "ps2.live",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "remix build",
|
"build": "remix build",
|
||||||
"dev:remix": "remix watch",
|
"dev:remix": "remix watch",
|
||||||
"dev:wrangler": "npm run wrangler",
|
"dev:wrangler": "cross-env NODE_ENV=development npm run wrangler",
|
||||||
"dev": "cross-env NODE_ENV=development npm-run-all build --parallel \"dev:*\"",
|
"dev": "npm-run-all build --parallel \"dev:*\"",
|
||||||
"start": "cross-env NODE_ENV=production npm run wrangler --live-reload",
|
"start": "cross-env NODE_ENV=production npm run wrangler",
|
||||||
"typecheck": "tsc",
|
"typecheck": "tsc",
|
||||||
"wrangler": "wrangler pages dev ./public",
|
"wrangler": "wrangler pages dev ./public",
|
||||||
"pages:deploy": "npm run build && wrangler pages publish ./public"
|
"pages:deploy": "npm run build && wrangler pages publish ./public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/cloudflare": "^1.17.0",
|
"@remix-run/cloudflare": "^1.16.0",
|
||||||
"@remix-run/cloudflare-pages": "^1.17.0",
|
"@remix-run/cloudflare-pages": "^1.16.0",
|
||||||
"@remix-run/css-bundle": "^1.17.0",
|
"@remix-run/css-bundle": "^1.16.0",
|
||||||
"@remix-run/react": "^1.17.0",
|
"@remix-run/react": "^1.16.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"isbot": "^3.6.10",
|
"isbot": "^3.6.8",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "3.x",
|
"@cloudflare/workers-types": "^3.19.0",
|
||||||
"@remix-run/dev": "^1.17.0",
|
"@remix-run/dev": "^1.16.0",
|
||||||
"@remix-run/eslint-config": "^1.17.0",
|
"@remix-run/eslint-config": "^1.16.0",
|
||||||
"@types/react": "^18.2.10",
|
"@types/react": "^18.0.35",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.0.11",
|
||||||
"@vanilla-extract/css": "^1.11.1",
|
"eslint": "^8.38.0",
|
||||||
"eslint": "^8.42.0",
|
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"typescript": "^5.1.3",
|
"typescript": "^5.0.4",
|
||||||
"wrangler": "^3.1.0"
|
"wrangler": "^2.15.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.13"
|
"node": ">=16.13"
|
||||||
|
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 5.5 KiB |
|
@ -15,7 +15,6 @@ module.exports = {
|
||||||
// publicPath: "/build/",
|
// publicPath: "/build/",
|
||||||
future: {
|
future: {
|
||||||
v2_errorBoundary: true,
|
v2_errorBoundary: true,
|
||||||
v2_headers: true,
|
|
||||||
v2_meta: true,
|
v2_meta: true,
|
||||||
v2_normalizeFormMethod: true,
|
v2_normalizeFormMethod: true,
|
||||||
v2_routeConvention: true,
|
v2_routeConvention: true,
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
compatibility_date = "2023-06-10"
|
compatibility_date = "2022-04-05"
|
||||||
|
compatibility_flags = ["streams_enable_constructors"]
|
||||||
|
|