A heckin ton. Mostly hackish

This commit is contained in:
Gnarwhal 2021-02-16 14:11:12 -05:00
parent 052052d76b
commit b229ff9a15
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
70 changed files with 2226 additions and 881 deletions

View file

@ -1 +1,5 @@
{}
{
"hosts": {
"backend": "https://localhost:4730"
}
}

View file

@ -2,6 +2,9 @@
"extends": [
"config/base.json"
],
"hosts": {
"frontend": "http://localhost:8080"
},
"build": "debug",
"port": 8080
}

View file

@ -2,6 +2,9 @@
"extends": [
"config/base.json"
],
"hosts": {
"frontend": "http://localhost"
},
"build": "release",
"port": 80
}

View file

@ -3,17 +3,19 @@
"version": "1.0.0",
"description": "Cross platform achievement tracker",
"repository": "github:Gnarwhal/AchievementProject",
"main": "static_server.js",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"debug": "node static_server.js config/debug.json",
"release": "node static_server.js config/release.json"
"debug": "node server.js config/debug.json",
"release": "node server.js config/release.json"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"morgan": "^1.10.0",
"passport": "^0.4.1",
"passport-steam": "^1.0.15",
"promptly": "^3.2.0"
}
}

62
frontend/server.js Normal file
View file

@ -0,0 +1,62 @@
const fs = require('fs' );
const path = require('path' );
const https = require('https' );
const express = require('express' );
const morgan = require('morgan' );
const passport = require('passport');
const SteamStrategy = require('passport-steam').Strategy;
const promptly = require('promptly');
const config = require('./config.js').load(process.argv[2]);
if (config.build === 'debug') {
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
}
console.log(`Running server at '${config.hosts.frontend}'`);
passport.use(new SteamStrategy({
returnURL: `${config.hosts.frontend}/profile/steam`,
realm: `${config.hosts.frontend}`,
profile: false,
}));
const app = express();
app.use("/", morgan("dev"));
app.use("/static", express.static("webpage/static"));
app.get("/login", (req, res) => {
res.sendFile(path.join(__dirname + "/webpage/login.html"));
});
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname + "/webpage/index.html"));
});
app.get("/about", (req, res) => {
res.sendFile(path.join(__dirname + "/webpage/about.html"));
});
app.get("/profile/:id", (req, res) => {
res.sendFile(path.join(__dirname + "/webpage/profile.html"));
});
app.get("/auth/steam", passport.authenticate('steam'), (req, res) => {});
// --- API Forward --- //
app.use("/api/*", (req, res) => {
res.redirect(307, `${config.hosts.backend}/${req.params[0]}`)
});
// ------------------- //
const server = app.listen(config.port);
const prompt = input => {
if (/q(?:uit)?|exit/i.test(input)) {
server.close();
} else {
promptly.prompt('')
.then(prompt);
}
};
prompt();

View file

@ -1,26 +0,0 @@
const express = require('express' );
const morgan = require('morgan' );
const promptly = require('promptly');
const config = require('./config.js').load(process.argv[2]);
if (config.build === 'debug') {
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
}
const app = express();
app.use("/", morgan("dev"));
app.use("/", express.static("webpage"));
const server = app.listen(config.port);
const prompt = input => {
if (/q(?:uit)?|exit/i.test(input)) {
server.close();
} else {
promptly.prompt('')
.then(prompt);
}
};
prompt();

View file

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Achievements Project</title>
<link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/about.css" />
</head>
<body>
<div id="navbar">
<template data-template="navbar: List<Basic>">
<div id="navbar-section-${section}" class="navbar-section">
<template data-template="navbar-section-${section}: List<Basic>">
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
${title}
</div>
</template>
</div>
</template>
</div>
<div id="content-body">
<div id="about-page" class="page">
<div class="page-subsection">
<div class="page-header">
<p class="page-header-text">About</p>
<div class="page-header-separator"></div>
</div>
</div>
<div class="page-subsection">
<div class="page-subsection-wrapper">
<p id="about-text" class="page-subsection-chunk">Collate achievement data from multiple platforms into a single location. Explore achievement data of yourself and others.</p>
</div>
</div>
</div>
</div>
<script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/about.js"></script>
</body>
</html>

View file

@ -4,9 +4,9 @@
<meta charset="UTF-8" />
<title>Achievements Project</title>
<link rel="stylesheet" href="styles/theme.css" />
<link rel="stylesheet" href="styles/common.css" />
<link rel="stylesheet" href="styles/index.css" />
<link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/index.css" />
</head>
<body>
<div id="navbar">
@ -21,19 +21,111 @@
</template>
</div>
<div id="content-body">
<template data-template="content-body: List<Basic>">
<div id="${page}-page" class="page">
<div class="page-subsection">
<div class="page-header">
<p class="page-header-text">${title}</p>
<div class="page-header-separator"></div>
<div id="index-page" class="page">
<div class="page-subsection">
<div class="page-header">
<p class="page-header-text">Achievements Project</p>
<div class="page-header-separator"></div>
</div>
</div>
<div class="page-subsection">
<div id="list-page-search-filters">
<div id="list-page-search-dropdown">
<div id="search-wrapper" class="page-subsection-wrapper">
<div id="list-page-search-pair" class="list-page-search page-subsection-chunk">
<label for="achievement-search">Search</label>
<input id="achievement-search" type="text" placeholder="Name" name="achievement-search"/>
</div>
</div>
<div id="filter-dropdown-wrapper" class="page-subsection-wrapper">
<div id="filter-dropdown-stack">
<img id="filter-dropdown-button" src="/static/res/dropdown.svg" alt="Dropdown Button"/>
<img id="filter-dropdown-button-hover" src="/static/res/dropdown-hover.svg" alt="Dropdown Button"/>
</div>
</div>
</div>
<div id="list-page-filters-flex">
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Games</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p>
</div>
</div>
</div>
</div>
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Achievements</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="from-games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">My Games</p>
</div>
<div id="in-progress-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">In Progress</p>
</div>
<div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p>
</div>
</div>
</div>
</div>
<div class="list-page-filter-section page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Platforms</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="list-page-partitions">
<div class="list-page-list-partition page-subsection-wrapper">
<div class="page-subsection-chunk">
<div class="list-page-list">
<div class="list-page-header">
<p class="list-page-entry-icon"></p>
<p class="list-page-entry-text achievement-name">Name</p>
<p class="list-page-entry-text achievement-description">Description</p>
<p class="list-page-entry-text achievement-stages">Stages</p>
</div>
<template data-template="achievements-page-list: List<Basic>">
<div class="list-page-entry">
<img class="list-page-entry-icon" src="/static/res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
<div class="list-page-entry-text-section">
<p class="list-page-entry-text achievement-name">${achievement-name}</p>
<p class="list-page-entry-text achievement-description">${achievement-description}</p>
<p class="list-page-entry-text achievement-stages">${stages}</p>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
<template data-template="extern-${page}-page: Extern"></template>
</div>
</template>
</div>
</div>
<script src="scripts/template.js"></script>
<script src="scripts/index.js"></script>
<script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/index.js"></script>
</body>
</html>

View file

@ -3,9 +3,9 @@
<head>
<meta charset="UTF-8" />
<title>Achievements Project | Login</title>
<link rel="stylesheet" href="styles/theme.css" />
<link rel="stylesheet" href="styles/common.css" />
<link rel="stylesheet" href="styles/login.css" />
<link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/login.css" />
</head>
<body>
<div id="navbar"></div>
@ -44,7 +44,8 @@
</div>
</div>
<script src="scripts/template.js"></script>
<script src="scripts/login.js"></script>
<script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/login.js"></script>
</body>
</html>

View file

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Achievements Project</title>
<link rel="stylesheet" href="/static/styles/theme.css" />
<link rel="stylesheet" href="/static/styles/common.css" />
<link rel="stylesheet" href="/static/styles/profile.css" />
</head>
<body>
<div id="navbar">
<template data-template="navbar: List<Basic>">
<div id="navbar-section-${section}" class="navbar-section">
<template data-template="navbar-section-${section}: List<Basic>">
<div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
${title}
</div>
</template>
</div>
</template>
</div>
<div id="content-body">
<div id="profile-page" class="page">
<div class="page-subsection">
<div class="page-header">
<p class="page-header-text">Profile</p>
<div class="page-header-separator"></div>
</div>
</div>
<template data-template="profile-page">
<div id="profile-section-1">
<div id="profile-info" class="page-subsection">
<div class="page-subsection-wrapper">
<div class="page-subheader">
<div id="profile-info-name" class="page-subheader-flex">
<p id="profile-info-username-text" class="page-subheader-text">${username}</p>
<input id="profile-info-username-field" class="page-subheader-text" value="${username}"/>
<span id="info-edit-stack" class="profile-edit-stack">
<img class="profile-edit page-subheader-icon" src="/static/res/edit.svg" alt="Edit Platforms" />
<img class="profile-edit-hover page-subheader-icon" src="/static/res/edit-hover.svg" alt="Edit Platforms" />
</span>
<span id="info-save-stack" class="profile-save-stack">
<img class="profile-save page-subheader-icon" src="/static/res/save.svg" alt="Save Platforms" />
<img class="profile-save-hover page-subheader-icon" src="/static/res/save-hover.svg" alt="Edit Platforms" />
</span>
</div>
<div class="page-subheader-separator"></div>
</div>
<div id="profile-info-pfp-border" class="page-subsection-chunk">
<div id="profile-info-pfp">
<img id="profile-info-pfp-img" src="/api/user/${id}/image" alt="User's Profile Picture" />
<div id="profile-info-pfp-vignette"></div>
<img id="profile-info-pfp-upload" src="/static/res/upload.svg" alt="Upload Image" />
<img id="profile-info-pfp-upload-hover" src="/static/res/upload-hover.svg" alt="Upload Image" />
<img id="profile-info-pfp-upload-invalid" src="/static/res/upload-invalid.svg" alt="Invalid Image" />
</div>
</div>
</div>
</div>
<div id="profile-stats" class="page-subsection">
<div id="profile-stats-numeric">
<div id="profile-completion" class="page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Avg. Completion</p>
<div class="page-subheader-separator"></div>
</div>
<div id="profile-completion-stack">
<img id="profile-completion-background" src="/static/res/completion.svg">
<canvas id="profile-completion-canvas"></canvas>
<p id="profile-completion-text">${average}</p>
</div>
</div>
<div id="profile-perfect" class="page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Perfect Games</p>
<div class="page-subheader-separator"></div>
</div>
<p id="profile-perfect-text">${perfect}</p>
</div>
</div>
<div id="profile-hardest" class="page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Noteworthy Achievements</p>
<div class="page-subheader-separator"></div>
</div>
</div>
</div>
</div>
<div id="profile-section-2">
<div id="profile-platforms" class="page-subsection">
<div class="page-subsection-wrapper">
<div class="page-subheader">
<div class="page-subheader-flex">
<p class="page-subheader-text">Platforms</p>
<span id="platform-edit-stack" class="profile-edit-stack">
<img id="platform-edit" class="profile-edit page-subheader-icon" src="/static/res/edit.svg" alt="Edit Platforms" />
<img id="platform-edit-hover" class="profile-edit-hover page-subheader-icon" src="/static/res/edit-hover.svg" alt="Edit Platforms" />
</span>
<span id="platform-save-stack" class="profile-save-stack">
<img id="platform-save" class="profile-save page-subheader-icon" src="/static/res/save.svg" alt="Save Platforms" />
<img id="platform-save-hover" class="profile-save-hover page-subheader-icon" src="/static/res/save-hover.svg" alt="Edit Platforms" />
</span>
</div>
<div class="page-subheader-separator"></div>
</div>
<div class="profile-list page-subsection-chunk">
<template data-template="profile-platforms-list: List<Basic>">
<div class="profile-entry ${connected}">
${img}
<p class="profile-entry-text platform-name">${name}</p>
<div id="platform-${platform_id}" class="platform-remove-stack">
<img class="platform-remove" src="/static/res/cancel.svg" alt="Cancel" />
<img class="platform-remove-hover" src="/static/res/cancel-hover.svg" alt="Cancel" />
</div>
${add}
</div>
</template>
</div>
</div>
</div>
<div id="profile-ratings" class="page-subsection">
<div class="page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Ratings</p>
<div class="page-subheader-separator"></div>
</div>
</div>
</div>
</div>
</template>
</div>
</div>
<script src="/static/scripts/template.js"></script>
<script src="/static/scripts/common.js"></script>
<script src="/static/scripts/profile.js"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

View file

@ -1,130 +0,0 @@
let session = null;
const loadSession = () => {
session = JSON.parse(window.sessionStorage.getItem('session'));
if (session) {
document.querySelector(":root").style.setProperty('--accent-hue', session.hue);
}
};
const expandTemplates = async () => {
template.apply("navbar").values([
{ section: "left" },
{ section: "right" }
]);
template.apply("navbar-section-left").values([
{ item: "games", title: "Games" },
{ item: "achievements", title: "Achievements" }
]);
if (session) {
template.apply("navbar-section-right").values([
{ item: "profile", title: "Profile" },
{ item: "logout", title: "Logout" }
]);
} else {
template.apply("navbar-section-right").values([
{ item: "login", title: "Login" }
]);
}
template.apply("content-body").values([
{ page: "games", title: "Games" },
{ page: "achievements", title: "Achievements" },
{ page: "profile", title: "Profile" }
]);
template.apply("extern-games-page" ).values("games_page" );
template.apply("extern-achievements-page").values("achievements_page");
template.apply("extern-profile-page" ).values("profile_page" );
await template.expand();
};
let pages = null;
const loadPages = () => {
pages = document.querySelectorAll(".page");
}
const connectNavbar = () => {
const navItems = document.querySelectorAll(".navbar-item");
for (const item of navItems) {
if (item.dataset.pageName === "logout") {
item.addEventListener("click", (clickEvent) => {
fetch('https://localhost:4730/logout', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: session.key })
})
.then(response => {
window.sessionStorage.removeItem('session');
window.location.href = "/login.html";
});
});
} else if (item.dataset.pageName === "login") {
item.addEventListener("click", (clickEvent) => window.location.href = "/login.html");
} else {
item.addEventListener("click", (clickEvent) => {
const navItemPageId = item.dataset.pageName + "-page"
for (const page of pages) {
if (page.id === navItemPageId) {
page.style.display = "block";
} else {
page.style.display = "none";
}
}
});
}
}
};
const connectProfile = () => {
const games = document.querySelector("#profile-games-header");
const achievements = document.querySelector("#profile-achievements-header");
games.children[0].addEventListener("click", (clickEvent) => {
for (const page of pages) {
if (page.id === "games-page") {
page.style.display = "block";
} else {
page.style.display = "none";
}
}
});
achievements.children[0].addEventListener("click", (clickEvent) => {
for (page of pages) {
if (page.id === "achievements-page") {
page.style.display = "block";
} else {
page.style.display = "none";
}
}
});
}
const loadFilters = () => {
const filters = document.querySelectorAll(".list-page-filter");
for (let filter of filters) {
filter.addEventListener("click", (clickEvent) => {
if (filter.classList.contains("selected")) {
filter.classList.remove("selected");
} else {
filter.classList.add("selected");
}
});
}
}
window.addEventListener("load", async (loadEvent) => {
loadSession();
await expandTemplates();
loadPages();
connectNavbar();
connectProfile();
loadFilters();
});

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#aaa;}</style></defs><path class="cls-1" d="M1717.59861,392.24859l-65.17222-65.17219a81.91631,81.91631,0,0,0-115.84716,0L1020.675,842.98065,504.77075,327.07642a81.91631,81.91631,0,0,0-115.84717,0L323.7514,392.2486a81.91631,81.91631,0,0,0,0,115.84716L839.65564,1024,323.75141,1539.90429a81.91631,81.91631,0,0,0,0,115.84717l65.17216,65.17214a81.91632,81.91632,0,0,0,115.84716,0l515.90425-515.9043,515.90425,515.90431a81.91633,81.91633,0,0,0,115.84715,0l65.1722-65.17216a81.91631,81.91631,0,0,0,0-115.84718L1201.69434,1024l515.90428-515.90423A81.91632,81.91632,0,0,0,1717.59861,392.24859Z"/></svg>

After

Width:  |  Height:  |  Size: 716 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M1717.59861,392.24859l-65.17222-65.17219a81.91631,81.91631,0,0,0-115.84716,0L1020.675,842.98065,504.77075,327.07642a81.91631,81.91631,0,0,0-115.84717,0L323.7514,392.2486a81.91631,81.91631,0,0,0,0,115.84716L839.65564,1024,323.75141,1539.90429a81.91631,81.91631,0,0,0,0,115.84717l65.17216,65.17214a81.91632,81.91632,0,0,0,115.84716,0l515.90425-515.9043,515.90425,515.90431a81.91633,81.91633,0,0,0,115.84715,0l65.1722-65.17216a81.91631,81.91631,0,0,0,0-115.84718L1201.69434,1024l515.90428-515.90423A81.91632,81.91632,0,0,0,1717.59861,392.24859Z"/></svg>

After

Width:  |  Height:  |  Size: 716 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><defs><style>.cls-1{fill:#44444c;}</style></defs><path class="cls-1" d="M500,0C223.8576,0,0,223.8576,0,500c0,276.14233,223.8576,500,500,500s500-223.85767,500-500C1000,223.8576,776.1424,0,500,0Zm0,955C248.71045,955,45,751.28955,45,500S248.71045,45,500,45,955,248.71045,955,500,751.28955,955,500,955Z"/></svg>

After

Width:  |  Height:  |  Size: 404 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#aaa;}</style></defs><polygon class="cls-1" points="1717.02 0 1354.981 0 512 842.981 330.98 1024 1354.98 2048 1717.02 2048 1717.02 1685.961 1055.058 1024 1717.02 362.039 1717.02 0"/></svg>

After

Width:  |  Height:  |  Size: 310 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#eee;}</style></defs><polygon class="cls-1" points="1717.02 0 1354.981 0 512 842.981 330.98 1024 1354.98 2048 1717.02 2048 1717.02 1685.961 1055.058 1024 1717.02 362.039 1717.02 0"/></svg>

After

Width:  |  Height:  |  Size: 310 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#aaa;}</style></defs><path class="cls-1" d="M1776.471,452.54834l-645.29239,645.29238a56.9113,56.9113,0,0,1-40.24239,16.66895H990.40167a56.91135,56.91135,0,0,1-56.91134-56.91134V957.06378a56.9113,56.9113,0,0,1,16.66895-40.24239L1595.45166,271.529Z"/><path class="cls-1" d="M1534.54675,1056.511v479.48889H512.00012V513.45325H991.48907l256.00007-256.00006H401.27344A145.273,145.273,0,0,0,256.00012,402.72656v1244a145.273,145.273,0,0,0,145.27332,145.27332h1244a145.27292,145.27292,0,0,0,145.27331-145.27332V800.511Z"/><path class="cls-1" d="M1738.98066,53.01934h203.9098a52.0902,52.0902,0,0,1,52.0902,52.0902v151.8196a52.0902,52.0902,0,0,1-52.0902,52.0902h-203.9098a0,0,0,0,1,0,0v-256a0,0,0,0,1,0,0Z" transform="translate(418.82598 1373.17402) rotate(-45)"/></svg>

After

Width:  |  Height:  |  Size: 882 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M1776.471,452.54834l-645.29239,645.29238a56.9113,56.9113,0,0,1-40.24239,16.66895H990.40167a56.91135,56.91135,0,0,1-56.91134-56.91134V957.06378a56.9113,56.9113,0,0,1,16.66895-40.24239L1595.45166,271.529Z"/><path class="cls-1" d="M1534.54675,1056.511v479.48889H512.00012V513.45325H991.48907l256.00007-256.00006H401.27344A145.273,145.273,0,0,0,256.00012,402.72656v1244a145.273,145.273,0,0,0,145.27332,145.27332h1244a145.27292,145.27292,0,0,0,145.27331-145.27332V800.511Z"/><path class="cls-1" d="M1738.98066,53.01934h203.9098a52.0902,52.0902,0,0,1,52.0902,52.0902v151.8196a52.0902,52.0902,0,0,1-52.0902,52.0902h-203.9098a0,0,0,0,1,0,0v-256a0,0,0,0,1,0,0Z" transform="translate(418.82598 1373.17402) rotate(-45)"/></svg>

After

Width:  |  Height:  |  Size: 882 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#aaa;}</style></defs><path class="cls-1" d="M170.90729,1032.66212l72.83391-72.83391a76.49864,76.49864,0,0,1,108.18542,0l388.18146,388.18146,955.9653-955.9653a76.49864,76.49864,0,0,1,108.18542,0l72.83391,72.83391a76.49865,76.49865,0,0,1,0,108.18543L740.10808,1710.04834,170.90729,1140.84755A76.49865,76.49865,0,0,1,170.90729,1032.66212Z"/></svg>

After

Width:  |  Height:  |  Size: 466 B

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2048 2048"><defs><style>.cls-1{fill:#eee;}</style></defs><path class="cls-1" d="M170.90729,1032.66212l72.83391-72.83391a76.49864,76.49864,0,0,1,108.18542,0l388.18146,388.18146,955.9653-955.9653a76.49864,76.49864,0,0,1,108.18542,0l72.83391,72.83391a76.49865,76.49865,0,0,1,0,108.18543L740.10808,1710.04834,170.90729,1140.84755A76.49865,76.49865,0,0,1,170.90729,1032.66212Z"/></svg>

After

Width:  |  Height:  |  Size: 466 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,8 @@
window.addEventListener("load", async (loadEvent) => {
await loadCommon();
await commonTemplates();
await template.expand();
connectNavbar();
});

View file

@ -0,0 +1,97 @@
let root = null;
const loadRoot = () => {
const rootElement = document.documentElement;
root = {};
root.getProperty = (name) => window.getComputedStyle(document.documentElement).getPropertyValue(name);
root.setProperty = (name, value) => {
rootElement.style.setProperty(name, value);
}
};
let session = null;
const loadSession = async () => {
window.addEventListener('beforeunload', (beforeUnloadEvent) => {
if (session) {
window.sessionStorage.setItem('session', JSON.stringify(session));
} else {
window.sessionStorage.removeItem('session');
}
});
session = JSON.parse(window.sessionStorage.getItem('session'));
if (session) {
root.setProperty('--accent-hue', session.hue);
await fetch(`/api/auth/refresh`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: session.key })
})
.then(async response => ({ status: response.status, data: await response.json() }))
.then(response => {
if (response.status !== 200 && window.location.pathname != "/login") {
delete session.key;
window.location.href = "/login";
}
});
}
};
const loadCommon = async () => {
loadRoot();
await loadSession();
}
const commonTemplates = async () => {
template.apply("navbar").values([
{ section: "left" },
{ section: "right" }
]);
template.apply("navbar-section-left").values([
{ item: "project", title: "Project" },
{ item: "about", title: "About" }
]);
if (session) {
template.apply("navbar-section-right").values([
{ item: "profile", title: "Profile" },
{ item: "logout", title: "Logout" }
]);
} else {
template.apply("navbar-section-right").values([
{ item: "login", title: "Login" }
]);
}
};
const connectNavbar = () => {
const navItems = document.querySelectorAll(".navbar-item");
for (const item of navItems) {
if (item.dataset.pageName === "logout") {
item.addEventListener("click", (clickEvent) => {
fetch(`/api/auth/logout`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: session.key })
})
.then(response => {
session = undefined;
window.location.href = "/login";
});
});
} else if (item.dataset.pageName === "profile") {
item.addEventListener("click", (clickEvent) => window.location.href = `/profile/${session.id}`);
} else if (item.dataset.pageName === "project") {
item.addEventListener("click", (clickEvent) => window.location.href = `/`);
} else {
item.addEventListener("click", (clickEvent) => window.location.href = `/${item.dataset.pageName}`);
}
}
};

View file

@ -0,0 +1,24 @@
const expandTemplates = async () => {
await commonTemplates();
}
const loadFilters = () => {
const filtersButton = document.querySelector("#filter-dropdown-stack");
const filters = document.querySelector("#list-page-filters-flex");
filtersButton.addEventListener("click", (clickEvent) => {
filtersButton.classList.toggle("active");
filters.classList.toggle("active");
});
}
window.addEventListener("load", async (loadEvent) => {
loadRoot();
loadSession();
await expandTemplates();
await template.expand();
connectNavbar();
loadFilters();
});

View file

@ -1,6 +1,7 @@
window.addEventListener("load", (loadEvent) => {
let session = window.sessionStorage.getItem('session');
if (session) {
window.addEventListener("load", async (loadEvent) => {
await loadCommon();
if (session && session.key) {
window.location.href = '/';
}
@ -18,6 +19,11 @@ window.addEventListener("load", (loadEvent) => {
const header = document.querySelector("#login-header-text");
const error = document.querySelector("#error-message");
if (session) {
error.style.display = "block";
error.textContent = "You have been signed out due to inactivity";
}
const raiseError = (errorFields, message) => {
for (const key in fields) {
if (errorFields.includes(key)) {
@ -72,7 +78,7 @@ window.addEventListener("load", (loadEvent) => {
raiseError([ "password", "confirm" ], "Password cannot be empty");
} else {
freeze();
fetch('https://localhost:4730/create_user', {
fetch(`/api/auth/create_user`, {
method: 'POST',
mode: 'cors',
headers: {
@ -81,10 +87,10 @@ window.addEventListener("load", (loadEvent) => {
body: JSON.stringify({ email: fields.email.value, username: fields.username.value, password: fields.password.value })
})
.then(async response => ({ status: response.status, data: await response.json() }))
.then(response =>{
.then(response => {
const data = response.data;
if (response.status === 200) {
window.sessionStorage.setItem('session', JSON.stringify(data));
if (response.status === 201) {
session = data;
window.location.href = "/";
} else if (response.status === 500) {
raiseError([], "Internal server error :(");
@ -95,6 +101,9 @@ window.addEventListener("load", (loadEvent) => {
} else if (data.code === 2) {
raiseError([ "email" ], "Invalid email address");
fields.email.value = '';
} else {
raiseError([ "email" ], "Server is bad :p");
fields.email.value = '';
}
}
})
@ -130,7 +139,7 @@ window.addEventListener("load", (loadEvent) => {
raiseError([ "password" ], "Password cannot be empty");
} else {
freeze();
fetch('https://localhost:4730/login', {
fetch(`/api/auth/login`, {
method: 'POST',
mode: 'cors',
headers: {
@ -142,8 +151,7 @@ window.addEventListener("load", (loadEvent) => {
.then(response => {
const data = response.data;
if (response.status === 200) {
console.log(data);
window.sessionStorage.setItem('session', JSON.stringify(data));
session = data;
window.location.href = "/";
} else if (response.status === 500) {
raiseError([], "Internal server error :(");

View file

@ -0,0 +1,278 @@
let profileId = window.location.pathname.split('/').pop();
let isReturn = false;
let profileData = null;
const loadProfile = () => {
{
const lists = document.querySelectorAll(".profile-list");
for (const list of lists) {
if (list.querySelectorAll(".profile-entry").length === 0) {
list.parentElement.removeChild(list);
}
}
}
{
const validImageFile = (type) => {
return type === "image/apng"
|| type === "image/avif"
|| type === "image/gif"
|| type === "image/jpeg"
|| type === "image/png"
|| type === "image/svg+xml"
|| type === "image/webp"
}
const editProfileButton = document.querySelector("#info-edit-stack");
const saveProfileButton = document.querySelector("#info-save-stack");
const usernameText = document.querySelector("#profile-info-username-text");
const usernameField = document.querySelector("#profile-info-username-field");
const finalizeName = () => {
if (usernameField.value !== '') {
fetch(`/api/user/${profileId}/username`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: `{ "sessionKey": "${session.key}", "username": "${usernameField.value}" }`
}).then(response => {
if (response.status === 201) {
usernameText.textContent = usernameField.value.substring(0, 32);
}
});
}
};
usernameField.addEventListener("input", (inputEvent) => {
if (usernameField.value.length > 32) {
usernameField.value = usernameField.value.substring(0, 32);
}
});
const pfp = document.querySelector("#profile-info-pfp-img");
const pfpStack = document.querySelector("#profile-info-pfp");
const upload = document.querySelector("#profile-info-pfp-upload");
const uploadHover = document.querySelector("#profile-info-pfp-upload-hover");
const uploadInvalid = document.querySelector("#profile-info-pfp-upload-invalid");
const togglePlatformEdit = (clickEvent) => {
editProfileButton.classList.toggle("active");
saveProfileButton.classList.toggle("active");
usernameText.classList.toggle("active");
usernameField.classList.toggle("active");
upload.classList.toggle("active");
uploadHover.classList.toggle("active");
uploadInvalid.classList.toggle("active");
pfpStack.classList.remove("hover");
pfpStack.classList.remove("invalid");
};
editProfileButton.addEventListener("click", togglePlatformEdit);
editProfileButton.addEventListener("click", () => {
usernameField.value = usernameText.textContent;
});
saveProfileButton.addEventListener("click", togglePlatformEdit);
saveProfileButton.addEventListener("click", finalizeName);
pfpStack.addEventListener("drop", (dropEvent) => {
if (upload.classList.contains("active")) {
dropEvent.preventDefault();
pfpStack.classList.remove("hover");
pfpStack.classList.remove("invalid");
if (dropEvent.dataTransfer.files) {
const file = dropEvent.dataTransfer.files[0];
if (validImageFile(file.type)) {
const data = new FormData();
data.append('session', new Blob([`{ "key": "${session.key}" }`], { type: `application/json` }));
data.append('file', file);
fetch(`/api/user/${profileId}/image`, {
method: 'POST',
mode: 'cors',
body: data
}).then(response => {
if (upload.classList.contains("active")) {
if (response.status === 201) {
pfp.src = `/api/user/${profileId}/image?time=${Date.now()}`;
} else {
pfpStack.classList.add("invalid");
}
}
});
return;
}
}
pfpStack.classList.add("invalid");
}
});
pfpStack.addEventListener("dragover", (dragEvent) => {
if (upload.classList.contains("active")) {
dragEvent.preventDefault();
}
});
pfpStack.addEventListener("dragenter", (dragEvent) => {
if (upload.classList.contains("active")) {
pfpStack.classList.remove("hover");
pfpStack.classList.remove("invalid");
if (dragEvent.dataTransfer.types.includes("application/x-moz-file")) {
pfpStack.classList.add("hover");
} else {
pfpStack.classList.add("invalid");
}
}
});
pfpStack.addEventListener("dragleave", (dragEvent) => {
if (upload.classList.contains("active")) {
pfpStack.classList.remove("hover");
pfpStack.classList.remove("invalid");
}
});
}
{
const editPlatformsButton = document.querySelector("#platform-edit-stack");
const savePlatformsButton = document.querySelector("#platform-save-stack");
const platforms = document.querySelectorAll("#profile-platforms .profile-entry");
const togglePlatformEdit = (clickEvent) => {
editPlatformsButton.classList.toggle("active");
savePlatformsButton.classList.toggle("active");
for (const platform of platforms) {
platform.classList.toggle("editing");
}
};
editPlatformsButton.addEventListener("click", togglePlatformEdit);
savePlatformsButton.addEventListener("click", togglePlatformEdit);
const steamButtons = [
document.querySelector("#add-steam"),
document.querySelector("#platform-0"),
];
steamButtons[0].addEventListener("click", (clickEvent) => {
window.location.href = "/auth/steam";
});
steamButtons[1].addEventListener("click", (clickEvent) => {
fetch(`/api/user/${profileId}/platforms/remove`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ sessionKey: session.key, platformId: 0 })
});
steamButtons[1].parentElement.classList.remove("connected");
});
if (isReturn) {
editPlatformsButton.click();
}
}
// Canvasing
const completionCanvas = document.querySelector("#profile-completion-canvas");
const completionText = document.querySelector("#profile-completion-text");
const STROKE_WIDTH = 0.18;
const style = window.getComputedStyle(completionCanvas);
const context = completionCanvas.getContext('2d');
const drawCanvas = () => profileData.then(data => {
const width = Number(style.getPropertyValue('width').slice(0, -2));
const height = width;
context.canvas.width = width;
context.canvas.height = height;
context.clearRect(0, 0, width, height);
context.strokeStyle = root.getProperty('--accent-value3');
context.lineWidth = (width / 2) * STROKE_WIDTH;
context.beginPath();
context.arc(width / 2, height / 2, (width / 2) * (1 - STROKE_WIDTH / 2), -0.5 * Math.PI, (-0.5 + (data.average === null ? 0 : (data.average / 100) * 2)) * Math.PI);
context.stroke();
});
window.addEventListener('resize', drawCanvas);
drawCanvas();
if (profileId === session.id) {
document.querySelector("#profile-page").classList.add("self");
}
}
const expandTemplates = async () => {
await commonTemplates();
template.apply("profile-page").promise(profileData.then(data => ({
id: profileId,
username: data.username,
completed: data.completed,
average: data.average === null ? "N/A" : data.average + "%",
perfect: data.perfect,
})));
template.apply("profile-platforms-list").promise(profileData.then(data =>
data.platforms.map(platform => ({
platform_id: platform.id,
img: `<img class="profile-entry-icon" src="/api/platform/image/${platform.id}" alt="Steam Logo" />`,
name: platform.name,
connected: platform.connected ? "connected" : "",
add:
(platform.id === 0 ? `<img id="add-steam" class="platform-add" src="https://community.cloudflare.steamstatic.com/public/images/signinthroughsteam/sits_01.png" alt="Add" />` :
(platform.id === 1 ? `<p class="platform-unsupported">Coming soon...</p>` :
(platform.id === 2 ? `<p class="platform-unsupported">Coming soon...</p>` :
"")))
}))
));
}
window.addEventListener("load", async (loadEvent) => {
await loadCommon();
if (!/\d+/.test(profileId)) {
isReturn = true;
const platform = profileId;
if (!session) {
window.location.href = "/404";
} else {
profileId = session.lastProfile;
delete session.lastProfile;
}
if (platform === 'steam') {
const query = new URLSearchParams(window.location.search);
if (query.get('openid.mode') === 'cancel') {
} else {
// Regex courtesy of https://github.com/liamcurry/passport-steam/blob/master/lib/passport-steam/strategy.js
var steamId = /^https?:\/\/steamcommunity\.com\/openid\/id\/(\d+)$/.exec(query.get('openid.claimed_id'))[1];
await fetch("/api/user/platforms/add", {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ sessionKey: session.key, userId: profileId, platformId: 0, platformUserId: `${steamId}` })
});
}
}
window.history.replaceState({}, '', `/profile/${profileId}`);
} else if (/\d+/.test(profileId)) {
profileId = Number(profileId);
if (session) {
session.lastProfile = profileId;
}
} else {
// Handle error
}
profileData = fetch(`/api/user/${profileId}`, { method: 'GET', mode: 'cors' })
.then(response => response.json());
await expandTemplates();
await template.expand();
connectNavbar();
loadProfile();
});

View file

@ -151,7 +151,7 @@ var template = template || {};
const data = child.dataset.template.split(/\s*:\s*/);
return {
id: data[0],
typeCapture: parseType(data[1] || 'Begin'),
typeCapture: parseType(data[1] || 'Basic'),
element: child
};
});

View file

@ -0,0 +1,13 @@
#about-page {
max-width: 1600px;
}
#about-text {
margin: 0;
padding: 16px;
font-size: 18px;
color: var(--foreground);
background-color: var(--distinction);
}

View file

@ -134,8 +134,6 @@ html, body {
background-color: var(--background);
box-shadow: 0px 0px 5px 10px var(--shadow-color);
display: none;
}
.page-subsection {
@ -171,17 +169,28 @@ html, body {
.page-subheader {
margin-bottom: 16px;
width: 100%;
}
.page-subheader-flex {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
.page-header-text,
.page-subheader-text {
width: max-content;
margin: 0 0 0.25em;
color: var(--foreground);
cursor: default;
overflow: hidden;
text-overflow: ellipsis;
flex-grow: 1;
}
.page-header-text.link,
@ -199,10 +208,17 @@ html, body {
font-size: 48px;
}
.page-subheader-text {
.page-subheader-text,
.page-subheader-icon {
font-size: 32px;
}
.page-subheader-icon {
margin: 0 0 0.25em;
height: 32px;
padding: 0;
}
.page-header-separator,
.page-subheader-separator {
width: 100%;
@ -214,8 +230,6 @@ html, body {
.list-page-search {
box-sizing: border-box;
margin: 16px;
display: flex;
flex-direction: row;
justify-content: center;
@ -285,6 +299,9 @@ html, body {
.list-page-filter-chunk {
background-color: var(--distinction);
width: 100%;
height: 100%;
}
.list-page-filter {
@ -394,7 +411,6 @@ html, body {
margin: 0;
padding: 0 12px;
width: 0;
height: 64px;
line-height: 64px;

View file

@ -0,0 +1,111 @@
#index-page {
max-width: 1600px;
}
#list-page-search-filters {
width: 100%;
height: max-content;
}
#list-page-search-dropdown {
display: flex;
flex-direction: row;
align-items: center;
}
#search-wrapper {
width: 100%;
}
#list-page-search-pair {
flex-grow: 1;
}
#filter-dropdown-wrapper {
box-sizing: border-box;
height: 84px;
width: 84px;
}
#filter-dropdown-stack {
width: 100%;
height: 100%;
position: relative;
}
#filter-dropdown-stack.active {
transform: rotateZ(-90deg);
}
#filter-dropdown-button {
position: absolute;
left: 0;
top: 0;
height: 100%;
display: block;
}
#filter-dropdown-stack:hover > #filter-dropdown-button {
display: none;
}
#filter-dropdown-button-hover {
position: absolute;
left: 0;
top: 0;
height: 100%;
display: none;
}
#filter-dropdown-stack:hover > #filter-dropdown-button-hover {
display: block;
}
#list-page-filters-flex {
display: none;
width: 100%;
height: max-content;
flex-direction: row;
}
#list-page-filters-flex.active {
display: flex;
}
.list-page-filter-section {
box-sizing: border-box;
flex-basis: 0;
flex-grow: 1;
height: 100%;
display: flex;
flex-direction: column;
}
#list-page-filters-background {
background-color: var(--distinction);
}
.list-page-entry-text.achievement-name {
flex-grow: 3;
flex-basis: 0;
}
.list-page-entry-text.achievement-description {
flex-grow: 6;
flex-basis: 0;
}
.list-page-entry-text.achievement-stages {
flex-grow: 1;
flex-basis: 0;
}

View file

@ -0,0 +1,401 @@
#profile-page {
max-width: 1600px;
}
.profile-list {
width: 100%;
height: max-content;
border-radius: 8px;
overflow: hidden;
box-shadow: 0px 0px 8px 8px var(--shadow-color);
}
.profile-entry {
overflow: hidden;
height: 64px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
background-color: var(--distinction);
}
.profile-entry-left {
height: 100%;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.profile-entry-right {
height: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.profile-entry-icon {
width: 64px;
flex-grow: 0;
}
.profile-entry-text {
box-sizing: border-box;
margin: 0;
padding: 0px 16px;
height: 100%;
line-height: 64px;
color: var(--foreground);
font-size: 24px;
border-top: 1px solid var(--background);
flex-basis: max-content;
flex-grow: 0;
}
.top > .profile-entry-text {
border: 0;
}
.profile-entry-text.platform-name {
flex-grow: 1;
}
#profile-section-1 {
box-sizing: border-box;
width: 100%;
height: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}
#profile-info {
width: 50%;
height: max-content;
max-width: 480px;
}
#profile-info-username-text.active,
#profile-info-username-field {
display: none;
}
#profile-info-username-field.active {
display: block;
}
#profile-info-username-field {
margin-right: 8px;
padding: 4px;
font-size: 24px;
color: var(--background);
background-color: var(--foreground);
border-radius: 8px;
border: 0;
outline: none;
}
#profile-info-pfp-border {
box-sizing: border-box;
padding: 24px;
background-color: var(--distinction);
width: 100%;
height: max-content;
box-shadow: 0px 0px 8px 8px var(--shadow-color);
border-radius: 8px;
}
#profile-info-pfp {
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 8px;
position: relative;
}
#profile-info-pfp-img {
display: block;
width: 100%;
height: 100%;
border-radius: 8px;
object-fit: contain;
background-color: var(--background);
position: absolute;
}
#profile-info-pfp-vignette {
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: inset 0px 0px 30px 10px var(--shadow-color);
border-radius: 8px;
position: absolute;
z-index: 1;
}
#profile-info-pfp-upload,
#profile-info-pfp-upload-hover,
#profile-info-pfp-upload-invalid {
top: 0;
left: 0;
width: 100%;
border-radius: 8px;
background-color: var(--background);
opacity: 0.8;
display: block;
visibility: hidden;
}
#profile-info-pfp-upload {
position: relative;
}
#profile-info-pfp-upload-hover,
#profile-info-pfp-upload-invalid {
position: absolute;
}
#profile-info-pfp #profile-info-pfp-upload.active,
#profile-info-pfp.hover #profile-info-pfp-upload-hover.active,
#profile-info-pfp.invalid #profile-info-pfp-upload-invalid.active {
visibility: visible;
}
#profile-info-pfp.hover #profile-info-pfp-upload.active,
#profile-info-pfp.invalid #profile-info-pfp-upload.active {
visibility: hidden;
}
#profile-stats {
flex-grow: 1;
display: flex;
flex-direction: row;
}
#profile-stats-numeric {
flex-grow: 1;
max-width: 300px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
#profile-completion-stack {
width: 100%;
height: max-content;
position: relative;
}
#profile-completion-background {
width: 100%;
display: block;
}
#profile-completion-canvas {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
#profile-completion-text {
margin: 0;
width: 100%;
height: 100%;
color: var(--foreground);
font-size: 64px;
position: absolute;
left: 0;
top: 0;
display: flex;
justify-content: center;
align-items: center;
}
#profile-perfect-text {
margin: 0;
height: 48px;
color: var(--foreground);
font-size: 48px;
line-height: 48px;
text-align: center;
}
#profile-section-2 {
box-sizing: border-box;
width: 100%;
height: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}
#profile-hardest {
flex-grow: 1;
height: 100%;
}
#profile-platforms {
flex-grow: 1;
max-width: 480px;
}
#profile-platforms .profile-entry {
display: none;
}
#profile-platforms .profile-entry-text {
color: var(--foreground);
}
#profile-platforms .profile-entry.connected,
#profile-platforms .profile-entry.editing {
display: flex;
}
#profile-page .profile-edit-stack,
#profile-page .profile-save-stack {
display: none;
}
#profile-page.self .profile-edit-stack,
#profile-page.self .profile-save-stack.active {
display: block;
width: max-content;
height: max-content;
}
#profile-page.self .profile-edit-stack.active,
#profile-page.self .profile-save-stack {
display: none;
}
.profile-edit-stack:hover > .profile-edit-hover,
.profile-edit {
display: block;
}
.profile-edit-stack:hover > .profile-edit,
.profile-edit-hover {
display: none;
}
.profile-save-stack:hover > .profile-save-hover,
.profile-save {
display: block;
}
.profile-save-stack:hover > .profile-save,
.profile-save-hover {
display: none;
}
.profile-entry .platform-remove-stack,
.profile-entry .platform-add,
.profile-entry .platform-unsupported,
.profile-entry.connected.editing .platform-add {
border-top: 1px solid var(--background);
display: none;
}
.profile-entry.connected.editing .platform-remove-stack {
box-sizing: border-box;
display: block;
height: 100%;
width: max-content;
flex-grow: 0;
}
.platform-remove, .platform-remove-hover {
box-sizing: border-box;
padding: 12px;
height: 100%;
}
.profile-entry.connected.editing .platform-remove-stack .platform-remove,
.profile-entry.connected.editing .platform-remove-stack:hover .platform-remove-hover {
display: block;
}
.profile-entry.connected.editing .platform-remove-stack:hover .platform-remove,
.profile-entry.connected.editing .platform-remove-stack .platform-remove-hover {
display: none;
}
.profile-entry .platform-add {
box-sizing: border-box;
height: 100%;
padding: 16px 8px;
}
.profile-entry.editing .platform-add {
display: block;
}
.profile-entry.editing .platform-unsupported {
box-sizing: border-box;
display: block;
margin: 0;
padding: 0% 2%;
line-height: 63px;
font-size: 24px;
color: var(--foreground-disabled);
}
#profile-ratings {
flex-grow: 1;
}

View file

@ -1,9 +1,10 @@
:root {
--background-dark: #111117;
--background: #22222A;
--foreground-dark: #AAAAAA;
--foreground: #EEEEEE;
--distinction: #44444C;
--background-dark: #111117;
--background: #22222A;
--foreground-disabled: #77777D;
--foreground-dark: #AAAAAA;
--foreground: #EEEEEE;
--distinction: #44444C;
--accent-hue: 0;

View file

@ -1,232 +0,0 @@
html, body {
background-color: var(--background-dark);
margin: 0;
border: 0;
padding: 0;
width: 100%;
height: 100%;
font-family: sans-serif;
}
#games-page {
max-width: 1920px;
}
.list-page-entry-text.game-name {
flex-grow: 1;
}
.list-page-entry-text.game-description {
flex-grow: 2;
}
#achievements-page {
max-width: 1920px;
}
.list-page-entry-text.achievement-name {
flex-grow: 4;
}
.list-page-entry-text.achievement-description {
box-sizing: border-box;
margin: 0;
padding: 0 12px;
width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-grow: 8;
}
.list-page-entry-text.achievement-stages {
flex-grow: 1;
}
#profile-page {
max-width: 1600px;
display: block;
}
#profile-section-1 {
box-sizing: border-box;
width: 100%;
height: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}
#profile-info {
width: 50%;
height: max-content;
max-width: 480px;
}
#profile-info-pfp-border {
box-sizing: border-box;
padding: 24px;
background-color: var(--distinction);
width: 100%;
max-width: 640px;
height: max-content;
box-shadow: 0px 0px 8px 8px var(--shadow-color);
border-radius: 8px;
}
#profile-info-pfp {
width: 100%;
height: 100%;
position: relative;
}
#profile-info-pfp-vignette {
top: 0%;
left: 0%;
width: 100%;
height: 100%;
box-shadow: inset 0px 0px 50px 30px var(--shadow-color);
border-radius: 8px;
position: absolute;
z-index: 1;
}
#profile-info-pfp-img {
display: block;
width: 100%;
border-radius: 8px;
position: relative;
}
.profile-list {
width: 100%;
height: max-content;
border-radius: 8px;
overflow: hidden;
box-shadow: 0px 0px 8px 8px var(--shadow-color);
}
.profile-entry {
overflow: hidden;
height: 64px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: var(--distinction);
}
.profile-entry.accented {
background-color: var(--accent-value1);
}
.profile-entry-left {
height: 100%;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.profile-entry-right {
height: 100%;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.profile-entry-icon {
width: 64px;
flex-grow: 0;
}
.profile-entry-text {
box-sizing: border-box;
margin: 0;
padding: 0px 16px;
height: 100%;
line-height: 64px;
color: var(--foreground);
font-size: 24px;
border-top: 1px solid var(--background);
flex-grow: 0;
}
.top > .profile-entry-text {
border: 0;
}
.accented > .profile-entry-text {
border-color: var(--accent-value0);
}
.profile-entry-text.platform-name,
.profile-entry-text.game-name,
.profile-entry-text.achievement-name {
flex-grow: 1;
}
.profile-entry-text.accented {
display: none;
}
.profile-entry.accented .profile-entry-text.accented {
display: block;
}
#profile-platforms {
flex-grow: 1;
}
#profile-section-2 {
box-sizing: border-box;
width: 100%;
height: max-content;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}
#profile-games,
#profile-achievements {
width: 50%;
height: max-content;
}

View file

@ -1,54 +0,0 @@
<div class="page-subsection">
<div class="list-page-search page-subsection-chunk">
<label for="achievement-search">Search</label>
<input id="achievement-search" type="text" placeholder="Name" name="achievement-search"/>
</div>
<div class="list-page-partitions">
<div class="list-page-filter-partition">
<div class="page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Filters</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="from-games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">My Games</p>
</div>
<div id="in-progress-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">In Progress</p>
</div>
<div id="completed-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Completed</p>
</div>
</div>
</div>
</div>
</div>
<div class="list-page-list-partition page-subsection-wrapper">
<div class="page-subsection-chunk">
<div class="list-page-list">
<div class="list-page-header">
<p class="list-page-entry-icon"></p>
<p class="list-page-entry-text achievement-name">Name</p>
<p class="list-page-entry-text achievement-description">Description</p>
<p class="list-page-entry-text achievement-stages">Stages</p>
</div>
<template data-template="achievements-page-list: List<Basic>">
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
<div class="list-page-entry-text-section">
<p class="list-page-entry-text achievement-name">${achievement-name}</p>
<p class="list-page-entry-text achievement-description">${achievement-description}</p>
<p class="list-page-entry-text achievement-stages">${stages}</p>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,65 +0,0 @@
<div class="page-subsection">
<div class="list-page-search page-subsection-chunk">
<label for="game-search">Search</label>
<input id="game-search" type="text" placeholder="Name" game-name="game-search" name="game-search"/>
</div>
<div class="list-page-partitions">
<div class="list-page-filter-partition">
<div class="page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Filters</p>
<div class="page-subheader-separator"></div>
</div>
<div class="list-page-filter-chunk page-subsection-chunk">
<div class="page-subsection-wrapper">
<div id="games-owned-filter" class="list-page-filter">
<div class="list-page-filter-checkbox"></div>
<p class="list-page-filter-name">Games Owned</p>
</div>
</div>
</div>
</div>
</div>
<div class="list-page-list-partition page-subsection-wrapper">
<div class="page-subsection-chunk">
<div class="list-page-list">
<div class="list-page-header">
<p class="list-page-entry-icon"></p>
<p class="list-page-entry-text game-name">Name</p>
<p class="list-page-entry-text game-description">Description</p>
</div>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
<div class="list-page-entry">
<img class="list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
<p class="list-page-entry-text game-name">Latin</p>
<p class="list-page-entry-text game-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,172 +0,0 @@
<div id="profile-section-1">
<div id="profile-info" class="page-subsection">
<div class="page-subsection-wrapper">
<div class="page-subheader">
<p id="profile-info-name" class="page-subheader-text">Jane Doe</p>
<div class="page-subheader-separator"></div>
</div>
<div id="profile-info-pfp-border" class="page-subsection-chunk">
<div id="profile-info-pfp">
<img id="profile-info-pfp-img" src="res/guest_pfp.png" alt="User's Profile Pictuer" />
<div id="profile-info-pfp-vignette"></div>
</div>
</div>
</div>
</div>
<div id="profile-platforms" class="page-subsection">
<div class="page-subsection-wrapper">
<div class="page-subheader">
<p class="page-subheader-text">Platforms</p>
<div class="page-subheader-separator"></div>
</div>
<div class="profile-list page-subsection-chunk">
<div class="profile-entry accented top">
<img class="profile-entry-icon" src="res/steam.png" alt="Steam Logo" />
<p class="profile-entry-text platform-name">Steam</p>
<p class="profile-entry-text accented">Connected</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/xbox.png" alt="Xbox Logo" />
<p class="profile-entry-text platform-name">Xbox Live</p>
<p class="profile-entry-text accented">Connected</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/psn.png" alt="PSN Logo" />
<p class="profile-entry-text platform-name">PSN</p>
<p class="profile-entry-text accented">Connected</p>
</div>
</div>
</div>
</div>
</div>
<div id="profile-section-2">
<div id="profile-games" class="page-subsection">
<div class="page-subsection-wrapper">
<div id="profile-games-header" class="page-subheader">
<p class="page-subheader-text link">Games</p>
<div class="page-subheader-separator"></div>
</div>
<div class="profile-list page-subsection-chunk">
<div class="profile-entry game top">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
<div class="profile-entry game">
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
<p class="profile-entry-text game-name">Latin</p>
</div>
</div>
</div>
</div>
<div id="profile-achievements" class="page-subsection">
<div class="page-subsection-wrapper">
<div id="profile-achievements-header" class="page-subheader">
<p class="page-subheader-text link">Achievements</p>
<div class="page-subheader-separator"></div>
</div>
<div class="profile-list page-subsection-chunk">
<div class="profile-entry accented top">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry accented">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry accented">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry accented">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry accented">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry accented">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry accented">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
<div class="profile-entry">
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
<p class="profile-entry-text achievement-name">Lorem Ipsum</p>
<p class="profile-entry-text accented">Completed</p>
</div>
</div>
</div>
</div>
</div>