mirror of
https://github.com/pmret/website.git
synced 2024-11-08 12:12:27 +01:00
Versioning (#1)
* progress caption wording * initial party impl * rm old index.js * many things * scrolling clouds in background
This commit is contained in:
parent
63b528c2e7
commit
66142b2f6d
75
src/Contributors.jsx
Normal file
75
src/Contributors.jsx
Normal file
@ -0,0 +1,75 @@
|
||||
import React, { useState } from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
import clsx from "clsx"
|
||||
|
||||
import cdownURL from "./cdown.svg"
|
||||
|
||||
function CDown() {
|
||||
return <img src={cdownURL} style={{ width: "1em", height: "1em", verticalAlign: "-5px" }}/>
|
||||
}
|
||||
|
||||
const contributors = [
|
||||
{
|
||||
name: "Ethan",
|
||||
avatar: "https://avatars2.githubusercontent.com/u/2985314?s=400",
|
||||
url: "https://github.com/ethteck",
|
||||
description: <div>
|
||||
</div>,
|
||||
},
|
||||
{
|
||||
name: "stuckpixel",
|
||||
avatar: "https://avatars3.githubusercontent.com/u/3634616?s=400",
|
||||
url: "https://github.com/pixel-stuck",
|
||||
description: <div>
|
||||
</div>,
|
||||
},
|
||||
{
|
||||
name: "alex",
|
||||
avatar: "https://avatars3.githubusercontent.com/u/9429556?s=400",
|
||||
url: "https://github.com/nanaian",
|
||||
description: <div>
|
||||
</div>,
|
||||
},
|
||||
]
|
||||
|
||||
export default function Contributors({ captionPortal }) {
|
||||
const [selected, setSelected] = useState(0)
|
||||
const { name, description, url } = contributors[selected]
|
||||
|
||||
return <div style={{ display: "flex", flexDirection: "row", width: "100%", alignItems: "center", justifyContent: "center" }}>
|
||||
<div style={{ padding: ".5em", display: "flex", flexDirection: "column", alignItems: "center" }}>
|
||||
<div className="avatars">
|
||||
{contributors.map((contributor, i) => {
|
||||
let t = (Math.PI * 2) * ((i - selected) / contributors.length) + Math.PI/2
|
||||
|
||||
let x = 150 * Math.cos(t) + 200
|
||||
let y = 140 * Math.sin(t) + 200
|
||||
|
||||
return <img
|
||||
key={contributor.name}
|
||||
className={clsx("avatar", { inactive: i !== selected })}
|
||||
onClick={() => setSelected(i)}
|
||||
src={contributor.avatar}
|
||||
alt={contributor.name}
|
||||
style={{ top: y + "px", left: x + "px", zIndex: y }}
|
||||
/>
|
||||
})}
|
||||
</div>
|
||||
|
||||
<button className="teal" style={{ width: "80%", cursor: "pointer" }} onClick={() => window.open(url)}>
|
||||
{name}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="shadow-box" style={{ width: "27em", height: "20em" }}>
|
||||
<div className="shadow-box-inner" style={{ backgroundImage: `url(http://placekitten.com/800/${600 + Math.floor(Math.random() * 100)})`, backgroundSize: "cover" }}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{captionPortal.current && createPortal(<div>
|
||||
{description}
|
||||
</div>, captionPortal.current)}
|
||||
</div>
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from "react"
|
||||
import React, { useState, useEffect } from "react"
|
||||
import { createPortal } from "react-dom"
|
||||
import { Area, XAxis, YAxis, AreaChart, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts"
|
||||
import { scalePow } from "d3-scale"
|
||||
@ -19,8 +19,13 @@ const csvVersions = {
|
||||
},
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
const csv = await fetch("https://papermar.io/reports/progress.csv")
|
||||
const colors = {
|
||||
yellow: { stroke: "#e3ac34", fill: "#edc97e" },
|
||||
green: { stroke: "#40e334", fill: "#91eb7f" },
|
||||
}
|
||||
|
||||
async function fetchData(version) {
|
||||
const csv = await fetch(`https://papermar.io/reports/progress_${version}.csv`)
|
||||
.then(response => response.text())
|
||||
|
||||
const rows = csv
|
||||
@ -46,23 +51,18 @@ async function fetchData() {
|
||||
return rows
|
||||
}
|
||||
|
||||
let cachedData = null
|
||||
|
||||
export default function ProgressPane({ captionPortal, nonce }) {
|
||||
const [data, setData] = useState(cachedData)
|
||||
export default function ProgressPane({ captionPortal, nonce, color, version }) {
|
||||
const [data, setData] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
fetchData(version)
|
||||
.then(data => {
|
||||
cachedData = data
|
||||
setData(cachedData)
|
||||
setData(data)
|
||||
})
|
||||
}, [])
|
||||
|
||||
// TODO: cute spin animation when data loads
|
||||
}, [version])
|
||||
|
||||
return <div style={{ display: "flex", flexDirection: "column", width: "100%" }}>
|
||||
{data && <DataView data={data} nonce={nonce} captionPortal={captionPortal}/>}
|
||||
{data && <DataView data={data} nonce={nonce} captionPortal={captionPortal} color={color}/>}
|
||||
{!data && "Loading..."}
|
||||
</div>
|
||||
}
|
||||
@ -78,9 +78,10 @@ const monthDates = []
|
||||
}
|
||||
}
|
||||
|
||||
function DataView({ data, captionPortal, nonce }) {
|
||||
function DataView({ data, captionPortal, nonce, color }) {
|
||||
const latest = data[data.length - 1]
|
||||
const oldest = data[0]
|
||||
const { stroke, fill } = colors[color]
|
||||
|
||||
const [selectedEntry, setSelectedEntry] = useState(latest)
|
||||
|
||||
@ -94,7 +95,7 @@ function DataView({ data, captionPortal, nonce }) {
|
||||
return <span/>
|
||||
}
|
||||
|
||||
const maxPercent = Math.ceil(latest.percentBytes / 25) * 25
|
||||
const maxPercent = latest ? Math.ceil(latest.percentBytes / 25) * 25 : 25
|
||||
|
||||
return <>
|
||||
<div className="shadow-box flex-grow">
|
||||
@ -111,8 +112,8 @@ function DataView({ data, captionPortal, nonce }) {
|
||||
type="linear"
|
||||
dataKey="percentBytes"
|
||||
unit="%"
|
||||
stroke="#e3ac34" strokeWidth={2}
|
||||
fill="#edc97e"
|
||||
stroke={stroke} strokeWidth={2}
|
||||
fill={fill}
|
||||
dot={true}
|
||||
isAnimationActive={false}
|
||||
/>
|
||||
@ -123,7 +124,7 @@ function DataView({ data, captionPortal, nonce }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="shadow-box-title yellow">
|
||||
<button className={"shadow-box-title " + color}>
|
||||
{selectedEntry ? formatTimestamp(selectedEntry.timestamp, {
|
||||
dateStyle: "long",
|
||||
timeStyle: "short",
|
||||
@ -175,7 +176,7 @@ function EntryInfo({ entry, isLatest }) {
|
||||
<tr>
|
||||
<td width="200">Matched</td>
|
||||
<td className="thin align-right">
|
||||
{Math.round((entry.percentBytes) * 100) / 100}%
|
||||
{Math.round(entry.percentBytes * 100) / 100}% bytes
|
||||
({entry.matchingFuncs}/{entry.totalFuncs} functions)
|
||||
</td>
|
||||
</tr>
|
||||
|
BIN
src/bg/green-checker.png
Normal file
BIN
src/bg/green-checker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 B |
BIN
src/bg/mrn-clouds.png
Normal file
BIN
src/bg/mrn-clouds.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 171 KiB |
BIN
src/bg/mrn-toad-town.png
Normal file
BIN
src/bg/mrn-toad-town.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
BIN
src/bg/teal-checker.png
Normal file
BIN
src/bg/teal-checker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 B |
19
src/cdown.svg
Normal file
19
src/cdown.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="62.695" height="62.695" version="1.1" viewBox="0 0 16.588 16.588" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="a" x1="72.973" x2="60.528" y1="266.26" y2="253.34" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#a98723" offset="0"/>
|
||||
<stop stop-color="#e7ce31" offset=".2617"/>
|
||||
<stop stop-color="#e7ce31" offset=".66415"/>
|
||||
<stop stop-color="#f8efd5" offset="1"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g transform="translate(-58.657 -251.24)">
|
||||
<g>
|
||||
<circle cx="66.951" cy="259.53" r="7.5003" fill="url(#a)" stroke="#623826" stroke-linecap="square" stroke-width="1.5875" style="paint-order:normal"/>
|
||||
<path d="m63.138 257.4h7.7581l0.02305 2.018-3.9191 4.0028-3.862-3.959z" fill="none" stroke="#986c2b" stroke-width="2.0361"/>
|
||||
<path d="m63.138 257.4h7.7581l0.02305 2.018-3.9191 4.0028-3.862-3.959z" fill="none" stroke="#fff" stroke-width="1.3616"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
120
src/index.css
120
src/index.css
@ -7,7 +7,7 @@ html {
|
||||
--dark: #313131;
|
||||
--light: #d6d6ce;
|
||||
|
||||
font-size: 28px;
|
||||
font-size: 32px;
|
||||
font-family: "Paper Mario Dialog Redesigned", sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
@ -15,11 +15,35 @@ html {
|
||||
color: var(--light);
|
||||
--text-outline: var(--dark);
|
||||
|
||||
background: #090942; /* TODO: dynamic star bg */
|
||||
background-color: #002c02;
|
||||
background-image: url(bg/mrn-toad-town.png);
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background-image: url(bg/mrn-clouds.png);
|
||||
background-repeat: repeat-x;
|
||||
background-size: contain;
|
||||
animation: clouds linear 30s infinite normal;
|
||||
}
|
||||
|
||||
@keyframes clouds {
|
||||
from {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
to {
|
||||
background-position: 100vw 0;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
|
||||
@ -27,6 +51,10 @@ html {
|
||||
text-shadow: var(--text-outline) 3px 0px 0px, var(--text-outline) 2.83487px 0.981584px 0px, var(--text-outline) 2.35766px 1.85511px 0px, var(--text-outline) 1.62091px 2.52441px 0px, var(--text-outline) 0.705713px 2.91581px 0px, var(--text-outline) -0.287171px 2.98622px 0px, var(--text-outline) -1.24844px 2.72789px 0px, var(--text-outline) -2.07227px 2.16926px 0px, var(--text-outline) -2.66798px 1.37182px 0px, var(--text-outline) -2.96998px 0.42336px 0px, var(--text-outline) -2.94502px -0.571704px 0px, var(--text-outline) -2.59586px -1.50383px 0px, var(--text-outline) -1.96093px -2.27041px 0px, var(--text-outline) -1.11013px -2.78704px 0px, var(--text-outline) -0.137119px -2.99686px 0px, var(--text-outline) 0.850987px -2.87677px 0px, var(--text-outline) 1.74541px -2.43999px 0px, var(--text-outline) 2.44769px -1.73459px 0px, var(--text-outline) 2.88051px -0.838247px 0px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a:any-link {
|
||||
color: #3796ff;
|
||||
--text-outline: #d3e5f9;
|
||||
@ -38,7 +66,6 @@ button {
|
||||
|
||||
border-radius: 100em;
|
||||
padding: 0 .4em .2em .4em;
|
||||
cursor: pointer;
|
||||
|
||||
filter: brightness(1.0);
|
||||
will-change: filter, box-shadow;
|
||||
@ -50,6 +77,9 @@ button {
|
||||
border-right: 2px solid;
|
||||
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button.red {
|
||||
@ -84,6 +114,22 @@ button.github {
|
||||
border-right-color: #171515;
|
||||
}
|
||||
|
||||
button.teal {
|
||||
border-top-color: #22eec9;
|
||||
border-left-color: #22eec9;
|
||||
background: #14a98e;
|
||||
border-bottom-color: #076d5a;
|
||||
border-right-color: #076d5a;
|
||||
}
|
||||
|
||||
button.green {
|
||||
border-top-color: #68ff51;
|
||||
border-left-color: #68ff51;
|
||||
background: #47b836;
|
||||
border-bottom-color: #2a791e;
|
||||
border-right-color: #2a791e;
|
||||
}
|
||||
|
||||
button.inactive {
|
||||
filter: brightness(0.6);
|
||||
}
|
||||
@ -95,6 +141,7 @@ button:hover {
|
||||
|
||||
.tab {
|
||||
margin-right: .25em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#container {
|
||||
@ -108,11 +155,10 @@ button:hover {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
width: 1400px;
|
||||
max-width: calc(100vw - 10vh);
|
||||
|
||||
height: 1200px;
|
||||
max-height: calc(100vh - 10vw);
|
||||
width: 1600px;
|
||||
max-width: 95%;
|
||||
height: 1100px;
|
||||
max-height: 950%;
|
||||
}
|
||||
|
||||
nav {
|
||||
@ -137,6 +183,7 @@ main {
|
||||
border-radius: 1rem;
|
||||
|
||||
padding: 1em;
|
||||
/*padding-bottom: 2.5em;*/
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
@ -148,6 +195,10 @@ main {
|
||||
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
main > :last-child {
|
||||
padding-bottom: 1.5em;
|
||||
}
|
||||
|
||||
main.red {
|
||||
background-color: #e2b6b3;
|
||||
background-image: url(bg/red-checker.png);
|
||||
@ -158,6 +209,16 @@ main.yellow {
|
||||
background-image: url(bg/yellow-checker.png);
|
||||
}
|
||||
|
||||
main.teal {
|
||||
background-color: #cad9d6;
|
||||
background-image: url(bg/teal-checker.png);
|
||||
}
|
||||
|
||||
main.green {
|
||||
background-color: #68ff51;
|
||||
background-image: url(bg/green-checker.png);
|
||||
}
|
||||
|
||||
main > * {
|
||||
image-rendering: initial;
|
||||
}
|
||||
@ -231,14 +292,18 @@ button.shadow-box-title {
|
||||
background-repeat: repeat;
|
||||
background-size: 48px;
|
||||
background-position: center;
|
||||
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: optimizespeed;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
|
||||
border-radius: 1rem;
|
||||
width: 85%;
|
||||
height: 3rem;
|
||||
height: 4rem;
|
||||
|
||||
padding: 8px 16px;
|
||||
margin-top: -14px;
|
||||
padding: .5em 1em;
|
||||
margin-top: -2em;
|
||||
z-index: 1;
|
||||
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
@ -247,7 +312,7 @@ button.shadow-box-title {
|
||||
.progress-chart {
|
||||
flex: 1;
|
||||
|
||||
font-size: 12px;
|
||||
font-size: 18px;
|
||||
overflow: hidden;
|
||||
|
||||
user-select: none;
|
||||
@ -269,3 +334,34 @@ button.shadow-box-title {
|
||||
.flex-spacer { width: 1em; height: 1em }
|
||||
|
||||
.align-right { text-align: right }
|
||||
|
||||
.avatars {
|
||||
position: relative;
|
||||
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 100em;
|
||||
|
||||
position: absolute;
|
||||
will-change: filter, top, left;
|
||||
transition: 200ms filter, 200ms top, 200ms left;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.avatar.inactive {
|
||||
filter: brightness(0.7) saturate(0.7);
|
||||
}
|
||||
|
||||
.avatar:hover {
|
||||
filter: brightness(1.0);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Paper Mario Reverse Engineering</title>
|
||||
<title>Paper Mario Decompilation</title>
|
||||
|
||||
<link rel="stylesheet" href="index.css"/>
|
||||
<script async defer src="main.jsx"></script>
|
||||
|
14
src/index.js
14
src/index.js
@ -1,14 +0,0 @@
|
||||
import moment from "moment"
|
||||
|
||||
import("./progress.js").then(async ({ fetchData, functionsChart }) => {
|
||||
const data = await fetchData()
|
||||
|
||||
functionsChart(data, document.getElementById("progress-chart"))
|
||||
|
||||
const first = data[0]
|
||||
const latest = data[data.length - 1]
|
||||
|
||||
document.getElementById("matched-rom-percent").innerText = Math.round((latest.matchingBytes / latest.totalBytes) * 10000) / 100 + "% matched" // TODO: include data
|
||||
document.getElementById("functions-ratio").innerText = latest.matchingFuncs + "/" + latest.totalFuncs
|
||||
document.getElementById("play-time").innerText = moment(latest.timestamp).from(first.timestamp, true)
|
||||
}).catch(console.error)
|
30
src/main.jsx
30
src/main.jsx
@ -3,6 +3,7 @@ import ReactDOM from "react-dom"
|
||||
import clsx from "clsx"
|
||||
|
||||
import ProgressPane from "./ProgressPane"
|
||||
import Contributors from "./Contributors"
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
@ -11,16 +12,28 @@ const tabs = [
|
||||
color: "red",
|
||||
pane: () => <div>
|
||||
<p className="outline-invert">
|
||||
Welcome to the Paper Mario Reverse-Engineering website!
|
||||
Welcome to the Paper Mario decompilation site!
|
||||
</p>
|
||||
</div>,
|
||||
},
|
||||
{
|
||||
slug: "/progress",
|
||||
name: "Progress",
|
||||
slug: "/progress-us",
|
||||
name: "Progress (US)",
|
||||
color: "yellow",
|
||||
pane: (props) => <ProgressPane {...props}/>
|
||||
pane: (props) => <ProgressPane version="us" color="yellow" {...props}/>
|
||||
},
|
||||
{
|
||||
slug: "/progress-jp",
|
||||
name: "Progress (JP)",
|
||||
color: "green",
|
||||
pane: (props) => <ProgressPane version="jp" color="green" {...props}/>
|
||||
},
|
||||
/*{
|
||||
slug: "/contributors",
|
||||
name: "Contributors",
|
||||
color: "teal",
|
||||
pane: (props) => <Contributors {...props}/>
|
||||
},*/
|
||||
]
|
||||
|
||||
let routedTabIndex = tabs.findIndex(tab => tab.slug === document.location.pathname)
|
||||
@ -33,7 +46,7 @@ function App() {
|
||||
const [flip, setFlip] = useState(false)
|
||||
const pane = useRef()
|
||||
|
||||
function switchToTab(index) {
|
||||
function switchToTab(index, pushState) {
|
||||
if (index === paneIndex || index === tabIndex) return
|
||||
|
||||
console.info("switching to tab", index)
|
||||
@ -41,7 +54,8 @@ function App() {
|
||||
setRotation(rotation - 180)
|
||||
setTabIndex(index)
|
||||
|
||||
history.pushState(null, tabs[index].name, tabs[index].slug)
|
||||
if (pushState)
|
||||
history.pushState(null, tabs[index].name, tabs[index].slug)
|
||||
|
||||
setTimeout(() => {
|
||||
setPaneIndex(index)
|
||||
@ -51,7 +65,7 @@ function App() {
|
||||
|
||||
useEffect(() => {
|
||||
function listener() {
|
||||
switchToTab(tabs.findIndex(tab => tab.slug === document.location.pathname))
|
||||
switchToTab(tabs.findIndex(tab => tab.slug === window.location.pathname), false)
|
||||
}
|
||||
|
||||
window.addEventListener("popstate", listener)
|
||||
@ -67,7 +81,7 @@ function App() {
|
||||
key={tab.name}
|
||||
className={clsx("tab", tab.color, { "inactive": index !== tabIndex })}
|
||||
onClick={() => {
|
||||
switchToTab(index)
|
||||
switchToTab(index, true)
|
||||
}}
|
||||
>
|
||||
{tab.name}
|
||||
|
Loading…
Reference in New Issue
Block a user