Added user login and account creation
This commit is contained in:
parent
9ba8a99e82
commit
5a1dd33dfe
19 changed files with 1276 additions and 874 deletions
|
@ -1,7 +1,5 @@
|
|||
const express = require('express');
|
||||
const morgan = require('morgan' );
|
||||
const fs = require('fs' );
|
||||
const https = require('https' );
|
||||
const express = require('express');
|
||||
const morgan = require('morgan' );
|
||||
|
||||
const config = require('./config.js').load(process.argv[2]);
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<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" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
41
frontend/webpage/login.html
Normal file
41
frontend/webpage/login.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="navbar"></div>
|
||||
|
||||
<div id="content-body">
|
||||
<div id="login-page" class="page">
|
||||
<div class="page-header">
|
||||
<p class="page-header-text">Achievements Project</p>
|
||||
<div class="page-header-separator"></div>
|
||||
</div>
|
||||
<div id="login-header">
|
||||
<p id="login-header-text" class="page-subheader-text">Login</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
</div>
|
||||
<div id="login-form">
|
||||
<p id="error-message">Egg</p>
|
||||
<input id="email" class="login-field" type="text" placeholder="Email"></input>
|
||||
<input id="username" class="login-field" type="text" placeholder="Username"></input>
|
||||
<input id="password" class="login-field" type="password" placeholder="Password"></input>
|
||||
<input id="confirm" class="login-field" type="password" placeholder="Confirm your password"></input>
|
||||
<div id="login-buttons">
|
||||
<div id="create-user-button" class="ap-button login">Create Account</div>
|
||||
<div id="login-button" class="ap-button login">Login</div>
|
||||
</div>
|
||||
<p id="warning">WARNING! The security of this project is questionable at best. Please refrain from using any truly sensitive data.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="scripts/template.js"></script>
|
||||
<script src="scripts/login.js"></script>
|
||||
</body>
|
||||
</html>
|
122
frontend/webpage/scripts/login.js
Normal file
122
frontend/webpage/scripts/login.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
window.addEventListener("load", (loadEvent) => {
|
||||
const fields = {
|
||||
email: document.querySelector("#email" ),
|
||||
username: document.querySelector("#username"),
|
||||
password: document.querySelector("#password"),
|
||||
confirm: document.querySelector("#confirm" )
|
||||
};
|
||||
|
||||
const createUser = document.querySelector("#create-user-button");
|
||||
const login = document.querySelector("#login-button");
|
||||
|
||||
const header = document.querySelector("#login-header-text");
|
||||
const error = document.querySelector("#error-message");
|
||||
|
||||
const raiseError = (errorFields, message) => {
|
||||
for (const key in fields) {
|
||||
if (errorFields.includes(key)) {
|
||||
fields[key].classList.add("error");
|
||||
} else {
|
||||
fields[key].classList.remove("error");
|
||||
}
|
||||
}
|
||||
|
||||
error.style.display = "block";
|
||||
error.textContent = message;
|
||||
}
|
||||
|
||||
let frozen = false;
|
||||
|
||||
const switchToCreateAction = (clickEvent) => {
|
||||
if (!frozen) {
|
||||
fields.username.style.display = "block";
|
||||
fields.confirm.style.display = "block";
|
||||
login.style.display = "none";
|
||||
header.textContent = "Create User";
|
||||
|
||||
createUser.removeEventListener("click", switchToCreateAction);
|
||||
createUser.addEventListener("click", createUserAction);
|
||||
}
|
||||
};
|
||||
const createUserAction = (clickEvent) => {
|
||||
if (!frozen) {
|
||||
if (fields.email.value === '') {
|
||||
raiseError([ "email" ], "Email cannot be empty");
|
||||
} else if (fields.username.value === '') {
|
||||
raiseError([ "username" ], "Username cannot be empty");
|
||||
} else if (fields.password.value !== fields.confirm.value) {
|
||||
raiseError([ "password", "confirm" ], "Password fields did not match");
|
||||
} else if (fields.password.value === '') {
|
||||
raiseError([ "password", "confirm" ], "Password cannot be empty");
|
||||
} else {
|
||||
frozen = true;
|
||||
fetch('https://localhost:4730/create_user', {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
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 => {
|
||||
const data = response.data;
|
||||
if (response.status === 200) {
|
||||
window.sessionStorage.setItem('sessionKey', data.key);
|
||||
window.location.href = "/";
|
||||
} else if (response.status === 500) {
|
||||
raiseError([], "Internal server error :(");
|
||||
} else {
|
||||
if (data.code === 1) {
|
||||
raiseError([ "email" ], "A user with that email is already registered");
|
||||
fields.email.value = '';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
raiseError([], "Server error :(");
|
||||
}).then(() => frozen = false);
|
||||
}
|
||||
}
|
||||
};
|
||||
createUser.addEventListener("click", switchToCreateAction);
|
||||
|
||||
const loginAction = (clickEvent) => {
|
||||
if (!frozen) {
|
||||
if (fields.email.value === '') {
|
||||
raiseError([ "email" ], "Email cannot be empty");
|
||||
} else if (fields.password.value === '') {
|
||||
raiseError([ "password" ], "Password cannot be empty");
|
||||
} else {
|
||||
frozen = true;
|
||||
fetch('https://localhost:4730/login', {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email: fields.email.value, password: fields.password.value })
|
||||
})
|
||||
.then(async response => ({ status: response.status, data: await response.json() }))
|
||||
.then(response => {
|
||||
const data = response.data;
|
||||
if (response.status === 200) {
|
||||
window.sessionStorage.setItem('sessionKey', data.key);
|
||||
window.location.href = "/";
|
||||
} else if (response.status === 500) {
|
||||
raiseError([], "Internal server error :(");
|
||||
} else {
|
||||
raiseError([ "email", "password" ], "Email or password is incorrect");
|
||||
fields.password.value = '';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
raiseError([], "Unknown error :(");
|
||||
}).then(() => frozen = false);
|
||||
}
|
||||
}
|
||||
};
|
||||
login.addEventListener("click", loginAction);
|
||||
});
|
|
@ -1,142 +1,141 @@
|
|||
var template = template || {};
|
||||
|
||||
template.type = {};
|
||||
template.type._entryMap = new Map();
|
||||
template.type.register = (type, callback) => {
|
||||
if (typeof type !== 'string') {
|
||||
console.error(`'type' must be a string, recieved: `, type);
|
||||
} else {
|
||||
const TYPE_REGEX = /^(\w+)\s*(<\s*\?(?:\s*,\s*\?)*\s*>)?\s*$/;
|
||||
const result = type.match(TYPE_REGEX);
|
||||
if (result === null) {
|
||||
console.error(`'${type}' is not a valid type id`);
|
||||
} else {
|
||||
if (result[2] === undefined) {
|
||||
result[2] = 0;
|
||||
} else {
|
||||
result[2] = result[2].split(/\s*,\s*/).length;
|
||||
}
|
||||
const completeType = result[1] + ':' + result[2];
|
||||
if (template.type._entryMap.get(completeType) === undefined) {
|
||||
template.type._entryMap.set(completeType, async function() {
|
||||
await callback.apply(null, Array.from(arguments));
|
||||
});
|
||||
} else {
|
||||
console.error(`${type} is already a registered template!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Courtesy of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
/* Intrinsic Templates */
|
||||
|
||||
// Basic - Simple search and replace
|
||||
template.type.register('Basic', (element, map) => {
|
||||
let html = element.innerHTML;
|
||||
function applyObject(object, path) {
|
||||
for (const key in object) {
|
||||
const regexKey = escapeRegExp(path + key);
|
||||
html = html.replace(new RegExp(`(?:(?<!\\\\)\\\${${regexKey}})`, 'gm'), object[key]);
|
||||
if (typeof object[key] === 'object') {
|
||||
applyObject(object[key], path + key + '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
applyObject(map, '');
|
||||
html = html.replace('\\&', '&');
|
||||
element.outerHTML = html.trim();
|
||||
});
|
||||
|
||||
// Extern - Retrieve template from webserver
|
||||
template.type.register('Extern', (element, name) => {
|
||||
return fetch(`templates/${name}.html.template`, {
|
||||
method: 'GET',
|
||||
mode: 'no-cors',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
}).then(response => response.text().then((data) => {
|
||||
element.outerHTML = data;
|
||||
})).catch(error => {
|
||||
console.error(`failed to retrieve template '${name}': `, error);
|
||||
});
|
||||
});
|
||||
|
||||
// List - Iterate over list and emit copy of child for each iteration
|
||||
template.type.register('List<?>', async (element, subtype, arrayMap) => {
|
||||
let cumulative = '';
|
||||
const temp = document.createElement('template');
|
||||
for (const obj of arrayMap) {
|
||||
temp.innerHTML = `<template></template>`;
|
||||
const child = temp.content.children[0];
|
||||
child.innerHTML = element.innerHTML;
|
||||
const callback = template.type._entryMap.get(subtype.type);
|
||||
if (callback === undefined) {
|
||||
cumulative = '';
|
||||
console.error(`'${subtype.type}' is not a registered template type`);
|
||||
} else {
|
||||
await callback.apply(null, [ child, obj ]);
|
||||
}
|
||||
cumulative = cumulative + temp.innerHTML.trim();
|
||||
}
|
||||
element.outerHTML = cumulative;
|
||||
});
|
||||
|
||||
template._entryMap = new Map();
|
||||
template.apply = function(pattern, promise) {
|
||||
if (typeof pattern !== 'string') {
|
||||
console.error('pattern must be a string, received: ', pattern);
|
||||
} else {
|
||||
return new template.apply.applicators(pattern);
|
||||
}
|
||||
};
|
||||
template.apply.applicators = class {
|
||||
constructor(pattern) {
|
||||
this._pattern = pattern;
|
||||
}
|
||||
|
||||
_apply(asyncArgs) {
|
||||
template._entryMap.set(RegExp('^' + this._pattern + '$'), asyncArgs);
|
||||
}
|
||||
|
||||
values(...args) {
|
||||
this._apply(async () => Array.from(args));
|
||||
}
|
||||
|
||||
promise(promise) {
|
||||
let args = null;
|
||||
promise = promise.then(data => args = [ data ]);
|
||||
this._apply(async () => args || promise);
|
||||
}
|
||||
|
||||
fetch(dataProcessor, url, options) {
|
||||
if (typeof dataProcessor === 'string') {
|
||||
const path = dataProcessor;
|
||||
dataProcessor = data => {
|
||||
for (const id of path.split(/\./)) {
|
||||
data = data[id];
|
||||
if (data === undefined) {
|
||||
throw `invalid path '${path}'`;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
};
|
||||
this.promise(
|
||||
fetch(url, options || { method: 'GET', mode: 'cors' })
|
||||
.then(response => response.json())
|
||||
.then(data => dataProcessor(data))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
(() => {
|
||||
templateTypeEntryMap = new Map();
|
||||
template.register = (type, callback) => {
|
||||
if (typeof type !== 'string') {
|
||||
console.error(`'type' must be a string, recieved: `, type);
|
||||
} else {
|
||||
const TYPE_REGEX = /^(\w+)\s*(<\s*\?(?:\s*,\s*\?)*\s*>)?\s*$/;
|
||||
const result = type.match(TYPE_REGEX);
|
||||
if (result === null) {
|
||||
console.error(`'${type}' is not a valid type id`);
|
||||
} else {
|
||||
if (result[2] === undefined) {
|
||||
result[2] = 0;
|
||||
} else {
|
||||
result[2] = result[2].split(/\s*,\s*/).length;
|
||||
}
|
||||
const completeType = result[1] + ':' + result[2];
|
||||
if (templateTypeEntryMap.get(completeType) === undefined) {
|
||||
templateTypeEntryMap.set(completeType, async function() {
|
||||
await callback.apply(null, Array.from(arguments));
|
||||
});
|
||||
} else {
|
||||
console.error(`${type} is already a registered template!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Courtesy of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
/* Intrinsic Templates */
|
||||
|
||||
// Basic - Simple search and replace
|
||||
template.register('Basic', (element, map) => {
|
||||
let html = element.innerHTML;
|
||||
function applyObject(object, path) {
|
||||
for (const key in object) {
|
||||
const regexKey = escapeRegExp(path + key);
|
||||
html = html.replace(new RegExp(`(?:(?<!\\\\)\\\${${regexKey}})`, 'gm'), object[key]);
|
||||
if (typeof object[key] === 'object') {
|
||||
applyObject(object[key], path + key + '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
applyObject(map, '');
|
||||
html = html.replace('\\&', '&');
|
||||
element.outerHTML = html.trim();
|
||||
});
|
||||
|
||||
// Extern - Retrieve template from webserver
|
||||
template.register('Extern', (element, name) => {
|
||||
return fetch(`templates/${name}.html.template`, {
|
||||
method: 'GET',
|
||||
mode: 'no-cors',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
}).then(response => response.text().then((data) => {
|
||||
element.outerHTML = data;
|
||||
})).catch(error => {
|
||||
console.error(`failed to retrieve template '${name}': `, error);
|
||||
});
|
||||
});
|
||||
|
||||
// List - Iterate over list and emit copy of child for each iteration
|
||||
template.register('List<?>', async (element, subtype, arrayMap) => {
|
||||
let cumulative = '';
|
||||
const temp = document.createElement('template');
|
||||
for (const obj of arrayMap) {
|
||||
temp.innerHTML = `<template></template>`;
|
||||
const child = temp.content.children[0];
|
||||
child.innerHTML = element.innerHTML;
|
||||
const callback = templateTypeEntryMap.get(subtype.type);
|
||||
if (callback === undefined) {
|
||||
cumulative = '';
|
||||
console.error(`'${subtype.type}' is not a registered template`);
|
||||
} else {
|
||||
await callback.apply(null, [ child, obj ]);
|
||||
}
|
||||
cumulative = cumulative + temp.innerHTML.trim();
|
||||
}
|
||||
element.outerHTML = cumulative;
|
||||
});
|
||||
|
||||
templateEntryMap = new Map();
|
||||
template.apply = function(pattern, promise) {
|
||||
if (typeof pattern !== 'string') {
|
||||
console.error('pattern must be a string, received: ', pattern);
|
||||
} else {
|
||||
return new template.apply.applicators(pattern);
|
||||
}
|
||||
};
|
||||
template.apply.applicators = class {
|
||||
constructor(pattern) {
|
||||
this._pattern = pattern;
|
||||
}
|
||||
|
||||
_apply(asyncArgs) {
|
||||
templateEntryMap.set(RegExp('^' + this._pattern + '$'), asyncArgs);
|
||||
}
|
||||
|
||||
values(...args) {
|
||||
this._apply(async () => Array.from(args));
|
||||
}
|
||||
|
||||
promise(promise) {
|
||||
let args = null;
|
||||
promise = promise.then(data => args = [ data ]);
|
||||
this._apply(async () => args || promise);
|
||||
}
|
||||
|
||||
fetch(dataProcessor, url, options) {
|
||||
if (typeof dataProcessor === 'string') {
|
||||
const path = dataProcessor;
|
||||
dataProcessor = data => {
|
||||
for (const id of path.split(/\./)) {
|
||||
data = data[id];
|
||||
if (data === undefined) {
|
||||
throw `invalid path '${path}'`;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
};
|
||||
this.promise(
|
||||
fetch(url, options || { method: 'GET', mode: 'cors' })
|
||||
.then(response => response.json())
|
||||
.then(data => dataProcessor(data))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const parseType = (type) => {
|
||||
let result = type.match(/^\s*(\w+)\s*(?:<(.*)>)?\s*$/);
|
||||
let id = result[1];
|
||||
|
@ -162,12 +161,12 @@ template.apply.applicators = class {
|
|||
let promises = [];
|
||||
let parents = new Set();
|
||||
for (const child of children) {
|
||||
for (const [pattern, argsCallback] of template._entryMap) {
|
||||
for (const [pattern, argsCallback] of templateEntryMap) {
|
||||
await argsCallback().then(args => {
|
||||
if (pattern.test(child.id)) {
|
||||
const callback = template.type._entryMap.get(child.typeCapture.type);
|
||||
const callback = templateTypeEntryMap.get(child.typeCapture.type);
|
||||
if (typeof callback !== 'function') {
|
||||
console.error(`'${child.typeCapture.type}' is not a registered template type`);
|
||||
console.error(`'${child.typeCapture.type}' is not a registered template`);
|
||||
} else {
|
||||
let params = Array.from(args)
|
||||
for (const subtype of child.typeCapture.params) {
|
||||
|
|
378
frontend/webpage/styles/common.css
Normal file
378
frontend/webpage/styles/common.css
Normal file
|
@ -0,0 +1,378 @@
|
|||
html, body {
|
||||
background-color: var(--background-dark);
|
||||
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
z-index: 1;
|
||||
|
||||
position: fixed;
|
||||
|
||||
background-color: var(--accent-value2);
|
||||
color: var(--foreground);
|
||||
|
||||
width: 100%;
|
||||
min-height: 76px;
|
||||
height: 5%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.navbar-section {
|
||||
width: max-content;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
box-sizing: border-box;
|
||||
padding: 0px 20px;
|
||||
|
||||
width: max-content;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
user-select: none;
|
||||
|
||||
transition-property: background-color;
|
||||
transition-duration: 0.15s;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navbar-item:hover {
|
||||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
.ap-button {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 12px 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
background-color: var(--accent-value2);
|
||||
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
cursor: default;
|
||||
|
||||
transition-property: background-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.ap-button:hover {
|
||||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
.ap-button:active {
|
||||
background-color: var(--accent-value1);
|
||||
}
|
||||
|
||||
#content-body {
|
||||
position: relative;
|
||||
|
||||
top: max(76px, 5%);
|
||||
|
||||
width: 100%;
|
||||
height: calc(100% - max(76px, 5%));
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page {
|
||||
z-index: 0;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 0px 64px;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
min-height: 100%;
|
||||
|
||||
background-color: var(--background);
|
||||
box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5);
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 64px 0px;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.page-header-text,
|
||||
.page-subheader-text {
|
||||
width: max-content;
|
||||
|
||||
margin: 0;
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.page-header-text.link,
|
||||
.page-subheader-text.link {
|
||||
transition-property: color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.page-header-text.link:hover,
|
||||
.page-subheader-text.link:hover {
|
||||
color: var(--accent-value4);
|
||||
}
|
||||
|
||||
.page-header-text {
|
||||
font-size: 64px;
|
||||
}
|
||||
|
||||
.page-subheader-text {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.page-header-separator,
|
||||
.page-subheader-separator {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
|
||||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
.list-page-search {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 0px 64px 32px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-page-search > label,
|
||||
.list-page-search > input {
|
||||
box-sizing: border-box;
|
||||
padding: 12px 20px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.list-page-search > label {
|
||||
background-color: var(--accent-value2);
|
||||
|
||||
border-radius: 8px 0px 0px 8px;
|
||||
}
|
||||
|
||||
.list-page-search > label:hover {
|
||||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
.list-page-search > label:active {
|
||||
background-color: var(--accent-value1);
|
||||
|
||||
transition-property: background-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-search > input {
|
||||
background-color: var(--distinction);
|
||||
|
||||
border: 0;
|
||||
border-radius: 0px 8px 8px 0px;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
outline: none;
|
||||
|
||||
transition-property: background-color, color;
|
||||
transition-duration: 0.075s;
|
||||
}
|
||||
|
||||
.list-page-search > input:focus {
|
||||
background-color: var(--foreground);
|
||||
|
||||
color: var(--background);
|
||||
}
|
||||
|
||||
.list-page-partitions {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 32px 64px;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.list-page-filter-partition {
|
||||
width: 20%;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.list-page-filter {
|
||||
margin-top: 16px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-page-filter-checkbox {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
background-color: var(--background);
|
||||
|
||||
border: 3px solid var(--distinction);
|
||||
border-radius: 8px;
|
||||
|
||||
transition-property: background-color, border-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-filter:hover > .list-page-filter-checkbox {
|
||||
background-color: var(--background);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter.selected > .list-page-filter-checkbox {
|
||||
background-color: var(--selected-accent1);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter.selected:hover > .list-page-filter-checkbox {
|
||||
background-color: var(--selected-accent0);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter-name {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.list-page-list-partition {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding-left: 64px;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.list-page-list {
|
||||
border-radius: 8px;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-page-header {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
background-color: var(--accent-value2);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: var(--foreground);
|
||||
font-size: 24px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-page-entry {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
background-color: var(--distinction);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: var(--foreground);
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.list-page-entry-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.list-page-entry-text {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
width: 0;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
border-top: 1px solid var(--background);
|
||||
}
|
||||
|
||||
.list-page-header > .list-page-entry-text {
|
||||
border: 0;
|
||||
}
|
|
@ -1,25 +1,3 @@
|
|||
:root {
|
||||
--background-dark: #111115;
|
||||
--background: #22222A;
|
||||
--foreground: #EEEEEE;
|
||||
--distinction: #44444F;
|
||||
|
||||
--accent-value0: #500000;
|
||||
--accent-value1: #800000;
|
||||
--accent-value2: #A00000;
|
||||
--accent-value3: #D02020;
|
||||
--accent-value4: #FA7575;
|
||||
|
||||
--selected-accent0: #0066CC;
|
||||
--selected-accent1: #3388FF;
|
||||
|
||||
--navbar-background: var(--accent-value2);
|
||||
--navbar-hover-background: var(--accent-value3);
|
||||
--navbar-foreground: #EEEEEE;
|
||||
|
||||
--header-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
html, body {
|
||||
background-color: var(--background-dark);
|
||||
|
||||
|
@ -32,394 +10,27 @@ html, body {
|
|||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
z-index: 1;
|
||||
|
||||
position: fixed;
|
||||
|
||||
background-color: var(--navbar-background);
|
||||
color: var(--navbar-foreground);
|
||||
|
||||
width: 100%;
|
||||
min-height: 76px;
|
||||
height: 5%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.navbar-section {
|
||||
width: max-content;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
box-sizing: border-box;
|
||||
padding: 0px 20px;
|
||||
|
||||
width: max-content;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
user-select: none;
|
||||
|
||||
transition-property: background-color;
|
||||
transition-duration: 0.15s;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navbar-item:hover {
|
||||
background-color: var(--navbar-hover-background);
|
||||
}
|
||||
|
||||
#content-body {
|
||||
position: relative;
|
||||
|
||||
top: max(76px, 5%);
|
||||
|
||||
width: 100%;
|
||||
height: calc(100% - max(76px, 5%));
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page {
|
||||
z-index: 0;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 0px 64px;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background-color: var(--background);
|
||||
box-shadow: 0px 0px 5px 10px rgba(0, 0, 0, 0.5);
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 64px 0px;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
.page-header-text {
|
||||
width: max-content;
|
||||
|
||||
margin: 0;
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
color: var(--header-color);
|
||||
|
||||
font-size: 64px;
|
||||
}
|
||||
|
||||
.page-header-separator {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
|
||||
background-color: var(--foreground);
|
||||
}
|
||||
|
||||
.page-subheader-text {
|
||||
width: max-content;
|
||||
|
||||
margin: 0;
|
||||
margin-bottom: 0.25em;
|
||||
|
||||
color: var(--header-color);
|
||||
|
||||
font-size: 48px;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.page-subheader-separator {
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
|
||||
background-color: var(--foreground);
|
||||
|
||||
transition-property: color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-search {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 32px 64px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-page-search > label {
|
||||
box-sizing: border-box;
|
||||
padding: 16px 24px;
|
||||
|
||||
background-color: var(--accent-value2);
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 32px;
|
||||
|
||||
border-radius: 8px 0px 0px 8px;
|
||||
|
||||
transition-property: background-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-search > label:hover {
|
||||
background-color: var(--accent-value3);
|
||||
}
|
||||
|
||||
.list-page-search > label:active {
|
||||
background-color: var(--accent-value1);
|
||||
}
|
||||
|
||||
.list-page-search > input {
|
||||
box-sizing: border-box;
|
||||
padding: 16px 24px;
|
||||
|
||||
background-color: var(--distinction);
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 32px;
|
||||
|
||||
border: 0;
|
||||
border-radius: 0px 8px 8px 0px;
|
||||
|
||||
flex-grow: 1;
|
||||
|
||||
outline: none;
|
||||
|
||||
transition-property: background-color, color;
|
||||
transition-duration: 0.075s;
|
||||
}
|
||||
|
||||
.list-page-search > input:focus {
|
||||
background-color: var(--foreground);
|
||||
color: var(--background);
|
||||
}
|
||||
|
||||
.list-page-partitions {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 32px 64px;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.list-page-filter-partition {
|
||||
width: 20%;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.list-page-filter {
|
||||
margin-top: 16px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list-page-filter-checkbox {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
background-color: var(--background);
|
||||
|
||||
border: 3px solid var(--distinction);
|
||||
border-radius: 8px;
|
||||
|
||||
transition-property: background-color, border-color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.list-page-filter:hover > .list-page-filter-checkbox {
|
||||
background-color: var(--background);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter.selected > .list-page-filter-checkbox {
|
||||
background-color: var(--selected-accent1);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter.selected:hover > .list-page-filter-checkbox {
|
||||
background-color: var(--selected-accent0);
|
||||
border-color: var(--selected-accent1);
|
||||
}
|
||||
|
||||
.list-page-filter-name {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.list-page-list-partition {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding-left: 64px;
|
||||
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.list-page-list {
|
||||
border-radius: 8px;
|
||||
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.list-page-list-header {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
background-color: var(--accent-value2);
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: var(--foreground);
|
||||
font-size: 24px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-page-list-entry {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
color: var(--foreground);
|
||||
font-size: 24px;
|
||||
|
||||
border-bottom: 1px solid var(--distinction);
|
||||
}
|
||||
|
||||
#games-page {
|
||||
max-width: 1920px;
|
||||
}
|
||||
|
||||
.game-list-page-entry-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.game-list-page-entry-name {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
width: 0;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
.list-page-entry-text.game-name {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0px;
|
||||
}
|
||||
|
||||
.game-list-page-entry-description {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
width: 0;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
.list-page-entry-text.game-description {
|
||||
flex-grow: 2;
|
||||
flex-basis: 0px;
|
||||
}
|
||||
|
||||
#achievements-page {
|
||||
max-width: 1920px;
|
||||
}
|
||||
|
||||
.achievement-list-page-entry-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.achievement-list-page-entry-name {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
width: 0;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
.list-page-entry-text.achievement-name {
|
||||
flex-grow: 4;
|
||||
flex-basis: 0px;
|
||||
}
|
||||
|
||||
.achievement-list-page-entry-description {
|
||||
.list-page-entry-text.achievement-description {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0;
|
||||
|
@ -431,22 +42,10 @@ html, body {
|
|||
white-space: nowrap;
|
||||
|
||||
flex-grow: 8;
|
||||
flex-basis: 0px;
|
||||
}
|
||||
|
||||
.achievement-list-page-entry-stages {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
width: 0;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
.list-page-entry-text.achievement-stages {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0px;
|
||||
}
|
||||
|
||||
#profile-page {
|
||||
|
@ -482,6 +81,8 @@ html, body {
|
|||
#profile-info-pfp {
|
||||
width: 100%;
|
||||
max-width: 640px;
|
||||
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
#profile-info-name {
|
||||
|
@ -491,7 +92,78 @@ html, body {
|
|||
|
||||
font-size: 42px;
|
||||
|
||||
color: var(--header-color);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.profile-list {
|
||||
margin-top: 1.25em;
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.profile-entry {
|
||||
overflow: hidden;
|
||||
|
||||
height: 64px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
background-color: var(--distinction);
|
||||
|
||||
border-bottom: 1px solid var(--background);
|
||||
}
|
||||
|
||||
.profile-entry.accented {
|
||||
border-bottom: 1px solid var(--accent-value0);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.profile-entry-text {
|
||||
margin: 0;
|
||||
padding: 0px 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.profile-entry-text.accented {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.profile-entry.accented .profile-entry-text.accented {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#profile-platforms {
|
||||
|
@ -504,74 +176,6 @@ html, body {
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.profile-platform-entry {
|
||||
overflow: hidden;
|
||||
|
||||
margin-top: 16px;
|
||||
|
||||
height: 64px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
border: 3px solid var(--distinction);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.profile-platform-entry.connected {
|
||||
border: 3px solid var(--accent-value3);
|
||||
|
||||
background-color: var(--accent-value2);
|
||||
}
|
||||
|
||||
.profile-platform-entry-left {
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile-platform-entry-right {
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile-platform-icon {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.profile-platform-name {
|
||||
margin: 0;
|
||||
padding: 0px 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.profile-platform-connected {
|
||||
display: none;
|
||||
|
||||
margin: 0;
|
||||
padding: 0px 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.profile-platform-entry.connected .profile-platform-connected {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#profile-section-2 {
|
||||
box-sizing: border-box;
|
||||
|
||||
|
@ -586,122 +190,16 @@ html, body {
|
|||
|
||||
#profile-games {
|
||||
box-sizing: border-box;
|
||||
padding: 0px 64px;
|
||||
padding: 0px 64px 64px;
|
||||
|
||||
width: 50%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
#profile-games > .page-subheader-text:hover {
|
||||
color: var(--accent-value4);
|
||||
}
|
||||
|
||||
.profile-game-entry {
|
||||
overflow: hidden;
|
||||
|
||||
margin-top: 16px;
|
||||
|
||||
height: 64px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
border: 3px solid var(--distinction);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.profile-game-entry-icon {
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.profile-game-entry-name {
|
||||
margin: 0;
|
||||
padding: 0px 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#profile-achievements {
|
||||
box-sizing: border-box;
|
||||
padding: 0px 64px;
|
||||
padding: 0px 64px 64px;
|
||||
|
||||
width: 50%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
#profile-achievements > .page-subheader-text:hover {
|
||||
color: var(--accent-value4);
|
||||
}
|
||||
|
||||
.profile-achievement-entry {
|
||||
overflow: hidden;
|
||||
|
||||
margin-top: 16px;
|
||||
|
||||
height: 64px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
border: 3px solid var(--distinction);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.profile-achievement-entry.completed {
|
||||
border: 3px solid var(--accent-value3);
|
||||
|
||||
background-color: var(--accent-value2);
|
||||
}
|
||||
|
||||
.profile-achievement-entry-left {
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile-achievement-entry-right {
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile-achievement-entry-icon {
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.profile-achievement-entry-name {
|
||||
margin: 0;
|
||||
padding: 0px 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.profile-achievement-completed {
|
||||
display: none;
|
||||
|
||||
margin: 0;
|
||||
padding: 0px 16px;
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.profile-achievement-entry.completed .profile-achievement-completed {
|
||||
display: block;
|
||||
}
|
||||
|
||||
}
|
101
frontend/webpage/styles/login.css
Normal file
101
frontend/webpage/styles/login.css
Normal file
|
@ -0,0 +1,101 @@
|
|||
:root {
|
||||
--form-spacing: 48px;
|
||||
|
||||
--element-spacing: 12px;
|
||||
|
||||
--error: #FA7575;
|
||||
}
|
||||
|
||||
#login-page {
|
||||
display: block;
|
||||
|
||||
max-width: 1280px;
|
||||
}
|
||||
|
||||
#login-header {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 0 calc(25% - 64px);
|
||||
|
||||
width: 100%;
|
||||
height: max-content;
|
||||
}
|
||||
|
||||
#login-form {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 24px calc(25% - 64px) 0;
|
||||
padding: 24px 0;
|
||||
|
||||
height: max-content;
|
||||
|
||||
background-color: var(--distinction);
|
||||
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#error-message {
|
||||
display: none;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0 var(--form-spacing) var(--element-spacing);
|
||||
width: calc(100% - (var(--form-spacing) * 2));
|
||||
|
||||
color: var(--error);
|
||||
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.login-field {
|
||||
box-sizing: border-box;
|
||||
|
||||
margin: 0 var(--form-spacing) var(--element-spacing);
|
||||
border: 0;
|
||||
padding: 8px;
|
||||
|
||||
width: calc(100% - (var(--form-spacing) * 2));
|
||||
height: max-content;
|
||||
|
||||
font-size: 20px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.login-field.error {
|
||||
background-color: var(--error);
|
||||
}
|
||||
|
||||
#username,
|
||||
#confirm {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#login-buttons {
|
||||
margin: 0 calc(var(--form-spacing) - (var(--element-spacing) / 2));
|
||||
width: calc(100% - (var(--form-spacing) * 2) + var(--element-spacing));
|
||||
height: max-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ap-button.login {
|
||||
margin: 0 calc(var(--element-spacing) / 2);
|
||||
flex-grow: 1;
|
||||
flex-basis: 0px;
|
||||
}
|
||||
|
||||
#warning {
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 0 var(--form-spacing);
|
||||
|
||||
color: var(--foreground);
|
||||
|
||||
font-size: 24px;
|
||||
}
|
16
frontend/webpage/styles/theme.css
Normal file
16
frontend/webpage/styles/theme.css
Normal file
|
@ -0,0 +1,16 @@
|
|||
:root {
|
||||
--background-dark: #111115;
|
||||
--background: #22222A;
|
||||
--foreground-dark: #AAAAAA;
|
||||
--foreground: #EEEEEE;
|
||||
--distinction: #44444F;
|
||||
|
||||
--accent-value0: #500000;
|
||||
--accent-value1: #800000;
|
||||
--accent-value2: #A00000;
|
||||
--accent-value3: #D02020;
|
||||
--accent-value4: #FA7575;
|
||||
|
||||
--selected-accent0: #0066CC;
|
||||
--selected-accent1: #3388FF;
|
||||
}
|
|
@ -1,38 +1,38 @@
|
|||
<div class="list-page-search">
|
||||
<label for="achievement-search">Search</label>
|
||||
<input id="achievement-search" type="text" placeholder="Name, Keyword, etc..." name="achievement-search" />
|
||||
<input id="achievement-search" type="text" placeholder="Name, Keyword, etc..." achievement-name="achievement-search" />
|
||||
</div>
|
||||
<div class="list-page-partitions">
|
||||
<div class="list-page-filter-partition">
|
||||
<p class="page-subheader-text">Filters</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
<div id="games-owned-filter" class="list-page-filter">
|
||||
<div id="from-games-owned-filter" class="list-page-filter">
|
||||
<div class="list-page-filter-checkbox"></div>
|
||||
<p class="list-page-filter-name">From Games Owned</p>
|
||||
</div>
|
||||
<div id="games-owned-filter" class="list-page-filter">
|
||||
<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="games-owned-filter" class="list-page-filter">
|
||||
<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 class="list-page-list-partition">
|
||||
<div class="list-page-list">
|
||||
<div class="list-page-list-header">
|
||||
<p class="achievement-list-page-entry-icon"></p>
|
||||
<p class="achievement-list-page-entry-name">Name</p>
|
||||
<p class="achievement-list-page-entry-description">Description</p>
|
||||
<p class="achievement-list-page-entry-stages">Stages</p>
|
||||
<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-list-entry">
|
||||
<img class="achievement-list-page-entry-icon" src="res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
|
||||
<p class="achievement-list-page-entry-name">${name}</p>
|
||||
<p class="achievement-list-page-entry-description">${description}</p>
|
||||
<p class="achievement-list-page-entry-stages">${stages}</p>
|
||||
<div class="list-page-entry">
|
||||
<img class="list-page-entry-icon" src="res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
|
||||
<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>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="list-page-search">
|
||||
<label for="game-search">Search</label>
|
||||
<input id="game-search" type="text" placeholder="Name, Keyword, etc..." name="game-search" />
|
||||
<input id="game-search" type="text" placeholder="Name, Keyword, etc..." game-name="game-search" />
|
||||
</div>
|
||||
<div class="list-page-partitions">
|
||||
<div class="list-page-filter-partition">
|
||||
|
@ -13,40 +13,40 @@
|
|||
</div>
|
||||
<div class="list-page-list-partition">
|
||||
<div class="list-page-list">
|
||||
<div class="list-page-list-header">
|
||||
<p class="game-list-page-entry-icon"></p>
|
||||
<p class="game-list-page-entry-name">Name</p>
|
||||
<p class="game-list-page-entry-description">Description</p>
|
||||
<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-list-entry">
|
||||
<img class="game-list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
|
||||
<p class="game-list-page-entry-name">Latin</p>
|
||||
<p class="game-list-page-entry-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<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-list-entry">
|
||||
<img class="game-list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
|
||||
<p class="game-list-page-entry-name">Latin</p>
|
||||
<p class="game-list-page-entry-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<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-list-entry">
|
||||
<img class="game-list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
|
||||
<p class="game-list-page-entry-name">Latin</p>
|
||||
<p class="game-list-page-entry-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<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-list-entry">
|
||||
<img class="game-list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
|
||||
<p class="game-list-page-entry-name">Latin</p>
|
||||
<p class="game-list-page-entry-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<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-list-entry">
|
||||
<img class="game-list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
|
||||
<p class="game-list-page-entry-name">Latin</p>
|
||||
<p class="game-list-page-entry-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<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-list-entry">
|
||||
<img class="game-list-page-entry-icon" src="res/dummy_game.png" alt="Achievement Icon.png" />
|
||||
<p class="game-list-page-entry-name">Latin</p>
|
||||
<p class="game-list-page-entry-description">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
<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>
|
||||
|
|
|
@ -1,134 +1,149 @@
|
|||
<div id="profile-section-1">
|
||||
<div id="profile-info">
|
||||
<img id="profile-info-pfp" src="res/temp_pfp.png" alt="User's Profile Pictuer" />
|
||||
<div class="page-subheader-separator"></div>
|
||||
<p id="profile-info-name">Jane Doe</p>
|
||||
</div>
|
||||
<div id="profile-platforms">
|
||||
<p class="page-subheader-text">Platforms</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
<div class="profile-platform-entry connected">
|
||||
<div class="profile-platform-entry-left">
|
||||
<img class="profile-platform-icon" src="res/steam.png" alt="Steam Logo" />
|
||||
<p class="profile-platform-name">Steam</p>
|
||||
<div class="profile-list">
|
||||
<div class="profile-entry accented">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/steam.png" alt="Steam Logo" />
|
||||
<p class="profile-entry-text">Steam</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Connected</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-platform-entry-right">
|
||||
<p class="profile-platform-connected">Connected</p>
|
||||
<div class="profile-entry">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/xbox.png" alt="Xbox Logo" />
|
||||
<p class="profile-entry-text">Xbox Live</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Connected</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-platform-entry">
|
||||
<div class="profile-platform-entry-left">
|
||||
<img class="profile-platform-icon" src="res/xbox.png" alt="Xbox Logo" />
|
||||
<p class="profile-platform-name">Xbox Live</p>
|
||||
</div>
|
||||
<div class="profile-platform-entry-right">
|
||||
<p class="profile-platform-connected">Connected</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-platform-entry">
|
||||
<div class="profile-platform-entry-left">
|
||||
<img class="profile-platform-icon" src="res/psn.png" alt="PSN Logo" />
|
||||
<p class="profile-platform-name">PSN</p>
|
||||
</div>
|
||||
<div class="profile-platform-entry-right">
|
||||
<p class="profile-platform-connected">Connected</p>
|
||||
<div class="profile-entry">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/psn.png" alt="PSN Logo" />
|
||||
<p class="profile-entry-text">PSN</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Connected</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-section-2">
|
||||
<div id="profile-games">
|
||||
<p class="page-subheader-text">Games</p>
|
||||
<p class="page-subheader-text link">Games</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
<div class="profile-game-entry">
|
||||
<img class="profile-game-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
|
||||
<p class="profile-game-entry-name">Latin</p>
|
||||
</div>
|
||||
<div class="profile-game-entry">
|
||||
<img class="profile-game-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
|
||||
<p class="profile-game-entry-name">Latin</p>
|
||||
</div>
|
||||
<div class="profile-game-entry">
|
||||
<img class="profile-game-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
|
||||
<p class="profile-game-entry-name">Latin</p>
|
||||
</div>
|
||||
<div class="profile-game-entry">
|
||||
<img class="profile-game-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
|
||||
<p class="profile-game-entry-name">Latin</p>
|
||||
<div class="profile-list">
|
||||
<div class="profile-entry game">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
|
||||
<p class="profile-entry-text">Latin</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-entry game">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
|
||||
<p class="profile-entry-text">Latin</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-entry game">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
|
||||
<p class="profile-entry-text">Latin</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-entry game">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_game.png" alt="Some Game Icon" />
|
||||
<p class="profile-entry-text">Latin</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="profile-achievements">
|
||||
<p class="page-subheader-text">Achievements</p>
|
||||
<p class="page-subheader-text link">Achievements</p>
|
||||
<div class="page-subheader-separator"></div>
|
||||
<div class="profile-achievement-entry completed">
|
||||
<div class="profile-achievement-entry-left">
|
||||
<img class="profile-achievement-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-achievement-entry-name">Lorem Ipsum</p>
|
||||
<div class="profile-list">
|
||||
<div class="profile-entry accented">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-entry-text">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry-right">
|
||||
<p class="profile-achievement-completed">Completed</p>
|
||||
<div class="profile-entry accented">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-entry-text">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry completed">
|
||||
<div class="profile-achievement-entry-left">
|
||||
<img class="profile-achievement-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-achievement-entry-name">Lorem Ipsum</p>
|
||||
<div class="profile-entry accented">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-entry-text">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry-right">
|
||||
<p class="profile-achievement-completed">Completed</p>
|
||||
<div class="profile-entry">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-entry-text">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry completed">
|
||||
<div class="profile-achievement-entry-left">
|
||||
<img class="profile-achievement-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-achievement-entry-name">Lorem Ipsum</p>
|
||||
<div class="profile-entry">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-entry-text">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry-right">
|
||||
<p class="profile-achievement-completed">Completed</p>
|
||||
<div class="profile-entry">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-entry-text">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry">
|
||||
<div class="profile-achievement-entry-left">
|
||||
<img class="profile-achievement-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-achievement-entry-name">Lorem Ipsum</p>
|
||||
<div class="profile-entry">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-entry-text">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry-right">
|
||||
<p class="profile-achievement-completed">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry">
|
||||
<div class="profile-achievement-entry-left">
|
||||
<img class="profile-achievement-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-achievement-entry-name">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-achievement-entry-right">
|
||||
<p class="profile-achievement-completed">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry">
|
||||
<div class="profile-achievement-entry-left">
|
||||
<img class="profile-achievement-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-achievement-entry-name">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-achievement-entry-right">
|
||||
<p class="profile-achievement-completed">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry">
|
||||
<div class="profile-achievement-entry-left">
|
||||
<img class="profile-achievement-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-achievement-entry-name">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-achievement-entry-right">
|
||||
<p class="profile-achievement-completed">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-achievement-entry">
|
||||
<div class="profile-achievement-entry-left">
|
||||
<img class="profile-achievement-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-achievement-entry-name">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-achievement-entry-right">
|
||||
<p class="profile-achievement-completed">Completed</p>
|
||||
<div class="profile-entry">
|
||||
<div class="profile-entry-left">
|
||||
<img class="profile-entry-icon" src="res/dummy_achievement.png" alt="Some Achievement Icon" />
|
||||
<p class="profile-entry-text">Lorem Ipsum</p>
|
||||
</div>
|
||||
<div class="profile-entry-right">
|
||||
<p class="profile-entry-text accented">Completed</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue