mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-03-21 17:24:28 -05:00
chore: remove old code, set up Next.js
This commit is contained in:
parent
16bb2e1065
commit
275a1cf324
|
|
@ -9,9 +9,9 @@
|
|||
"document": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"extends": "next/core-web-vitals",
|
||||
"rules": {
|
||||
"no-case-declarations": "off",
|
||||
"no-empty": "off",
|
||||
|
|
|
|||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -67,3 +67,6 @@ static-text.json
|
|||
|
||||
# keep browserified files out
|
||||
*.bundled.js
|
||||
|
||||
# Next.js server files
|
||||
.next
|
||||
|
|
|
|||
6
next.config.js
Normal file
6
next.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
12831
package-lock.json
generated
12831
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
60
package.json
60
package.json
|
|
@ -1,46 +1,18 @@
|
|||
{
|
||||
"name": "website",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/server.js",
|
||||
"scripts": {
|
||||
"start": "browserify ./public/assets/js/miieditor.js -o ./public/assets/js/miieditor.bundled.js && node src/server.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/PretendoNetwork/website.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/PretendoNetwork/website/issues"
|
||||
},
|
||||
"homepage": "https://github.com/PretendoNetwork/website#readme",
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^0.5.0",
|
||||
"adm-zip": "^0.5.9",
|
||||
"browserify": "^17.0.0",
|
||||
"colors": "^1.4.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"discord-api-types": "^0.36.1",
|
||||
"discord-oauth2": "github:ryanblenis/discord-oauth2",
|
||||
"express": "^4.17.1",
|
||||
"express-handlebars": "^5.3.1",
|
||||
"express-locale": "^2.0.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"got": "^11.8.5",
|
||||
"graphql-request": "^4.3.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"kaitai-struct": "^0.9.0",
|
||||
"marked": "^4.0.10",
|
||||
"mii-js": "github:PretendoNetwork/mii-js#v1.0.4",
|
||||
"mongoose": "^6.4.0",
|
||||
"morgan": "^1.10.0",
|
||||
"nodemailer": "^6.7.5",
|
||||
"stripe": "^9.9.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.32.0"
|
||||
}
|
||||
"name": "pretendo",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "8.26.0",
|
||||
"eslint-config-next": "13.0.1",
|
||||
"next": "13.0.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
pages/_app.js
Normal file
7
pages/_app.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import '../styles/globals.css';
|
||||
|
||||
function Pretendo({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
||||
export default Pretendo;
|
||||
3
pages/index.js
Normal file
3
pages/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export default function Home() {
|
||||
return <p>bandwidth my beloved</p>;
|
||||
}
|
||||
|
|
@ -1,346 +0,0 @@
|
|||
/* Removing until it's done */
|
||||
.sign-in-history a {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.account-wrapper {
|
||||
display: grid;
|
||||
column-gap: 48px;
|
||||
margin-top: 80px;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
/* Account settings sidebar */
|
||||
.account-sidebar .user {
|
||||
margin: 55px auto;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
}
|
||||
.account-sidebar .user .miiname {
|
||||
font-size: 1.2rem;
|
||||
color: var(--text-shade-3);
|
||||
margin: 8px 0 4px;
|
||||
}
|
||||
.account-sidebar .user .username {
|
||||
margin: 0;
|
||||
}
|
||||
.account-sidebar .user .tier-name {
|
||||
margin: 12px 0;
|
||||
line-height: 1.2em;
|
||||
border-radius: 1.2em;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.account-sidebar .user .tier-level-0,
|
||||
.account-sidebar .user .access-level-0 {
|
||||
background: #2a2f50;
|
||||
color: var(--text-shade-1);
|
||||
border-color: #383f6b;
|
||||
}
|
||||
.account-sidebar .user .tier-level-1 {
|
||||
background: rgba(255, 132, 132, 0.2);
|
||||
color: #FF8484;
|
||||
border-color: rgba(255, 132, 132, 0.8);
|
||||
}
|
||||
.account-sidebar .user .tier-level-2 {
|
||||
background: rgba(89, 201, 165, 0.3);
|
||||
color:#59c9a5;
|
||||
border-color: #59c9a5;
|
||||
}
|
||||
.account-sidebar .user .tier-level-3 {
|
||||
background: rgba(202, 177, 251, 0.3);
|
||||
color:var(--accent-shade-3);
|
||||
border-color: var(--accent-shade-3);
|
||||
}
|
||||
.account-sidebar .user .access-level-banned {
|
||||
background: rgba(255, 63, 0, 0.1);
|
||||
color:#FF3F00;
|
||||
border-color: rgba(255, 63, 0, 0.8);
|
||||
}
|
||||
.account-sidebar .user .access-level-1 {
|
||||
background: rgba(100, 247, 239, 0.3);
|
||||
color: #64F7EF;
|
||||
border-color: #64F7EF;
|
||||
}
|
||||
.account-sidebar .user .access-level-2 {
|
||||
background: rgba(255, 199, 89, 0.3);
|
||||
color: #FFC759;
|
||||
border-color: #FFC759;
|
||||
}
|
||||
.account-sidebar .user .access-level-3 {
|
||||
background: rgba(90, 255, 21, 0.3);
|
||||
color:#5AFF15;
|
||||
border-color: #5AFF15;
|
||||
}
|
||||
|
||||
.account-sidebar .user a.mii {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
overflow: hidden;
|
||||
border-radius: 100%;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
.account-sidebar .user a.mii::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: no-repeat center/40% url("/assets/images/edit.svg"), rgba(55, 60, 101, 0.7);
|
||||
opacity: 0;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
.account-sidebar .user a.mii:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.account-sidebar .user .mii {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.account-sidebar .buttons a {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
padding: 20px 24px;
|
||||
margin: 20px 0 0;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
}
|
||||
.account-sidebar .buttons a svg {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.account-sidebar .buttons a p.caption {
|
||||
margin: 0;
|
||||
}
|
||||
.account-sidebar .buttons p.cemu-warning {
|
||||
margin: 4px 0 0;
|
||||
font-size: 0.7rem;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
/* Settings */
|
||||
.settings-wrapper {
|
||||
display: grid;
|
||||
grid-column-start: 2;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 20px;
|
||||
}
|
||||
.settings-wrapper a {
|
||||
color: var(--accent-shade-1);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.settings-wrapper a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.settings-wrapper h2.section-header {
|
||||
margin-top: 40px;
|
||||
grid-column: 1 / 3;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.setting-card {
|
||||
display: grid;
|
||||
grid-template-rows: 35px repeat(2, auto);
|
||||
row-gap: 24px;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
background: var(--bg-shade-2);
|
||||
padding: 48px 60px;
|
||||
}
|
||||
.setting-card * {
|
||||
margin: 0;
|
||||
}
|
||||
.setting-card .edit {
|
||||
color: var(--text-shade-1);
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
right: 48px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 12px;
|
||||
}
|
||||
.setting-card .edit:hover {
|
||||
background: var(--bg-shade-3);
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
.setting-card .edit svg {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.setting-card .header {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.setting-card .setting-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, auto);
|
||||
gap: 24px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.setting-card .setting-list p.label {
|
||||
color: var(--text-shade-3);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
position: relative;
|
||||
height: min-content;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.setting-card .server-selection {
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
.setting-card .server-selection input {
|
||||
display: none;
|
||||
}
|
||||
.server-selection input + label {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
flex: 50%;
|
||||
color: var(--text-shade-1);
|
||||
padding: 40px;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
}
|
||||
.server-selection input + label h2 {
|
||||
margin-top: 12px;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
.server-selection input:checked + label,
|
||||
.server-selection input:checked + label h2 {
|
||||
background: var(--accent-shade-0);
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.setting-card #link-discord-account {
|
||||
width: 100%;
|
||||
padding: 12px 48px;
|
||||
cursor: pointer;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.setting-card button {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
padding: 12px 48px;
|
||||
align-self: flex-end;
|
||||
cursor: pointer;
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.setting-card.span-both-columns {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
@keyframes banner-notice {
|
||||
0% {
|
||||
top: -150px;
|
||||
}
|
||||
20% {
|
||||
top: 35px;
|
||||
}
|
||||
80% {
|
||||
top: 35px;
|
||||
}
|
||||
100% {
|
||||
top: -150px;
|
||||
}
|
||||
}
|
||||
.banner-notice {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: -150px;
|
||||
width: 100%;
|
||||
animation: banner-notice 5s;
|
||||
}
|
||||
.banner-notice div {
|
||||
padding: 4px 36px;
|
||||
border-radius: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
.banner-notice.success div {
|
||||
background: var(--green-shade-0);
|
||||
}
|
||||
.banner-notice.error div {
|
||||
background: var(--red-shade-1);
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 80px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1300px) {
|
||||
.account-wrapper {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.settings-wrapper {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
.account-sidebar {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.account-sidebar .user .mii {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.settings-wrapper {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.setting-card {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.setting-card {
|
||||
padding: 24px;
|
||||
width: calc(100vw - 48px);
|
||||
margin-left: -5vw;
|
||||
margin-right: -2.5vw;
|
||||
border-radius: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.setting-card .edit {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
transform: scale(0.85);
|
||||
}
|
||||
|
||||
.setting-card .server-selection {
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 350px) {
|
||||
.setting-card .setting-list {
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
.new-font {
|
||||
font-family: museo-sans, sans-serif;
|
||||
}
|
||||
|
||||
.pretendo {
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.announcement-hero {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
padding: 96px 0;
|
||||
margin: 36px 0 24px;
|
||||
}
|
||||
.announcement-hero p {
|
||||
font-size: 24px;
|
||||
margin: 0;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.announcement-hero h1 {
|
||||
font-size: 450%;
|
||||
margin: 0;
|
||||
}
|
||||
.announcement-hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 500vw;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: -50vw;
|
||||
background: var(--accent-shade-0);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.bro-what.subscribe {
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
}
|
||||
.bro-what.subscribe h1 {
|
||||
margin: 0;
|
||||
margin-right: 12px;
|
||||
width: fit-content;
|
||||
}
|
||||
.buy-now {
|
||||
margin-left:auto;
|
||||
}
|
||||
.buy-now button {
|
||||
cursor: pointer;
|
||||
width: max-content;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bro-what {
|
||||
padding: 48px;
|
||||
}
|
||||
.bro-what a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
|
||||
.dotted-bg {
|
||||
position: relative;
|
||||
}
|
||||
.dotted-bg::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 500vw;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: -50vw;
|
||||
background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='100%25' width='100%25'%3E%3Cdefs%3E%3Cpattern id='doodad' width='6' height='6' viewBox='0 0 40 40' patternUnits='userSpaceOnUse' patternTransform=''%3E%3Crect width='100%25' height='100%25' fill='rgba(27, 31, 59,1)'/%3E%3Ccircle cx='20' cy='20' r='11' fill='rgba(103, 61, 182,0.4)'/%3E%3Cpath d='M9 20aInfinityInfinity 0 0 0InfinityNaNaInfinityInfinity 0 0 0-InfinityNaN' fill='%23ecc94b'/%3E%3C/pattern%3E%3C/defs%3E%3Crect fill='url(%23doodad)' height='200%25' width='200%25'/%3E%3C/svg%3E ");
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.footnotes {
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 946px) {
|
||||
header nav a:not(.keep-on-mobile) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.announcement-hero h1 {
|
||||
font-size: 350%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 724px) {
|
||||
header .logo-link svg text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header .logo-link svg {
|
||||
width: 39.876px;
|
||||
}
|
||||
|
||||
header .logo-link {
|
||||
margin-right: 10px;
|
||||
}
|
||||
header nav a {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.announcement-hero h1 {
|
||||
font-size: 250%;
|
||||
}
|
||||
.announcement-hero p {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.bro-what.subscribe {
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
.bro-what a,
|
||||
.buy-now button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bro-what a {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.announcement-hero {
|
||||
padding: 72px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.bro-what {
|
||||
padding: 36px 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
.blog-card {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
max-width: 1000px;
|
||||
margin-bottom: 30px;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.blog-card .post-info {
|
||||
flex: 50%;
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
.blog-card .post-info .title {
|
||||
color: var(--text-shade-3);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.blog-card .post-info .caption {
|
||||
margin: 4px 0 32px 0;
|
||||
}
|
||||
|
||||
.blog-card .pub-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.blog-card .pub-info .date {
|
||||
font-weight: bold;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.blog-card .pub-info > * {
|
||||
margin-right: 0.5em;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
.blog-card .profile {
|
||||
display: inline-grid;
|
||||
grid-template-columns: 30px auto;
|
||||
grid-gap: 10px;
|
||||
font-weight: bold;
|
||||
color: var(--text-shade-3);
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
.blog-card .profile img {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.blog-card .cover {
|
||||
flex: 50%;
|
||||
border: 3px solid var(--bg-shade-0);
|
||||
border-radius: 0 10px 10px 0;
|
||||
}
|
||||
|
||||
.progress-hero a,
|
||||
.progress-hero a * {
|
||||
color: var(--accent-shade-1);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.progress-hero a:hover,
|
||||
.progress-hero a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin: 10vh auto;
|
||||
width: min-content;
|
||||
}
|
||||
.buttons button.secondary.icon-btn {
|
||||
cursor: pointer;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 160px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.blog-card {
|
||||
flex-flow: column;
|
||||
}
|
||||
.blog-card .post-info {
|
||||
padding: 30px;
|
||||
}
|
||||
.blog-card .cover {
|
||||
order: -1;
|
||||
min-height: 250px;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
footer {
|
||||
margin-top: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 450px) {
|
||||
.blog-card .cover {
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 95%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.blog-card {
|
||||
padding: 60px;
|
||||
max-width: 1100px;
|
||||
margin: 50px auto;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
.blog-card h1,
|
||||
.blog-card h2,
|
||||
.blog-card h3,
|
||||
.blog-card h4,
|
||||
.blog-card h5,
|
||||
.blog-card h6 {
|
||||
margin: 40px 0 10px;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.blog-card strong {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.blog-card a,
|
||||
.blog-card a * {
|
||||
color: var(--accent-shade-1);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.blog-card a:hover,
|
||||
.blog-card a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.blog-card del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.blog-card .title {
|
||||
margin: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.blog-card .pub-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
margin-top: auto;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.blog-card .pub-info .date {
|
||||
font-weight: bold;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
.blog-card .pub-info > * {
|
||||
margin-right: 0.5em;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
.blog-card .profile {
|
||||
display: inline-grid;
|
||||
grid-template-columns: 30px auto;
|
||||
grid-gap: 10px;
|
||||
font-weight: bold;
|
||||
color: var(--text-shade-3);
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
.blog-card .profile img {
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.blog-card p,
|
||||
.post-info {
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
.blog-card img {
|
||||
max-width: 100%;
|
||||
max-height: 800px;
|
||||
margin: 10px auto;
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.blog-card img.emoji {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.blog-card video {
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.blog-card iframe {
|
||||
width: 100%;
|
||||
aspect-ratio: 16/9;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
/* Fallback for aspect-ratio since it's unsupported by some browsers (looking at you Safari) */
|
||||
@supports not (aspect-ratio: 16/9) {
|
||||
.blog-card .aspectratio-fallback {
|
||||
position: relative;
|
||||
height: 0;
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
.blog-card iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
/* Some twitter iframe specific stuff */
|
||||
.blog-card .twitter-tweet {
|
||||
margin: auto;
|
||||
}
|
||||
.blog-card .twitter-tweet iframe {
|
||||
border: none; /* Fixes the double border */
|
||||
}
|
||||
|
||||
.blog-card table {
|
||||
border-radius: 4px;
|
||||
border-collapse: collapse;
|
||||
background: var(--bg-shade-3);
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.blog-card table th {
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-shade-4);
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
.blog-card table td {
|
||||
padding: 8px 12px;
|
||||
vertical-align: top;
|
||||
border-radius: inherit;
|
||||
}
|
||||
.blog-card table tr:nth-child(even) {
|
||||
background: var(--bg-shade-2);
|
||||
}
|
||||
|
||||
.blog-card pre code {
|
||||
border-radius: 4px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.blog-card input[type="checkbox"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: inline-block;
|
||||
background: var(--bg-shade-3);
|
||||
padding: 12px;
|
||||
margin: 4px;
|
||||
border-radius: 4px;
|
||||
vertical-align: -60%;
|
||||
}
|
||||
.blog-card input[type="checkbox"]:checked {
|
||||
content: "checkboxtest";
|
||||
background: no-repeat center/contain url(../images/check.svg),
|
||||
var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.blog-card hr {
|
||||
border: 1px solid var(--text-shade-1);
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.blog-card blockquote {
|
||||
border-left: 2px solid var(--text-shade-1);
|
||||
padding: 8px 24px;
|
||||
margin: 0;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.blog-card {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 90%;
|
||||
margin: 35px auto;
|
||||
}
|
||||
|
||||
.blog-card {
|
||||
padding: 40px 5vw;
|
||||
border-radius: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 95%;
|
||||
margin: auto auto 40px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
/* BUTTONS */
|
||||
|
||||
button,
|
||||
.button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
color: var(--text-shade-3);
|
||||
padding: 12px 48px;
|
||||
background: var(--bg-shade-3);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
button:hover,
|
||||
.button:hover {
|
||||
background: var(--bg-shade-4);
|
||||
}
|
||||
button.inactive {
|
||||
pointer-events: none;
|
||||
}
|
||||
button.primary,
|
||||
.button.primary {
|
||||
background: var(--accent-shade-0);
|
||||
}
|
||||
button.primary:hover,
|
||||
.button.primary:hover {
|
||||
background: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
button.secondary.icon-btn,
|
||||
.button.secondary.icon-btn {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
button svg,
|
||||
.button svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* MODALS */
|
||||
|
||||
body.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.modal-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
|
||||
z-index: 10;
|
||||
}
|
||||
div.modal-wrapper.hidden {
|
||||
display: none;
|
||||
}
|
||||
div.modal {
|
||||
background: var(--bg-shade-3);
|
||||
padding: 48px;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
width: min(660px, 90%);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.modal h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
p.modal-caption {
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
p.modal-caption span,
|
||||
p.switch-tier-modal-caption span {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.modal-button-wrapper {
|
||||
margin-top: 24px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.modal-button-wrapper button {
|
||||
margin-left: 12px;
|
||||
width: fit-content;
|
||||
}
|
||||
.modal-button-wrapper button.cancel {
|
||||
background: none;
|
||||
}
|
||||
.modal-button-wrapper button {
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
div.modal {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* MISC FORM COMPONENTS */
|
||||
|
||||
input[type="checkbox"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: var(--bg-shade-3);
|
||||
padding: 12px;
|
||||
margin: 4px;
|
||||
margin-left: 0;
|
||||
border-radius: 4px;
|
||||
vertical-align: -65%;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="checkbox"]:checked {
|
||||
background: no-repeat center/contain url(../images/check.svg), var(--accent-shade-0);
|
||||
}
|
||||
|
||||
input {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: block;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
background-color: var(--bg-shade-3);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
color: var(--text-shade-3);
|
||||
width: calc(100% - 24px);
|
||||
}
|
||||
input:focus {
|
||||
background-color: var(--bg-shade-4);
|
||||
outline: none;
|
||||
transition: 150ms;
|
||||
}
|
||||
input[type="range"] {
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
background: var(--bg-shade-3);
|
||||
height: 1rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
input[type="range"]::-moz-range-track {
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 1.5rem;
|
||||
height: 3rem;
|
||||
margin-top: -1rem;
|
||||
background-color: var(--accent-shade-1);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
width: 1.5rem;
|
||||
height: 3rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
background-color: var(--accent-shade-1);
|
||||
}
|
||||
input[type="range"]:focus {
|
||||
outline: none;
|
||||
}
|
||||
input[type="range"]:focus::-webkit-slider-thumb {
|
||||
background-color: var(--accent-shade-3);
|
||||
}
|
||||
input[type="range"]:focus::-moz-range-thumb {
|
||||
background-color: var(--accent-shade-3);
|
||||
}
|
||||
|
|
@ -1,425 +0,0 @@
|
|||
html,
|
||||
body,
|
||||
div.main-body {
|
||||
height: 100%;
|
||||
background: var(--bg-shade-0);
|
||||
}
|
||||
|
||||
a.logo-link {
|
||||
margin: auto;
|
||||
margin-left: 36px;
|
||||
height: 40px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button#openSidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.docs-wrapper .content:not(.search) a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
.docs-wrapper header {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin: 20px;
|
||||
margin-left: 0;
|
||||
}
|
||||
.docs-wrapper header::before {
|
||||
content: none;
|
||||
background: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
.docs-wrapper header a.logo-link {
|
||||
display: none;
|
||||
}
|
||||
.docs-wrapper header nav a:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.docs-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: fit-content(100%) auto;
|
||||
grid-template-rows: fit-content(100%) auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.docs-wrapper .sidebar {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
width: clamp(270px, 25vw, 500px);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
min-height: 100%;
|
||||
}
|
||||
.docs-wrapper .sidebar .section {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 200px;
|
||||
margin-left: clamp(60px, 10vw, 138px);
|
||||
margin-bottom: 72px;
|
||||
}
|
||||
.docs-wrapper .sidebar .section:first-child {
|
||||
margin-top: 72px;
|
||||
}
|
||||
.docs-wrapper .sidebar .section h5 {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-shade-0);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.docs-wrapper .sidebar .section a {
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
color: var(--text-shade-1);
|
||||
width: fit-content;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.docs-wrapper .sidebar .section a.active,
|
||||
.docs-wrapper .sidebar .section a:hover {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
.docs-wrapper .sidebar .section a.active::before {
|
||||
/* This filter thing is jank, if anyone knows a better way to do this please fix */
|
||||
filter: invert(51%) sepia(12%) saturate(2930%) hue-rotate(218deg)
|
||||
brightness(99%) contrast(92%);
|
||||
position: absolute;
|
||||
left: -30px;
|
||||
content: url(../images/docs/arrow-right.svg);
|
||||
}
|
||||
|
||||
.docs-wrapper .content {
|
||||
background: var(--bg-shade-1);
|
||||
padding: 72px;
|
||||
max-height: 100%;
|
||||
overflow-y: scroll;
|
||||
border-top-left-radius: 8px;
|
||||
}
|
||||
.docs-wrapper .content-inner {
|
||||
max-width: 900px;
|
||||
}
|
||||
.docs-wrapper .content p {
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
.docs-wrapper .content h1:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.docs-wrapper .content .quick-links-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-gap: 24px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
.docs-wrapper .quick-links-grid a {
|
||||
text-decoration: none;
|
||||
background: var(--bg-shade-2);
|
||||
border-radius: 6px;
|
||||
color: var(--text-shade-1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.docs-wrapper .quick-links-grid svg:first-child {
|
||||
height: 36px;
|
||||
margin-right: 24px;
|
||||
margin-left: 4px;
|
||||
color: var(--accent-shade-2);
|
||||
}
|
||||
.docs-wrapper .quick-links-grid p.header {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--text-shade-3);
|
||||
margin: 0;
|
||||
}
|
||||
.docs-wrapper .quick-links-grid p {
|
||||
margin: 0;
|
||||
}
|
||||
.docs-wrapper .quick-links-grid svg:last-child {
|
||||
height: 36px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.docs-wrapper .content-inner div.tip {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 36px;
|
||||
background: var(--bg-shade-2);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: var(--accent-shade-2);
|
||||
margin: 24px 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.docs-wrapper .content-inner div.tip::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 12px;
|
||||
background: var(--accent-shade-2);
|
||||
opacity: 1;
|
||||
}
|
||||
.docs-wrapper .content-inner div.tip.red::after {
|
||||
background: var(--red-shade-1);
|
||||
}
|
||||
.docs-wrapper .content-inner div.tip.green::after {
|
||||
background: var(--green-shade-1);
|
||||
}
|
||||
|
||||
.docs-wrapper .content .missing-in-locale-notice {
|
||||
background: var(--bg-shade-2);
|
||||
padding: 24px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.search .purple-card {
|
||||
padding: 36px;
|
||||
}
|
||||
.search .purple-card h1 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.search .purple-card p {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
.search .purple-card input::placeholder {
|
||||
color: var(--text-shade-0);
|
||||
}
|
||||
.search .purple-card input:focus {
|
||||
background-color: var(--bg-shade-4);
|
||||
color: #fff;
|
||||
transition: 200ms;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search .input-wrapper {
|
||||
position: relative;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.search .input-wrapper .matches {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
font-size: 1rem;
|
||||
background-color: var(--bg-shade-2);
|
||||
border: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
max-height: 204px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.search .input-wrapper .matches * {
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
.search .input-wrapper .matches a:hover {
|
||||
background-color: var(--bg-shade-1);
|
||||
}
|
||||
.search input.has-matches {
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.platform-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24px;
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.docs-wrapper .platform-grid a {
|
||||
text-decoration: none;
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 12px;
|
||||
color: var(--text-shade-4) !important;
|
||||
display: grid;
|
||||
grid-template-rows: auto fit-content(100%);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 36px;
|
||||
padding-bottom: 24px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.platform-grid a img {
|
||||
width: 180px;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.platform-grid a span {
|
||||
margin-top: auto;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1296px) {
|
||||
.docs-wrapper .content {
|
||||
padding: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1080px) {
|
||||
.docs-wrapper .header-wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
button#openSidebar {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
margin-left: 20px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.docs-wrapper header {
|
||||
margin-left: 20px;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.docs-wrapper {
|
||||
margin-top: 80px;
|
||||
height: calc(100% - 80px);
|
||||
}
|
||||
|
||||
a.logo-link {
|
||||
display: none;
|
||||
}
|
||||
.docs-wrapper header a.logo-link {
|
||||
display: block;
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
margin-right: 34px;
|
||||
}
|
||||
|
||||
.docs-wrapper .sidebar {
|
||||
grid-column: 1 / span 1;
|
||||
grid-row: 2 / span 1;
|
||||
width: 0;
|
||||
transition: width 250ms;
|
||||
}
|
||||
|
||||
.docs-wrapper .sidebar.open {
|
||||
width: min(300px, 100vw);
|
||||
}
|
||||
|
||||
.docs-wrapper .content {
|
||||
width: 100vw;
|
||||
box-sizing: border-box;
|
||||
border-top-left-radius: 0;
|
||||
grid-column: 2 / span 1;
|
||||
grid-row: 2 / span 1;
|
||||
}
|
||||
.docs-wrapper .content.open-sidebar {
|
||||
border-top-left-radius: 8px;
|
||||
}
|
||||
.docs-wrapper .content-inner {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.docs-wrapper header button.dropdown-button#mobile-button {
|
||||
display: none;
|
||||
}
|
||||
.docs-wrapper header .logo-link svg text {
|
||||
display: block;
|
||||
}
|
||||
.docs-wrapper header .logo-link svg {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 820px) {
|
||||
.docs-wrapper .content .quick-links-grid,
|
||||
.platform-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-auto-rows: 1fr;
|
||||
}
|
||||
|
||||
.docs-wrapper header a.logo-link {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
.docs-wrapper header div.dropdown {
|
||||
left: calc(-39.876px - 6px - 30px - 40px);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 492px) {
|
||||
.docs-wrapper .content {
|
||||
padding: 36px;
|
||||
}
|
||||
|
||||
header .logo-link svg text {
|
||||
display: none;
|
||||
}
|
||||
header .logo-link svg {
|
||||
width: 39.876px;
|
||||
}
|
||||
.docs-wrapper header a.logo-link {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
.docs-wrapper .content {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar styling 'cause it's fancy */
|
||||
.docs-wrapper .sidebar::-webkit-scrollbar,
|
||||
.docs-wrapper .content::-webkit-scrollbar,
|
||||
.docs-wrapper .content pre code::-webkit-scrollbar,
|
||||
.search .input-wrapper .matches::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
.docs-wrapper .sidebar::-webkit-scrollbar-track,
|
||||
.docs-wrapper .content::-webkit-scrollbar-track,
|
||||
.docs-wrapper .content pre code::-webkit-scrollbar-track,
|
||||
.search .input-wrapper .matches::-webkit-scrollbar-track {
|
||||
background: none;
|
||||
}
|
||||
.docs-wrapper .sidebar::-webkit-scrollbar-thumb,
|
||||
.docs-wrapper .content::-webkit-scrollbar-thumb,
|
||||
.docs-wrapper .content pre code::-webkit-scrollbar-thumb,
|
||||
.search .input-wrapper .matches::-webkit-scrollbar-thumb {
|
||||
background-color: var(--text-shade-0);
|
||||
border-radius: 24px;
|
||||
border: 3px solid var(--bg-shade-0);
|
||||
}
|
||||
.docs-wrapper .content::-webkit-scrollbar-thumb {
|
||||
border: 3px solid var(--bg-shade-1);
|
||||
}
|
||||
.docs-wrapper .content pre code::-webkit-scrollbar-thumb,
|
||||
.search .input-wrapper .matches::-webkit-scrollbar-thumb {
|
||||
border: 3px solid var(--bg-shade-2);
|
||||
}
|
||||
|
||||
.docs-wrapper .sidebar,
|
||||
.search .input-wrapper .matches {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--text-shade-0) var(--bg-shade-1);
|
||||
}
|
||||
.docs-wrapper .content {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--text-shade-0) var(--bg-shade-1);
|
||||
}
|
||||
.docs-wrapper .content pre code {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--text-shade-0) var(--bg-shade-0);
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
.select-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.select-box > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.select-box .options-container {
|
||||
max-height: 0;
|
||||
width: fit-content;
|
||||
opacity: 0;
|
||||
transition: all 0.4s;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
background-color: var(--bg-shade-3);
|
||||
order: 1;
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.select-box .option .item {
|
||||
color: var(--text-shade-2);
|
||||
}
|
||||
|
||||
.select-box .lang {
|
||||
width: 1.3rem;
|
||||
height: 1rem;
|
||||
margin-right: .2rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select-box .options-container.active {
|
||||
max-height: 240px;
|
||||
opacity: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.select-box .options-container.active + .locale-dropdown-toggle::after {
|
||||
transform: translateY(-50%) rotateX(180deg);
|
||||
}
|
||||
|
||||
.select-box .options-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
.select-box .options-container::-webkit-scrollbar-thumb {
|
||||
background: var(--text-shade-1);
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
.select-box .option {
|
||||
padding: 12px 15px;
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.select-box .option:hover {
|
||||
background: var(--bg-shade-4);
|
||||
}
|
||||
.select-box .option:hover .item {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.select-box label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select-box .option .radio {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
pre code.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 36px;
|
||||
border-radius: 10px;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
code.hljs {
|
||||
padding: 3px 5px;
|
||||
}
|
||||
.hljs {
|
||||
background: var(--bg-shade-0);
|
||||
color: #d6deeb;
|
||||
}
|
||||
.hljs-keyword {
|
||||
color: var(--accent-shade-2);
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-built_in {
|
||||
color: #addb67;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-type {
|
||||
color: #82aaff;
|
||||
}
|
||||
.hljs-literal {
|
||||
color: #ff5874;
|
||||
}
|
||||
.hljs-number {
|
||||
color: #f78c6c;
|
||||
}
|
||||
.hljs-regexp {
|
||||
color: #5ca7e4;
|
||||
}
|
||||
.hljs-string {
|
||||
color: #ecc48d;
|
||||
}
|
||||
.hljs-subst {
|
||||
color: #d3423e;
|
||||
}
|
||||
.hljs-symbol {
|
||||
color: #82aaff;
|
||||
}
|
||||
.hljs-class {
|
||||
color: #ffcb8b;
|
||||
}
|
||||
.hljs-function {
|
||||
color: #82aaff;
|
||||
}
|
||||
.hljs-title {
|
||||
color: #dcdcaa;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-params {
|
||||
color: #7fdbca;
|
||||
}
|
||||
.hljs-comment {
|
||||
color: #637777;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-doctag {
|
||||
color: #7fdbca;
|
||||
}
|
||||
.hljs-meta,
|
||||
.hljs-meta .hljs-keyword {
|
||||
color: #82aaff;
|
||||
}
|
||||
.hljs-meta .hljs-string {
|
||||
color: #ecc48d;
|
||||
}
|
||||
.hljs-section {
|
||||
color: #82b1ff;
|
||||
}
|
||||
.hljs-attr,
|
||||
.hljs-name,
|
||||
.hljs-tag {
|
||||
color: #7fdbca;
|
||||
}
|
||||
.hljs-attribute {
|
||||
color: #80cbc4;
|
||||
}
|
||||
.hljs-variable {
|
||||
color: #addb67;
|
||||
}
|
||||
.hljs-bullet {
|
||||
color: #d9f5dd;
|
||||
}
|
||||
.hljs-code {
|
||||
color: #80cbc4;
|
||||
}
|
||||
.hljs-emphasis {
|
||||
color: #c792ea;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-strong {
|
||||
color: #addb67;
|
||||
font-weight: 700;
|
||||
}
|
||||
.hljs-formula {
|
||||
color: #c792ea;
|
||||
}
|
||||
.hljs-link {
|
||||
color: #ff869a;
|
||||
}
|
||||
.hljs-quote {
|
||||
color: #697098;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-selector-tag {
|
||||
color: #ff6363;
|
||||
}
|
||||
.hljs-selector-id {
|
||||
color: #fad430;
|
||||
}
|
||||
.hljs-selector-class {
|
||||
color: #addb67;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #c792ea;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-template-tag {
|
||||
color: #c792ea;
|
||||
}
|
||||
.hljs-template-variable {
|
||||
color: #addb67;
|
||||
}
|
||||
.hljs-addition {
|
||||
color: #addb67ff;
|
||||
font-style: italic;
|
||||
}
|
||||
.hljs-deletion {
|
||||
color: #ef535090;
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
.localization-wrapper {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 155px);
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.localization-widget {
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.caption {
|
||||
color: var(--text-shade-1);
|
||||
max-width: 400px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.title.dot {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.localization-instr,
|
||||
.localization-instr:visited {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--accent-shade-2);
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
left: -4px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.localization-instr svg {
|
||||
height: 1.3em;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.localization-form {
|
||||
padding: 36px;
|
||||
background-color: var(--bg-shade-0);
|
||||
border-radius: 12px;
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.localization-form input {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: 0;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
background-color: var(--bg-shade-3);
|
||||
border: none;
|
||||
border-radius: 4px 0 0 4px;
|
||||
padding: 12px 24px;
|
||||
color: var(--text-shade-1);
|
||||
width: 20px;
|
||||
flex: 2 10%;
|
||||
}
|
||||
|
||||
.localization-form input::placeholder {
|
||||
color: var(--text-shade-0);
|
||||
}
|
||||
|
||||
.localization-form input:focus {
|
||||
background-color: var(--bg-shade-4);
|
||||
color: var(--bg-shade-3);
|
||||
transition: 200ms;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.localization-form button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: 0;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
color: var(--text-shade-3);
|
||||
padding: 12px 36px;
|
||||
background: var(--accent-shade-0);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
.wrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
header {
|
||||
margin: 35px 0;
|
||||
}
|
||||
|
||||
.account-form-wrapper {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
form.account {
|
||||
display: block;
|
||||
padding: 40px 48px;
|
||||
background-color: var(--bg-shade-2);
|
||||
color: var(--text-shade-1);
|
||||
border-radius: 12px;
|
||||
width: min(480px, 90vw);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form.account h2 {
|
||||
margin: 0;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
form.account p {
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
form.account div {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
form.account label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
form.account button {
|
||||
width: 100%;
|
||||
background: var(--accent-shade-0);
|
||||
}
|
||||
|
||||
form.account a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
color: var(--text-shade-1);
|
||||
text-align: right;
|
||||
margin: 6px 0;
|
||||
width: fit-content;
|
||||
}
|
||||
form.account a:hover {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
form.account a.pwdreset {
|
||||
margin-left: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
form.account a.register {
|
||||
margin: auto;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
@keyframes banner-notice {
|
||||
0% {
|
||||
top: -150px;
|
||||
}
|
||||
20% {
|
||||
top: 35px;
|
||||
}
|
||||
80% {
|
||||
top: 35px;
|
||||
}
|
||||
100% {
|
||||
top: -150px;
|
||||
}
|
||||
}
|
||||
.banner-notice {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: -150px;
|
||||
width: 100%;
|
||||
animation: banner-notice 5s;
|
||||
}
|
||||
.banner-notice div {
|
||||
padding: 4px 36px;
|
||||
border-radius: 5px;
|
||||
z-index: 3;
|
||||
}
|
||||
.banner-notice.error div {
|
||||
background: var(--red-shade-1);
|
||||
}
|
||||
|
||||
form.account.register {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
width: min(780px, 90vw);
|
||||
column-gap: 24px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
form.account.register div.h-captcha {
|
||||
grid-column: 1 / span 2;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
form.account.register p,
|
||||
form.account.register div.email,
|
||||
form.account.register div.buttons {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 720px) {
|
||||
form.account.register {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
form.account.register div.h-captcha,
|
||||
form.account.register p,
|
||||
form.account.register div.email,
|
||||
form.account.register div.buttons {
|
||||
grid-column: unset;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,709 +0,0 @@
|
|||
html,
|
||||
body,
|
||||
div.main-body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body,
|
||||
div.main-body,
|
||||
.miieditor-wrapper {
|
||||
z-index: -1;
|
||||
user-select: none;
|
||||
background: var(--bg-shade-0);
|
||||
}
|
||||
|
||||
svg.logotype {
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
top: 42px;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
.miieditor-wrapper {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
|
||||
width: 95vw;
|
||||
max-width: 1920px;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
gap: 0 120px;
|
||||
}
|
||||
.params-wrapper::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: var(--bg-shade-1);
|
||||
border-radius: 100% 0 0 100%;
|
||||
width: 1300px;
|
||||
height: 1700px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
left: -200px;
|
||||
z-index: -1;
|
||||
}
|
||||
.miieditor-wrapper::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
background: radial-gradient(
|
||||
closest-side,
|
||||
var(--bg-shade-1) 0%,
|
||||
rgba(27, 31, 59, 0) 100%
|
||||
);
|
||||
width: 200vh;
|
||||
height: 200vh;
|
||||
top: -100vh;
|
||||
left: -100vh;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.canvas-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
canvas#miiCanvas {
|
||||
width: auto;
|
||||
height: auto;
|
||||
transform-origin: center;
|
||||
transition: transform 200ms, filter 200ms;
|
||||
}
|
||||
|
||||
div.mii-img-wrapper::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -22px;
|
||||
height: 72px;
|
||||
width: 100%;
|
||||
background: radial-gradient(
|
||||
farthest-side,
|
||||
var(--bg-shade-2) 0%,
|
||||
rgba(35, 39, 74, 0) 100%
|
||||
);
|
||||
}
|
||||
|
||||
div.params-wrapper {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
display: grid;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
div.tabs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(11, 1fr);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #0a0c19;
|
||||
padding: 6px;
|
||||
gap: 6px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
div.tabs .tabbtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 6px;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
div.tabs .tabbtn::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: url("/assets/images/miieditor.svg");
|
||||
background-position: calc(var(--assetcol) * -12px) -312px;
|
||||
transform: scale(2.9);
|
||||
}
|
||||
div.tabs .tabbtn:hover,
|
||||
div.tabs .tabbtn.active {
|
||||
background: var(--bg-shade-2);
|
||||
}
|
||||
|
||||
div.subtabs {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
}
|
||||
div.subtabs .subtabbtn {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
border-radius: 6px;
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: var(--text-shade-3);
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
div.subtabs .subtabbtn::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: url("/assets/images/miieditor.svg");
|
||||
background-position: calc(var(--assetcol) * -12px) -324px;
|
||||
transform: scale(2.9);
|
||||
}
|
||||
div.subtabs .subtabbtn.active::before,
|
||||
div.subtabs .subtabbtn.active:hover::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 5%;
|
||||
width: 90%;
|
||||
height: 5px;
|
||||
background: var(--accent-shade-1);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.has-sliders {
|
||||
grid-template-columns: 60px auto;
|
||||
gap: 12px;
|
||||
}
|
||||
.has-sliders label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.has-sliders label::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url("/assets/images/miieditor.svg");
|
||||
background-position: calc(var(--assetcol) * -16px) -336px;
|
||||
transform: scale(3);
|
||||
}
|
||||
|
||||
.has-textinput {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-rows: auto;
|
||||
grid-auto-rows: auto;
|
||||
gap: 24px;
|
||||
}
|
||||
.has-textinput label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.has-textinput .icons {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.has-textinput .icons input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
height: 49px;
|
||||
width: 49px;
|
||||
}
|
||||
|
||||
input[type="checkbox"]#allowCopying:checked {
|
||||
background: no-repeat center/80% url(../images/copy.svg), var(--accent-shade-0);
|
||||
}
|
||||
input[type="checkbox"]#disableSharing:checked {
|
||||
background: no-repeat center/80% url(../images/share.svg), var(--accent-shade-0);
|
||||
}
|
||||
input[type="checkbox"]#favorite:checked {
|
||||
background: no-repeat center/80% url(../images/star.svg), var(--accent-shade-0);
|
||||
}
|
||||
|
||||
form.params {
|
||||
grid-template-columns: repeat(2, auto);
|
||||
height: 618px;
|
||||
width: 582px;
|
||||
}
|
||||
form.params .tab {
|
||||
display: none;
|
||||
gap: 4rem 0;
|
||||
}
|
||||
form.params .tab.active {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
fieldset,
|
||||
fieldset.has-subpages .subpage {
|
||||
appearance: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: none;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: repeat(4, 1fr);
|
||||
gap: 18px;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
fieldset.active {
|
||||
display: grid;
|
||||
}
|
||||
fieldset input[type="radio"] {
|
||||
display: none;
|
||||
}
|
||||
fieldset input[type="radio"] + label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 18px;
|
||||
background: var(--bg-shade-3);
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
fieldset.has-subpages.active {
|
||||
display: block;
|
||||
}
|
||||
fieldset.has-subpages .subpage.active {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
fieldset:not(.color, #favoriteColor) input[type="radio"] + label::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: url("/assets/images/miieditor.svg");
|
||||
background-position: calc(
|
||||
((var(--assetcol) + (var(--subpage, 0) * 12)) * -24px)
|
||||
)
|
||||
calc(var(--assetrow) * -24px);
|
||||
transform: scale(4.5);
|
||||
}
|
||||
|
||||
fieldset input[type="radio"]:checked + label {
|
||||
background: var(--bg-shade-4);
|
||||
box-shadow: inset 0 0 0 4px var(--accent-shade-1);
|
||||
}
|
||||
fieldset.color input[type="radio"]:checked + label,
|
||||
fieldset#favoriteColor input[type="radio"]:checked + label {
|
||||
box-shadow: inset 0 0 0 4px var(--accent-shade-1),
|
||||
inset 0 0 0 6px var(--bg-shade-1);
|
||||
}
|
||||
|
||||
input[type="range"].invert {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
height: fit-content;
|
||||
grid-column: 1 / span 4;
|
||||
grid-row: 4;
|
||||
margin-left: auto;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
.pagination .current-page-index {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
color: var(--text-shade-3);
|
||||
width: 18px;
|
||||
margin-right: 0.5ch;
|
||||
text-align: right;
|
||||
}
|
||||
.page-btn {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.page-btn:hover {
|
||||
background: none;
|
||||
}
|
||||
.page-btn:hover svg path {
|
||||
fill: var(--accent-shade-3);
|
||||
}
|
||||
.page-btn svg {
|
||||
height: 36px;
|
||||
margin: 6px;
|
||||
}
|
||||
.page-btn.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
.page-btn.disabled svg path {
|
||||
fill: var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.tab#saveTab {
|
||||
gap: 2rem 0;
|
||||
}
|
||||
.tab#saveTab p.save-prompt {
|
||||
margin-bottom: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mii-comparison-animation-wrapper {
|
||||
position: relative;
|
||||
height: fit-content;
|
||||
}
|
||||
.mii-comparison {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, auto);
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
.mii-comparison.confirmed {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.mii-comparison img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.mii-comparison .new-mii-wrapper {
|
||||
position: relative;
|
||||
transition: right 500ms, transform 500ms;
|
||||
right: 0;
|
||||
}
|
||||
.mii-comparison .new-mii-wrapper::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
display: block;
|
||||
box-shadow: inset 0 0 0 8px var(--accent-shade-1);
|
||||
border-radius: 24px;
|
||||
margin: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.mii-comparison svg {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
.mii-comparison svg path {
|
||||
fill: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.25s forwards;
|
||||
}
|
||||
.fade-out {
|
||||
animation: fadeOut 0.5s forwards;
|
||||
}
|
||||
.mii-comparison div.new-mii-wrapper.centered-mii-img {
|
||||
position: absolute;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
height: 100%;
|
||||
aspect-ratio: 1;
|
||||
width: auto;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes fadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1400px) {
|
||||
form.params {
|
||||
height: 562px;
|
||||
width: 512px;
|
||||
}
|
||||
|
||||
fieldset,
|
||||
fieldset.has-subpages .subpage {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
div.params-wrapper {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.params-wrapper::before {
|
||||
left: -150px;
|
||||
}
|
||||
|
||||
div.tabs {
|
||||
padding: 4px;
|
||||
gap: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1280px) {
|
||||
form.params {
|
||||
height: 492px;
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
fieldset,
|
||||
fieldset.has-subpages .subpage {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
div.params-wrapper {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.params-wrapper::before {
|
||||
left: -150px;
|
||||
}
|
||||
|
||||
fieldset:not(.color, #favoriteColor) input[type="radio"] + label::after {
|
||||
transform: scale(4);
|
||||
}
|
||||
|
||||
div.subtabs .subtabbtn::after,
|
||||
div.tabs .tabbtn::after {
|
||||
transform: scale(2.4);
|
||||
}
|
||||
div.subtabs .subtabbtn {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.params-wrapper::before {
|
||||
left: -100px;
|
||||
}
|
||||
|
||||
div.tabs {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
form.params .tab {
|
||||
gap: 2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1120px) {
|
||||
form.params {
|
||||
height: 444px;
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
fieldset,
|
||||
fieldset.has-subpages .subpage {
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1080px) {
|
||||
.canvas-wrapper {
|
||||
height: calc(100% - 12px);
|
||||
}
|
||||
|
||||
svg.logotype {
|
||||
left: 0;
|
||||
}
|
||||
svg.logotype text#Pretendo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.miieditor-wrapper {
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto fit-content(100%);
|
||||
margin: auto;
|
||||
max-width: 360px;
|
||||
width: 90vw;
|
||||
}
|
||||
|
||||
div.params-wrapper {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
div.tabs,
|
||||
div.subtabs {
|
||||
order: 2;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
form.params {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
order: 1;
|
||||
}
|
||||
fieldset input[type="radio"] + label {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
fieldset:not(.color, #favoriteColor) input[type="radio"] + label::after {
|
||||
transform: scale(3.5);
|
||||
}
|
||||
|
||||
.has-sliders {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
gap: 2px 6px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--text-shade-1) var(--bg-shade-3);
|
||||
}
|
||||
.has-sliders::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 9px;
|
||||
}
|
||||
.has-sliders::-webkit-scrollbar-thumb {
|
||||
background: var(--text-shade-1);
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.params-wrapper::before {
|
||||
top: -12px;
|
||||
left: -100vw;
|
||||
width: 300vw;
|
||||
border-radius: 0;
|
||||
background: var(--bg-shade-2);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
fieldset,
|
||||
fieldset.has-subpages .subpage {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-rows: repeat(3, 1fr) 48px;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
grid-column: 1 / span 4;
|
||||
grid-row: 4;
|
||||
}
|
||||
|
||||
fieldset:not(.has-sliders, .has-textinput) {
|
||||
position: relative;
|
||||
margin-bottom: -60px;
|
||||
}
|
||||
|
||||
div.tabs {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
form.params .tab {
|
||||
gap: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
div.subtabs {
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
form.params {
|
||||
grid-template-columns: repeat(2, auto);
|
||||
}
|
||||
|
||||
div.tabs .tabbtn::after,
|
||||
div.subtabs .subtabbtn::after,
|
||||
.has-sliders label::after {
|
||||
transform: scale(2);
|
||||
}
|
||||
|
||||
div.subtabs .subtabbtn {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 424px) {
|
||||
fieldset:not(.color, #favoriteColor) input[type="radio"] + label::after {
|
||||
transform: scale(2.8);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 396px) {
|
||||
div.tabs .tabbtn::after,
|
||||
div.subtabs .subtabbtn::after,
|
||||
.has-sliders label::after {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
div.tabs .tabbtn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
div.subtabs .subtabbtn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
div.pagination {
|
||||
transform: scale(0.7);
|
||||
transform-origin: right;
|
||||
}
|
||||
|
||||
.has-textinput {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 360px) {
|
||||
fieldset,
|
||||
fieldset.has-subpages .subpage {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
fieldset input[type="radio"]:checked + label {
|
||||
box-shadow: inset 0 0 0 3px var(--accent-shade-1);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 344px) {
|
||||
fieldset:not(.color, #favoriteColor) input[type="radio"] + label::after {
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
div.tabs .tabbtn {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
div.tabs .tabbtn::after {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
footer {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, fit-content(100%)) 1fr;
|
||||
gap: min(48px, 7.7vw);
|
||||
color: var(--text-shade-1);
|
||||
margin-top: 120px;
|
||||
position: relative;
|
||||
padding: 60px 0;
|
||||
}
|
||||
footer::after {
|
||||
content: "";
|
||||
width: 400vw;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -50vw;
|
||||
background: var(--bg-shade-0);
|
||||
z-index: -1;
|
||||
}
|
||||
footer div {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: fit-content;
|
||||
}
|
||||
footer svg.logotype {
|
||||
height: 56px;
|
||||
width: fit-content;
|
||||
margin: -10px 0 24px -10px;
|
||||
}
|
||||
footer p {
|
||||
margin: 0;
|
||||
}
|
||||
footer h1 {
|
||||
font-size: 20px;
|
||||
margin-top: 0;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
footer a {
|
||||
color: var(--text-shade-1);
|
||||
text-decoration: none;
|
||||
width: fit-content;
|
||||
}
|
||||
footer a:hover {
|
||||
color: var(--text-shade-3);
|
||||
text-decoration: underline;
|
||||
}
|
||||
footer div.discord-server-card-wrapper {
|
||||
z-index: 2;
|
||||
justify-self: end;
|
||||
position: relative;
|
||||
}
|
||||
footer div.discord-server-card {
|
||||
background: var(--bg-shade-2);
|
||||
border-radius: 12px;
|
||||
padding: 30px 90px 30px 36px;
|
||||
}
|
||||
footer div.discord-server-card h1 {
|
||||
font-size: 25px;
|
||||
margin: 0;
|
||||
}
|
||||
footer div.discord-server-card h2 {
|
||||
color: var(--text-shade-3);
|
||||
font-size: 22px;
|
||||
margin: 0;
|
||||
}
|
||||
footer div.discord-server-card a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--accent-shade-3);
|
||||
font-size: 22px;
|
||||
text-decoration: none;
|
||||
width: fit-content;
|
||||
margin-left: -2px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
footer div.discord-server-card a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
footer div.discord-server-card svg {
|
||||
height: 24px;
|
||||
stroke-width: 3px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
footer div.discord-server-card-wrapper .bandwidth-raccoon-wrapper {
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
right: 0px;
|
||||
z-index: -1;
|
||||
}
|
||||
footer div.discord-server-card-wrapper img.bandwidth-raccoon {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
cursor: pointer;
|
||||
transform: none;
|
||||
transition: transform 150ms;
|
||||
}
|
||||
footer div.bandwidth-raccoon-wrapper.speak img.bandwidth-raccoon {
|
||||
transform: rotate(12deg) translateY(-12px);
|
||||
}
|
||||
footer .bandwidth-raccoon-wrapper .text-bubble {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
top: -24px;
|
||||
max-width: min(200%, 90vw);
|
||||
width: max-content;
|
||||
background: var(--bg-shade-3);
|
||||
padding: 18px;
|
||||
align-self: center;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 12px;
|
||||
box-sizing: border-box;
|
||||
transform: translateY(-100%);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
footer .bandwidth-raccoon-wrapper.speak .text-bubble {
|
||||
opacity: 1;
|
||||
}
|
||||
footer .bandwidth-raccoon-wrapper .text-bubble:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
z-index: 1;
|
||||
border-style: solid;
|
||||
border-color: var(--bg-shade-3) transparent;
|
||||
border-width: 12px 12px 0;
|
||||
bottom: -9px;
|
||||
right: 60px;
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
footer {
|
||||
margin-top: 100px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(2, fit-content(100%));
|
||||
}
|
||||
footer div {
|
||||
justify-self: center;
|
||||
}
|
||||
footer div.discord-server-card-wrapper {
|
||||
grid-column: 1 / span 4;
|
||||
width: 100%;
|
||||
justify-self: normal;
|
||||
}
|
||||
footer div.discord-server-card-wrapper::before {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
position: absolute;
|
||||
bottom: -60px;
|
||||
left: 0;
|
||||
background: var(--bg-shade-0);
|
||||
z-index: 2;
|
||||
}
|
||||
footer div.discord-server-card-wrapper .bandwidth-raccoon-wrapper {
|
||||
bottom: -72px;
|
||||
top: unset;
|
||||
z-index: 0;
|
||||
}
|
||||
footer div.discord-server-card {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 580px) {
|
||||
footer {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: repeat(4, fit-content(100%));
|
||||
}
|
||||
footer div {
|
||||
justify-self: start;
|
||||
}
|
||||
footer div.discord-server-card-wrapper {
|
||||
grid-column: 1 / span 1;
|
||||
}
|
||||
footer div.discord-server-card {
|
||||
padding: 30px;
|
||||
overflow: visible;
|
||||
}
|
||||
footer div.discord-server-card-wrapper .bandwidth-raccoon-wrapper {
|
||||
bottom: unset;
|
||||
top: -120px;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
footer div.discord-server-card-wrapper .bandwidth-raccoon-wrapper {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,469 +0,0 @@
|
|||
header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 2.5%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 95%;
|
||||
margin-top: 35px;
|
||||
z-index: 60;
|
||||
transition: box-shadow 180ms, background 180ms;
|
||||
}
|
||||
header * {
|
||||
z-index: 1;
|
||||
}
|
||||
header::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -35px;
|
||||
left: -10vw;
|
||||
width: 120vw;
|
||||
height: calc(100% + 35px + 35px);
|
||||
background: rgba(27, 31, 59, 0.98);
|
||||
transition: background 180ms;
|
||||
}
|
||||
header.transparent,
|
||||
header.transparent::before {
|
||||
background: rgba(27, 31, 59, 0);
|
||||
}
|
||||
header.dropdown-active {
|
||||
background: rgba(27, 31, 59, 0.98);
|
||||
box-shadow: 0 0 0 600vw rgba(27, 31, 59, 0.8);
|
||||
}
|
||||
header .dropdown-arrow {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
border-style: solid;
|
||||
border-color: var(--bg-shade-3) transparent;
|
||||
border-width: 0 14px 14px;
|
||||
bottom: -26px;
|
||||
margin-left: -10px;
|
||||
margin-bottom: -10px;
|
||||
transition: left 180ms, margin-bottom 180ms, opacity 180ms;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
header.dropdown-active .dropdown-arrow {
|
||||
opacity: 1;
|
||||
display: block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
header a {
|
||||
text-decoration: none;
|
||||
}
|
||||
header .logo-link,
|
||||
header .logo-link svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
header div.left-section {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
|
||||
header nav {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-left: 40px;
|
||||
}
|
||||
header button.dropdown-button#mobile-button {
|
||||
display: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin-right: 12px;
|
||||
transition: transform 200ms;
|
||||
}
|
||||
header button.dropdown-button#mobile-button.active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
header div.dropdown-button-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
/* these are safezones where the dropdown will not close */
|
||||
header div.dropdown-button-wrapper::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: -12px;
|
||||
}
|
||||
header div.dropdown-button-wrapper::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
bottom: -32px;
|
||||
left: 0;
|
||||
right: -12px;
|
||||
}
|
||||
|
||||
header nav button {
|
||||
background: none;
|
||||
color: var(--text-shade-1);
|
||||
margin: 0 17px;
|
||||
padding: 0;
|
||||
}
|
||||
header nav button:hover,
|
||||
header nav button.active {
|
||||
background: none;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
header nav a.donate button {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-weight: bold;
|
||||
background: #332b61;
|
||||
color: var(--accent-shade-3);
|
||||
padding: 2px 12px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
header nav a.donate button svg {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
header nav a.donate button:hover {
|
||||
background: var(--accent-shade-0);
|
||||
color: #fff;
|
||||
}
|
||||
header nav a.donate button.dropdown-button::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
width: 16px;
|
||||
height: 8px;
|
||||
bottom: 0;
|
||||
margin-left: 0.3rem;
|
||||
background: no-repeat center url("/assets/images/down-arrow.svg");
|
||||
filter: brightness(0) invert(78%) sepia(2%) saturate(5488%) hue-rotate(197deg)
|
||||
brightness(88%) contrast(93%);
|
||||
|
||||
transition: transform 100ms;
|
||||
}
|
||||
header nav button.dropdown-button.active::after {
|
||||
transform: scaleY(-1);
|
||||
filter: none;
|
||||
}
|
||||
|
||||
header div.dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
margin-top: 24px;
|
||||
left: 0;
|
||||
display: block;
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 8px;
|
||||
width: 420px;
|
||||
height: 0;
|
||||
overflow-y: hidden;
|
||||
transition: height 180ms;
|
||||
}
|
||||
header div.dropdown * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
header div.dropdown-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 180ms;
|
||||
}
|
||||
header div.dropdown-content.show {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
header div.dropdown .top {
|
||||
padding: 32px 18px;
|
||||
}
|
||||
header div.dropdown .top a {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 16px;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
color: var(--text-shade-1);
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
header div.dropdown .top a .icon {
|
||||
background: var(--bg-shade-2);
|
||||
color: var(--accent-shade-3);
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
header div.dropdown .top a:hover .icon {
|
||||
background: #151b44;
|
||||
color: var(--accent-shade-1);
|
||||
}
|
||||
header div.dropdown .top a .icon svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 12px;
|
||||
}
|
||||
header div.dropdown .top a .title {
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
header div.dropdown .top a .caption {
|
||||
margin: 0;
|
||||
}
|
||||
header div.dropdown .top a:hover {
|
||||
background: var(--bg-shade-2-5);
|
||||
}
|
||||
header div.dropdown .top a:hover::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 24px;
|
||||
width: 24px;
|
||||
height: 100%;
|
||||
background: no-repeat center url("/assets/images/arrow-right.svg");
|
||||
/* garbage to make it look the same color */
|
||||
filter: brightness(0) invert(60%) sepia(70%) saturate(453%) hue-rotate(208deg)
|
||||
brightness(113%) contrast(97%);
|
||||
}
|
||||
header div.dropdown .top a:hover .title {
|
||||
color: var(--accent-shade-3);
|
||||
}
|
||||
|
||||
header div.dropdown .bottom {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
background: var(--bg-shade-3-5);
|
||||
padding: 22px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
header div.dropdown .bottom a {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: var(--bg-shade-3);
|
||||
color: var(--text-shade-3);
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
header div.dropdown .bottom a:hover {
|
||||
background: var(--bg-shade-2);
|
||||
}
|
||||
header div.dropdown .bottom a svg {
|
||||
width: 24px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
header div.dropdown .top a.show-on-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header .right-section {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 24px;
|
||||
margin-left: auto;
|
||||
z-index: 2;
|
||||
color: var(--text-shade-1);
|
||||
}
|
||||
|
||||
header .locale-dropdown-toggle {
|
||||
width: fit-content;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
transition: color 150ms;
|
||||
cursor: pointer;
|
||||
}
|
||||
header .locale-dropdown-toggle:hover,
|
||||
header .locale-dropdown-toggle.active {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
header .user-widget-wrapper {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
header .user-widget-wrapper a.login-link {
|
||||
color: var(--text-shade-1);
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
height: 32px;
|
||||
transition: color 150ms;
|
||||
}
|
||||
header .user-widget-wrapper a.login-link:hover {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
header .user-widget-wrapper.logged-in {
|
||||
position: relative;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
header .user-widget-wrapper.logged-in .user-widget-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--text-shade-0);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
header .user-widget-wrapper .user-widget-toggle img,
|
||||
header .user-widget .user-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
header .user-widget {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
transition: max-height 300ms, padding 200ms, opacity 150ms;
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 48px;
|
||||
padding: 0;
|
||||
background: var(--bg-shade-2);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
opacity: 0;
|
||||
|
||||
box-shadow: 0 0 10px -2px var(--bg-shade-0);
|
||||
}
|
||||
header .user-widget.active {
|
||||
max-height: 100vh;
|
||||
padding: 36px;
|
||||
opacity: 1;
|
||||
}
|
||||
header .user-widget .user-avatar {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
margin: auto;
|
||||
background: var(--text-shade-0);
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
header .user-widget .user-info {
|
||||
margin-top: 12px;
|
||||
}
|
||||
header .user-widget .user-info .mii-name {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
header .user-widget .buttons {
|
||||
margin-top: 12px;
|
||||
}
|
||||
header .user-widget .button {
|
||||
margin-top: 12px;
|
||||
width: 100%;
|
||||
padding: 8px 60px;
|
||||
cursor: pointer;
|
||||
}
|
||||
header .user-widget .button.logout {
|
||||
background: var(--bg-shade-3);
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
header {
|
||||
position: relative;
|
||||
justify-content: flex-end;
|
||||
width: 90%;
|
||||
left: 5%;
|
||||
}
|
||||
|
||||
header .hide-on-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
header button.dropdown-button#mobile-button {
|
||||
display: block;
|
||||
}
|
||||
header .left-section {
|
||||
margin-right: auto;
|
||||
}
|
||||
header .right-section {
|
||||
margin-left: 24px;
|
||||
}
|
||||
header nav a.donate button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
header div.dropdown {
|
||||
left: unset;
|
||||
right: -104px;
|
||||
width: 90vw;
|
||||
}
|
||||
header div.dropdown .top {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
header div.dropdown .top {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
header div.dropdown .top {
|
||||
padding: 16px 9px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
header nav a.donate button span {
|
||||
display: none;
|
||||
}
|
||||
header nav a.donate button {
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 420px) {
|
||||
header nav a.donate button {
|
||||
display: none;
|
||||
}
|
||||
header .right-section {
|
||||
margin-left: 0;
|
||||
}
|
||||
header div.dropdown {
|
||||
right: -80px;
|
||||
}
|
||||
header div.dropdown .top a {
|
||||
padding: 8px;
|
||||
}
|
||||
header div.dropdown .top a .icon {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
header div.dropdown .top a .icon svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 330px) {
|
||||
header .logo-link svg text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
header .logo-link svg {
|
||||
width: 39.876px;
|
||||
}
|
||||
|
||||
header .logo-link {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
MOVE PROGRESS CSS HERE
|
||||
*/
|
||||
#quick-nav a {
|
||||
color: var(--text-shade-1);
|
||||
text-decoration: none;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
#quick-nav a:hover {
|
||||
color: var(--text-shade-3);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
@ -1,308 +0,0 @@
|
|||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.wrapper::before {
|
||||
position: absolute;
|
||||
top: -800px;
|
||||
content: "";
|
||||
background: var(--bg-shade-0);
|
||||
border-radius: 100%;
|
||||
width: 1600px;
|
||||
height: 1400px;
|
||||
}
|
||||
|
||||
.back-arrow {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
top: 36px;
|
||||
left: max(calc((100vw - 1590px) / 2), 2.5vw);
|
||||
padding: 6px 10px;
|
||||
background: var(--bg-shade-3);
|
||||
border-radius: 24px;
|
||||
|
||||
transition: filter 150ms;
|
||||
text-decoration: none;
|
||||
color: var(--text-shade-3);
|
||||
z-index: 5;
|
||||
}
|
||||
.back-arrow:hover {
|
||||
filter: brightness(1.5)
|
||||
}
|
||||
.back-arrow svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.back-arrow span {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.account-form-wrapper {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: min(1200px, 100%);
|
||||
color: var(--text-shade-1);
|
||||
margin: 0 auto 48px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.account-form-wrapper .logotype {
|
||||
margin: 36px auto 0;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
h1.title {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
p.caption {
|
||||
width: min(100%, 500px);
|
||||
margin: 0 auto 36px;
|
||||
}
|
||||
|
||||
.account-form-wrapper .progress-bar-wrapper {
|
||||
justify-content: center;
|
||||
width: min(100%, 500px);
|
||||
margin: 0 auto 72px;
|
||||
padding: 24px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-shade-2);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.account-form-wrapper .progress-bar-wrapper p {
|
||||
text-align: left;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.account-form-wrapper .progress-bar-wrapper p span {
|
||||
color: var(--text-shade-3);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.account-form-wrapper .progress-bar {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1.3rem;
|
||||
}
|
||||
|
||||
form .tier-radio {
|
||||
display: none;
|
||||
}
|
||||
form .tier-radio:checked + label::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: inset 0 0 0 4px var(--accent-shade-1);
|
||||
border-radius: 10px;
|
||||
}
|
||||
form .tier-radio:checked + label::after {
|
||||
content: url(/assets/images/check.svg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: var(--accent-shade-1);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
right: -16px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
label.tier {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
align-items: center;
|
||||
padding-top: calc(50px + 1rem);
|
||||
background: var(--bg-shade-3);
|
||||
cursor: pointer;
|
||||
transition: all 150ms;
|
||||
margin-top: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
label.tier p {
|
||||
margin: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
label.tier .tier-thumbnail {
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
z-index: 2;
|
||||
background: var(--bg-shade-4);
|
||||
}
|
||||
form .tier-radio:checked + label .tier-thumbnail::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: inset 0 0 0 4px var(--accent-shade-1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
label.tier .tier-text {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
label.tier .tier-name {
|
||||
color: var(--text-shade-3);
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
label.tier .tier-perks {
|
||||
text-align: left;
|
||||
width: 70%;
|
||||
margin: 24px auto 48px;
|
||||
}
|
||||
label.tier .tier-perks div {
|
||||
display: grid;
|
||||
grid-template-columns: 16px auto;
|
||||
gap: 8px;
|
||||
}
|
||||
label.tier .tier-perks svg {
|
||||
stroke-width: 5px;
|
||||
stroke: var(--green-shade-1);
|
||||
stroke-linecap: square;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: top;
|
||||
margin-top: 0.5ex;
|
||||
}
|
||||
|
||||
label.tier p.price {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--bg-shade-4);
|
||||
margin: 0;
|
||||
padding: 1.5rem 1rem;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 0 10px 10px;
|
||||
}
|
||||
label.tier p.price span {
|
||||
font-size: 2rem;
|
||||
color: var(--text-shade-3);
|
||||
font-weight: bold;
|
||||
margin-right: 0.5ch;
|
||||
}
|
||||
|
||||
form .button-wrapper {
|
||||
grid-column: 2 / span 1;
|
||||
position: relative;
|
||||
margin-top: 24px;
|
||||
}
|
||||
button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: block;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
font-size: 1rem;
|
||||
height: fit-content;
|
||||
|
||||
background: var(--accent-shade-0);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
color: var(--text-shade-3);
|
||||
width: 100%;
|
||||
|
||||
transition: filter 300ms;
|
||||
pointer-events: all;
|
||||
cursor: pointer;
|
||||
filter: none;
|
||||
}
|
||||
form button.disabled {
|
||||
pointer-events: none;
|
||||
filter: brightness(0.75) saturate(0.75); /* not using opacity here 'cause in the mobile layout you would see the cards under it */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
form button.unsubscribe {
|
||||
position: relative;
|
||||
background: none;
|
||||
color: var(--text-shade-1);
|
||||
margin-top: 12px;
|
||||
padding: 0;
|
||||
}
|
||||
form button.unsubscribe.hidden {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
form button.unsubscribe:hover {
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.account-form-wrapper {
|
||||
width: min(500px, 100%);
|
||||
margin-bottom: 172px;
|
||||
}
|
||||
|
||||
form {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2.4rem;
|
||||
}
|
||||
|
||||
form button {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
form .button-wrapper {
|
||||
grid-column: 1 / span 1;
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
width: min(500px, 90%);
|
||||
z-index: 5;
|
||||
}
|
||||
form .button-wrapper::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -24px;
|
||||
left: -100vw;
|
||||
width: 200vw;
|
||||
height: 300%;
|
||||
background: var(--bg-shade-0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 380px) {
|
||||
label.tier .tier-perks {
|
||||
width: 80%;
|
||||
}
|
||||
.back-arrow {
|
||||
padding: 6px;
|
||||
}
|
||||
.back-arrow span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
const onlineFilesModal = document.querySelector('.modal-wrapper#onlinefiles');
|
||||
const onlineFilesModalButtonConfirm = document.getElementById('onlineFilesConfirmButton');
|
||||
const onlineFilesModalButtonClose = document.getElementById('onlineFilesCloseButton');
|
||||
const onlineFilesModalPasswordInput = document.getElementById('password');
|
||||
|
||||
const editSettingsModal = document.querySelector('.modal-wrapper#edit-settings');
|
||||
const editSettingsModalButtonClose = document.getElementById('editSettingsCloseButton');
|
||||
|
||||
document.getElementById('download-cemu-files')?.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
|
||||
onlineFilesModal.classList.remove('hidden');
|
||||
});
|
||||
|
||||
onlineFilesModalButtonConfirm?.addEventListener('click', () => {
|
||||
fetch('/account/online-files', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
password: onlineFilesModalPasswordInput.value
|
||||
})
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(blob => URL.createObjectURL(blob))
|
||||
.then(blobUrl => {
|
||||
const a = document.createElement('a');
|
||||
a.href = blobUrl;
|
||||
a.setAttribute('download', 'Cemu Pretendo Online Files.zip');
|
||||
a.click();
|
||||
|
||||
onlineFilesModal.classList.add('hidden');
|
||||
})
|
||||
.catch(console.log);
|
||||
});
|
||||
|
||||
onlineFilesModalButtonClose?.addEventListener('click', () => {
|
||||
onlineFilesModal.classList.add('hidden');
|
||||
});
|
||||
|
||||
editSettingsModalButtonClose?.addEventListener('click', () => {
|
||||
editSettingsModal.classList.add('hidden');
|
||||
});
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
if (event.target.classList.contains('edit')) {
|
||||
event.preventDefault();
|
||||
|
||||
editSettingsModal.classList.remove('hidden');
|
||||
}
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
const openSidebarBtn = document.querySelector('#openSidebar');
|
||||
const content = document.querySelector('div.content');
|
||||
openSidebarBtn.addEventListener('click', function() {
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
sidebar.classList.toggle('open');
|
||||
content.classList.toggle('open-sidebar');
|
||||
});
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
/* eslint-disable no-undef, no-unused-vars */
|
||||
|
||||
const header = document.querySelector('header');
|
||||
const dropdownButtonWrapper = document.querySelector('.dropdown-button-wrapper');
|
||||
const dropdown = document.querySelector('header div.dropdown');
|
||||
const allDropdownButtons = document.querySelectorAll('button.dropdown-button');
|
||||
const desktopDropdownBtns = document.querySelectorAll('header .dropdown-button-wrapper button.dropdown-button');
|
||||
const mobileDropdownBtn = document.querySelector('.dropdown-button#mobile-button');
|
||||
let dropdownContent;
|
||||
|
||||
function isDropdownOpen() {
|
||||
return header.classList.contains('dropdown-active');
|
||||
}
|
||||
|
||||
function closeDropdown() {
|
||||
dropdown.style.height = '0';
|
||||
header.classList.remove('dropdown-active');
|
||||
// deselect all buttons
|
||||
allDropdownButtons.forEach((button) => {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
}
|
||||
window.addEventListener('resize', () => {
|
||||
if (isDropdownOpen() && dropdownContent) {
|
||||
// set the dropdown height to the height of the content
|
||||
dropdown.style.height = `${dropdownContent.offsetHeight}px`;
|
||||
}
|
||||
});
|
||||
|
||||
function navbarDropdownHandler(buttonID) {
|
||||
const allDropdownContents = dropdown.querySelectorAll('div.dropdown-content');
|
||||
dropdownContent = document.querySelector(`.dropdown-content#${buttonID}-dropdown-content`);
|
||||
const dropdownButton = document.querySelector(`button.dropdown-button#${buttonID}-button`);
|
||||
const dropdownArrow = document.querySelector('.dropdown-arrow#navbar-dropdown-arrow');
|
||||
|
||||
// if on mobile, reclicking the button should close the dropdown
|
||||
if (buttonID === 'mobile' && dropdownButton.classList.contains('active')) {
|
||||
closeDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// hide all contents
|
||||
allDropdownContents.forEach((content) => {
|
||||
content.classList.remove('show');
|
||||
});
|
||||
// deselect all buttons
|
||||
allDropdownButtons.forEach((button) => {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
// show the content of the clicked button
|
||||
dropdownContent.classList.add('show');
|
||||
// select the clicked button
|
||||
dropdownButton.classList.add('active');
|
||||
// set the dropdown height to the height of the content
|
||||
dropdown.style.height = `${dropdownContent.offsetHeight}px`;
|
||||
|
||||
// move the arrow to the selected button
|
||||
dropdownArrow.style.left = `${dropdownButton.offsetLeft + dropdownButton.offsetWidth / 2 - 5}px`;
|
||||
|
||||
// dim the rest of the page
|
||||
header.classList.add('dropdown-active');
|
||||
}
|
||||
|
||||
const dropdownAnchors = document.querySelectorAll('.dropdown-content a');
|
||||
dropdownAnchors.forEach((a) => {
|
||||
a.addEventListener('click', () => {
|
||||
closeDropdown();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// make the header background transparent if near the top of the page
|
||||
function makeHeaderBackgroundTransparent() {
|
||||
if(window.pageYOffset < 100) {
|
||||
header.classList.add('transparent');
|
||||
} else {
|
||||
header.classList.remove('transparent');
|
||||
}
|
||||
}
|
||||
makeHeaderBackgroundTransparent();
|
||||
window.addEventListener('scroll', () => {
|
||||
makeHeaderBackgroundTransparent();
|
||||
});
|
||||
|
||||
desktopDropdownBtns.forEach((btn) => {
|
||||
[ 'click', 'mouseover' ].forEach((event) => {
|
||||
btn.addEventListener(event, () => {
|
||||
const id = btn.id.replace('-button', '');
|
||||
navbarDropdownHandler(id);
|
||||
});
|
||||
});
|
||||
});
|
||||
mobileDropdownBtn.addEventListener('click', () => {
|
||||
const id = 'mobile';
|
||||
navbarDropdownHandler(id, true);
|
||||
});
|
||||
|
||||
/* if on desktop: we check if the element the mouse moves to is part of the ignored element (keep the dropdown open) or not (close the dropdown)
|
||||
* if on mobile: do nothing
|
||||
*/
|
||||
function dropdownOnMouseLeave(e, ignoredElement) {
|
||||
if (window.innerWidth > 900) {
|
||||
const targetElement = e.relatedTarget || e.toElement;
|
||||
if (targetElement !== ignoredElement && !ignoredElement.contains(targetElement)) {
|
||||
closeDropdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
dropdownButtonWrapper.addEventListener('mouseleave', (e) => {
|
||||
dropdownOnMouseLeave(e, dropdown);
|
||||
});
|
||||
dropdown.addEventListener('mouseleave', (e) => {
|
||||
dropdownOnMouseLeave(e, dropdownButtonWrapper);
|
||||
});
|
||||
|
||||
// Account widget handler
|
||||
const userWidgetToggle = document.querySelector('.user-widget-toggle') ;
|
||||
const userWidget = document.querySelector('.user-widget');
|
||||
|
||||
// Open widget on click, close locale dropdown
|
||||
userWidgetToggle?.addEventListener('click', () => {
|
||||
userWidget.classList.toggle('active');
|
||||
localeOptionsContainer.classList.toggle('active');
|
||||
localeDropdownToggle.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Locale dropdown handler
|
||||
function localeDropdownHandler(selectedLocale) {
|
||||
document.cookie = `preferredLocale=${selectedLocale};max-age=31536000`;
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
const localeDropdown = document.querySelector(
|
||||
'.locale-dropdown[data-dropdown]'
|
||||
);
|
||||
const localeDropdownOptions = document.querySelectorAll(
|
||||
'.locale-dropdown[data-dropdown] .options-container'
|
||||
);
|
||||
const localeDropdownToggle = document.querySelector('.locale-dropdown-toggle');
|
||||
|
||||
const localeOptionsContainer = localeDropdown.querySelector('.options-container');
|
||||
const localeOptionsList = localeDropdown.querySelectorAll('.option');
|
||||
|
||||
// click dropdown element will open dropdown
|
||||
localeDropdownToggle.addEventListener('click', () => {
|
||||
localeOptionsContainer.classList.toggle('active');
|
||||
localeDropdownToggle.classList.toggle('active');
|
||||
});
|
||||
|
||||
// clicking on any option will close dropdown and change value
|
||||
localeOptionsList.forEach((option) => {
|
||||
option.addEventListener('click', () => {
|
||||
localeDropdownToggle.classList.remove('active');
|
||||
localeOptionsContainer.classList.remove('active');
|
||||
const selectedLocale = option.querySelector('label').getAttribute('for');
|
||||
localeDropdownHandler(selectedLocale);
|
||||
});
|
||||
});
|
||||
|
||||
// close all dropdowns on scroll
|
||||
document.addEventListener('scroll', () => {
|
||||
localeDropdownOptions.forEach((el) => el.classList.remove('active'));
|
||||
localeDropdownToggle.classList.remove('active');
|
||||
|
||||
userWidget?.classList.remove('active');
|
||||
});
|
||||
|
||||
// click outside of dropdown will close all dropdowns
|
||||
document.addEventListener('click', (e) => {
|
||||
const targetElement = e.target;
|
||||
|
||||
let found = false;
|
||||
if (
|
||||
localeDropdown == targetElement ||
|
||||
localeDropdown?.contains(targetElement)
|
||||
) {
|
||||
found = true;
|
||||
userWidget?.classList.remove('active');
|
||||
}
|
||||
|
||||
if (
|
||||
userWidget == targetElement ||
|
||||
userWidget?.contains(targetElement) ||
|
||||
userWidgetToggle == targetElement ||
|
||||
userWidgetToggle?.contains(targetElement)
|
||||
) {
|
||||
found = true;
|
||||
localeDropdownToggle.classList.remove('active');
|
||||
localeOptionsContainer.classList.remove('active');
|
||||
}
|
||||
|
||||
if (found) return;
|
||||
|
||||
// click outside of dropdowns
|
||||
userWidget?.classList.remove('active');
|
||||
localeDropdownToggle.classList.remove('active');
|
||||
localeOptionsContainer.classList.remove('active');
|
||||
});
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/* eslint-disable no-undef */
|
||||
const form = document.querySelector('.localization-form');
|
||||
|
||||
form.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.location.href=`https://pretendo-locale-tester.herokuapp.com/?url=${e.target[0].value}`;
|
||||
});
|
||||
|
|
@ -1,506 +0,0 @@
|
|||
/**
|
||||
* Compilation note:
|
||||
* This file gets automatically bundled with browserify when running the start script.
|
||||
* This also means that after any update you're gonna need to restart the server to see any changes.
|
||||
*
|
||||
* browserify is needed for the use of require() in the browser
|
||||
*/
|
||||
const Mii = require('mii-js');
|
||||
const config = require('../../../config.json');
|
||||
const newMiiData = 'AwAAQOlVognnx0GC2qjhdwOzuI0n2QAAAGBzAHQAZQB2AGUAAAAAAAAAAAAAAEBAAAAhAQJoRBgmNEYUgRIXaA0AACkAUkhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAANeC';
|
||||
|
||||
// Prevent the user from reloading or leaving the page
|
||||
window.onbeforeunload = function (e) {
|
||||
e?.preventDefault();
|
||||
e.returnValue = '';
|
||||
};
|
||||
|
||||
// this makes it so the canvas fits in the target element
|
||||
function setCanvasScale() {
|
||||
let targetX;
|
||||
let targetY;
|
||||
|
||||
if (window.innerWidth <= 1080) {
|
||||
const canvasWrapper = document.querySelector('.canvas-wrapper');
|
||||
|
||||
targetX = canvasWrapper.offsetWidth;
|
||||
targetY = canvasWrapper.offsetHeight;
|
||||
} else {
|
||||
targetX = window.innerWidth * 0.9;
|
||||
targetY = window.innerHeight * 0.9;
|
||||
}
|
||||
|
||||
const canvas = document.querySelector('canvas#miiCanvas');
|
||||
const XScale = targetX / canvas.width;
|
||||
const YScale = targetY / canvas.height;
|
||||
canvas.style.transform = `scale(${Math.min(XScale, YScale)})`;
|
||||
}
|
||||
|
||||
setCanvasScale();
|
||||
window.addEventListener('resize', () => {
|
||||
setCanvasScale();
|
||||
});
|
||||
|
||||
let mii; // global mii object
|
||||
|
||||
// this initalizes a mii for editing
|
||||
// returns if mii data was parsed successfully
|
||||
function initializeMiiData(encodedUserMiiData) {
|
||||
console.group('Initalizing Mii data');
|
||||
console.log('encoded mii data:', encodedUserMiiData);
|
||||
|
||||
// We initialize the Mii object
|
||||
try {
|
||||
console.log('Attempting to parse mii data');
|
||||
mii = new Mii(Buffer.from(encodedUserMiiData, 'base64'));
|
||||
} catch (err) {
|
||||
console.error('failed to decode mii data', err);
|
||||
console.groupEnd();
|
||||
return false;
|
||||
}
|
||||
|
||||
// We set the img sources for the unedited miis in the save animation
|
||||
console.log('grabbing rendered miis for later use');
|
||||
const miiStudioNeutralUrl = mii.studioUrl({
|
||||
width: 512,
|
||||
bgColor: '13173300',
|
||||
});
|
||||
const miiStudioSorrowUrl = mii.studioUrl({
|
||||
width: 512,
|
||||
bgColor: '13173300',
|
||||
expression: 'sorrow',
|
||||
});
|
||||
document.querySelector('.mii-comparison img.old-mii').src = miiStudioNeutralUrl;
|
||||
document.querySelector('.mii-comparison.confirmed img.old-mii').src = miiStudioSorrowUrl;
|
||||
console.log('initialization complete');
|
||||
console.groupEnd();
|
||||
return true;
|
||||
}
|
||||
|
||||
// The Mii data is stored in a script tag in the HTML, so we can just grab it and then remove the element
|
||||
const encodedUserMiiData = document.querySelector(
|
||||
'script#encodedUserMiiData'
|
||||
).textContent;
|
||||
document.querySelector('script#encodedUserMiiData').remove();
|
||||
|
||||
// is valid mii data
|
||||
const validMiiData = initializeMiiData(encodedUserMiiData);
|
||||
if (!validMiiData) {
|
||||
const shouldContinue = window.confirm('Found corrupted mii data, want to continue with a new Mii?');
|
||||
if (!shouldContinue)
|
||||
window.location.assign('/account');
|
||||
initializeMiiData(newMiiData);
|
||||
}
|
||||
|
||||
// we keeep the images here so we can cache them when we need to change the build/height
|
||||
const miiFaceImg = new Image();
|
||||
const baldMiiFaceImg = new Image();
|
||||
const miiBodyImg = new Image();
|
||||
|
||||
// Initial mii render
|
||||
renderMii();
|
||||
// This function renders the Mii on the canvas
|
||||
function renderMii(heightOverride, buildOverride) {
|
||||
const canvas = document.querySelector('canvas#miiCanvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const height = heightOverride || mii.height;
|
||||
const build = buildOverride || mii.build;
|
||||
|
||||
// if there isn't an override or the images haven't been cached, we load the images
|
||||
if ((!heightOverride && !buildOverride) || !miiFaceImg.src || !baldMiiFaceImg.src || !miiBodyImg.src) {
|
||||
canvas.style.filter = 'blur(4px) brightness(70%)';
|
||||
|
||||
// we create a copy of the mii and make it bald
|
||||
const baldMii = Object.create(
|
||||
Object.getPrototypeOf(mii),
|
||||
Object.getOwnPropertyDescriptors(mii)
|
||||
);
|
||||
baldMii.hairType = 30;
|
||||
baldMiiFaceImg.src = baldMii.studioUrl({
|
||||
width: 512,
|
||||
bgColor: '13173300',
|
||||
type: 'face_only',
|
||||
});
|
||||
miiFaceImg.src = mii.studioUrl({
|
||||
width: 512,
|
||||
bgColor: '13173300',
|
||||
type: 'face_only',
|
||||
});
|
||||
miiBodyImg.src = mii.studioAssetUrlBody();
|
||||
}
|
||||
|
||||
// misc calculations
|
||||
const bodyWidth = (build * 1.7 + 220) * (0.003 * height + 0.6);
|
||||
const bodyHeight = height * 3.5 + 227;
|
||||
const bodyXPos = (canvas.width - bodyWidth) / 2;
|
||||
const bodyYPos = canvas.height - bodyHeight;
|
||||
const headYPos = bodyYPos - 408;
|
||||
|
||||
// we make sure every image is loaded before rendering
|
||||
if (miiFaceImg.complete) {
|
||||
onMiiFaceImgLoad();
|
||||
} else {
|
||||
miiFaceImg.onload = () => {
|
||||
onMiiFaceImgLoad();
|
||||
};
|
||||
}
|
||||
function onMiiFaceImgLoad() {
|
||||
if (miiBodyImg.complete) {
|
||||
onBodyImgLoad();
|
||||
} else {
|
||||
miiBodyImg.onload = () => {
|
||||
onBodyImgLoad();
|
||||
};
|
||||
}
|
||||
}
|
||||
function onBodyImgLoad() {
|
||||
if (baldMiiFaceImg.complete) {
|
||||
onBaldMiiFaceImgLoad();
|
||||
} else {
|
||||
baldMiiFaceImg.onload = () => {
|
||||
onBaldMiiFaceImgLoad();
|
||||
};
|
||||
}
|
||||
}
|
||||
function onBaldMiiFaceImgLoad() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(miiFaceImg, 0, headYPos);
|
||||
ctx.drawImage(miiBodyImg, bodyXPos, bodyYPos, bodyWidth, bodyHeight);
|
||||
|
||||
// we draw a portion of the bald mii on top of the normal mii to hide the mii's neck (see https://i.imgur.com/U0fpkwi.png)
|
||||
ctx.drawImage(baldMiiFaceImg, 186, 384, 140, 120, 186, headYPos + 384, 140, 120);
|
||||
canvas.style.filter = '';
|
||||
}
|
||||
|
||||
if (!heightOverride && !buildOverride) {
|
||||
const faceMiiStudioUrl = mii.studioUrl({
|
||||
width: 512,
|
||||
bgColor: '13173300',
|
||||
});
|
||||
|
||||
const faceMiiStudioSmileUrl = mii.studioUrl({
|
||||
width: 512,
|
||||
bgColor: '13173300',
|
||||
expression: 'smile'
|
||||
});
|
||||
|
||||
// sets the new mii in the save tab to the new mii
|
||||
document.querySelector('.mii-comparison img.new-mii').src = faceMiiStudioUrl;
|
||||
document.querySelector('.mii-comparison.confirmed img.new-mii').src = faceMiiStudioSmileUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// This function updates a prop of the Mii and rerenders it
|
||||
function updateMii(e) {
|
||||
const prop = e.target.name;
|
||||
let value = e.target.value || e.target.defaultValue;
|
||||
|
||||
// if the value comes from a checkbox, we use the checked property
|
||||
if (value === 'on' || value === 'off') {
|
||||
value = e.target.checked;
|
||||
}
|
||||
|
||||
// if the prop is disableSharing, we set the value to the opposite of the current value
|
||||
if (prop === 'disableSharing') {
|
||||
value = !value;
|
||||
}
|
||||
|
||||
// Handle booleans, on/offs and strings
|
||||
if (value === 'true' || value === 'false') {
|
||||
mii[prop] = value === 'true';
|
||||
} else if (value === 'on' || value === 'off') {
|
||||
mii[prop] = value === 'on';
|
||||
} else if (isNaN(parseInt(value))) {
|
||||
mii[prop] = value;
|
||||
} else {
|
||||
mii[prop] = parseInt(value);
|
||||
}
|
||||
|
||||
// if the user is editing the height or the build, we render the mii with the correct override, else we do a straight render
|
||||
if (prop === 'height') {
|
||||
renderMii(value, false);
|
||||
} else if (prop === 'build') {
|
||||
renderMii(false, value);
|
||||
} else {
|
||||
renderMii();
|
||||
console.log(mii);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCalendar(e) {
|
||||
const valueArray = e.target.value.split('-');
|
||||
const day = valueArray[2];
|
||||
const month = valueArray[1];
|
||||
|
||||
mii.birthDay = parseInt(day);
|
||||
mii.birthMonth = parseInt(month);
|
||||
}
|
||||
|
||||
function preventEmpty(e) {
|
||||
if (e.target.value !== '') return;
|
||||
|
||||
e.target.value = e.target.defaultValue;
|
||||
}
|
||||
|
||||
document.querySelectorAll('fieldset').forEach((fieldset) => {
|
||||
fieldset.addEventListener('change', updateMii);
|
||||
});
|
||||
document.querySelectorAll('input[type=\'range\']').forEach((input) => {
|
||||
input.addEventListener('input', updateMii);
|
||||
});
|
||||
document
|
||||
.querySelectorAll('input[type=\'text\'], input[type=\'number\']')
|
||||
.forEach((input) => {
|
||||
input.addEventListener('blur', preventEmpty);
|
||||
});
|
||||
document
|
||||
.querySelector('input[type=\'date\']#birthDate')
|
||||
.addEventListener('change', handleCalendar);
|
||||
|
||||
// FORM
|
||||
|
||||
// Here we preselect the options corresponding to the Mii's current values
|
||||
[
|
||||
'faceType',
|
||||
'skinColor',
|
||||
'makeupType',
|
||||
'wrinklesType',
|
||||
'hairType',
|
||||
'hairColor',
|
||||
'eyebrowType',
|
||||
'eyebrowColor',
|
||||
'eyeType',
|
||||
'eyeColor',
|
||||
'noseType',
|
||||
'mouthType',
|
||||
'mouthColor',
|
||||
'glassesType',
|
||||
'glassesColor',
|
||||
'beardType',
|
||||
'facialHairColor',
|
||||
'mustacheType',
|
||||
'moleEnabled',
|
||||
'gender',
|
||||
'favoriteColor',
|
||||
].forEach((prop) => {
|
||||
const el = document.querySelector(`#${prop}${mii[prop]}`);
|
||||
if (el) {
|
||||
el.checked = true;
|
||||
}
|
||||
console.log(`[info] preselected value for ${prop}`);
|
||||
});
|
||||
|
||||
['favorite', 'allowCopying'].forEach((prop) => {
|
||||
const el = document.querySelector(`#${prop}`);
|
||||
if (el) {
|
||||
el.checked = mii[prop];
|
||||
}
|
||||
console.log(`[info] preselected value for ${prop}`);
|
||||
});
|
||||
|
||||
document.querySelector('#disableSharing').checked = !mii.disableSharing;
|
||||
console.log('[info] preselected value for disableSharing');
|
||||
|
||||
[
|
||||
'eyebrowYPosition',
|
||||
'eyebrowSpacing',
|
||||
'eyebrowRotation',
|
||||
'eyebrowScale',
|
||||
'eyebrowVerticalStretch',
|
||||
'eyeYPosition',
|
||||
'eyeSpacing',
|
||||
'eyeRotation',
|
||||
'eyeScale',
|
||||
'eyeVerticalStretch',
|
||||
'noseYPosition',
|
||||
'noseScale',
|
||||
'mouthYPosition',
|
||||
'mouthScale',
|
||||
'mouthHorizontalStretch',
|
||||
'glassesYPosition',
|
||||
'glassesScale',
|
||||
'mustacheYPosition',
|
||||
'mustacheScale',
|
||||
'moleYPosition',
|
||||
'moleXPosition',
|
||||
'moleScale',
|
||||
'height',
|
||||
'build',
|
||||
'miiName',
|
||||
'creatorName',
|
||||
].forEach((prop) => {
|
||||
document.querySelector(`#${prop}`).value = mii[prop];
|
||||
document.querySelector(`#${prop}`).defaultValue = mii[prop];
|
||||
console.log(`[info] preselected value for ${prop}`);
|
||||
});
|
||||
|
||||
const paddedBirthDay = mii.birthDay.toString().padStart(2, '0');
|
||||
const paddedBirthMonth = mii.birthMonth.toString().padStart(2, '0');
|
||||
document.querySelector(
|
||||
'input[type=\'date\']#birthDate'
|
||||
).value = `2024-${paddedBirthMonth}-${paddedBirthDay}`;
|
||||
console.log('[info] preselected value for birthMonth && birthDay');
|
||||
|
||||
// TABS, SUBTABS, AND ALL THE INHERENT JANK
|
||||
|
||||
function openTab(e, tabType) {
|
||||
e.preventDefault();
|
||||
|
||||
// Deselect all subpages
|
||||
document
|
||||
.querySelectorAll('.subtab.has-subpages .subpage.active')
|
||||
.forEach((el) => {
|
||||
el.classList?.remove('active');
|
||||
});
|
||||
document.querySelectorAll('.subtab.active').forEach((el) => {
|
||||
el.classList?.remove('active');
|
||||
});
|
||||
|
||||
const buttonReplacement =
|
||||
tabType.charAt(0).toUpperCase() + tabType.slice(1);
|
||||
|
||||
document.querySelectorAll(`.${tabType}.active`).forEach((el) => {
|
||||
el?.classList?.remove('active');
|
||||
});
|
||||
document.querySelectorAll(`.${tabType}btn.active`).forEach((el) => {
|
||||
el?.classList?.remove('active');
|
||||
});
|
||||
|
||||
const elementID = e.target?.id;
|
||||
document.querySelector(`#${elementID}`).classList.add('active');
|
||||
const selectedID = elementID
|
||||
.replace('SubButton', '')
|
||||
.replace('Button', buttonReplacement);
|
||||
document.querySelector(`#${selectedID}`).classList.add('active');
|
||||
|
||||
if (tabType === 'tab') {
|
||||
// Click the first subtab button, if there is one
|
||||
document.querySelector(`#${selectedID} .subtabbtn`)?.click();
|
||||
}
|
||||
|
||||
setCanvasScale();
|
||||
|
||||
// We hide all subpages
|
||||
document.querySelectorAll('.subpage').forEach((el) => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
|
||||
// Selects the first subpage if there is one
|
||||
document.querySelector(`#${selectedID} .subpage`)?.classList?.add('active');
|
||||
}
|
||||
|
||||
// Here we bind all of the functions to the corresponding buttons
|
||||
document.querySelectorAll('.tabs button.tabbtn').forEach((el) => {
|
||||
el.addEventListener('click', (e) => openTab(e, 'tab'));
|
||||
});
|
||||
document.querySelectorAll('.subtabs button.subtabbtn').forEach((el) => {
|
||||
el.addEventListener('click', (e) => openTab(e, 'subtab'));
|
||||
});
|
||||
|
||||
// SUBPAGES
|
||||
|
||||
function paginationHandler(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// We hide all subpages
|
||||
document.querySelectorAll('.subpage').forEach((el) => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
|
||||
// We get the current subpage
|
||||
const currentPageIndex = parseInt(
|
||||
e.target.classList[2].replace('index-', '')
|
||||
);
|
||||
let newPageIndex = currentPageIndex;
|
||||
|
||||
// We calculate the new subpage
|
||||
if (e.target.classList.contains('next')) {
|
||||
newPageIndex += 1;
|
||||
} else {
|
||||
newPageIndex -= 1;
|
||||
}
|
||||
|
||||
// We find the new subpage and activate it
|
||||
e.target.parentNode.parentNode.parentNode.children[
|
||||
newPageIndex
|
||||
].classList.add('active');
|
||||
}
|
||||
|
||||
// This adds 1 to the rendered page indexes to make them start from 1 instead of 0
|
||||
document.querySelectorAll('span.current-page-index').forEach((el) => {
|
||||
el.textContent = parseInt(el.textContent) + 1;
|
||||
});
|
||||
|
||||
// Here we bind the functions to the corresponding buttons
|
||||
document.querySelectorAll('button.page-btn').forEach((el) => {
|
||||
el.addEventListener('click', paginationHandler);
|
||||
});
|
||||
|
||||
// mii saving business (animation jank & actual saving)
|
||||
document
|
||||
.querySelector('#saveTab #saveButton')
|
||||
.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
document
|
||||
.querySelector('#saveTab #saveButton')
|
||||
.classList.add('inactive', 'fade-out');
|
||||
document.querySelector('.tabs').style.pointerEvents = 'none';
|
||||
document.querySelector('.mii-comparison.confirmed').style.opacity = 1;
|
||||
document
|
||||
.querySelector('#saveTab p.save-prompt')
|
||||
.classList.add('fade-out');
|
||||
|
||||
setTimeout(() => {
|
||||
document.querySelector(
|
||||
'.mii-comparison.unconfirmed'
|
||||
).style.opacity = 0;
|
||||
}, 500);
|
||||
|
||||
setTimeout(() => {
|
||||
document
|
||||
.querySelector('.mii-comparison.confirmed .old-mii')
|
||||
.classList.add('fade-out');
|
||||
document
|
||||
.querySelector('.mii-comparison.confirmed svg')
|
||||
.classList.add('fade-out');
|
||||
}, 1500);
|
||||
|
||||
setTimeout(() => {
|
||||
document
|
||||
.querySelector('.mii-comparison.confirmed .new-mii-wrapper')
|
||||
.classList.add('centered-mii-img');
|
||||
}, 2000);
|
||||
|
||||
try {
|
||||
const miiData = mii.encode().toString('base64');
|
||||
const tokenType = document.cookie.split('; ').find(row => row.startsWith('token_type=')).split('=')[1];
|
||||
const accessToken = document.cookie.split('; ').find(row => row.startsWith('access_token=')).split('=')[1];
|
||||
|
||||
fetch(`${config.api_base}/v1/user`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `${tokenType} ${decodeURIComponent(accessToken)}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mii: {
|
||||
name: mii.miiName,
|
||||
primary: 'Y',
|
||||
data: miiData,
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(({ status }) => {
|
||||
if (status === 200) {
|
||||
window.onbeforeunload = null;
|
||||
window.location.assign('/account');
|
||||
}
|
||||
})
|
||||
.catch(console.log);
|
||||
// CHECK IF MII IS VALID SERVERSIDE
|
||||
} catch (error) {
|
||||
alert(error);
|
||||
}
|
||||
});
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/* eslint-disable no-undef */
|
||||
document.querySelectorAll('.feature-list-wrapper').forEach(progressListElement => {
|
||||
|
||||
// Find and generate all relevant data
|
||||
const percentageOverride = progressListElement.querySelector('canvas.percentage-chart').dataset.percentageoverride;
|
||||
const allFeatureNodes = progressListElement.querySelectorAll('.feature');
|
||||
const allDoneFeatureNodes = progressListElement.querySelectorAll('.feature .done');
|
||||
const allStartedFeatureNodes = progressListElement.querySelectorAll('.feature .ongoing');
|
||||
|
||||
// Use percentage override data attribute if present, else calculate
|
||||
const progressPercentage = Math.round(percentageOverride) || Math.round(Math.min((allDoneFeatureNodes.length + allStartedFeatureNodes.length * 0.5) / allFeatureNodes.length * 100, 100)) || 0;
|
||||
const remainingPercentage = 100 - progressPercentage;
|
||||
|
||||
// Set inner paragraph
|
||||
progressListElement.querySelectorAll('.percentage-label').forEach(p => {
|
||||
if (progressPercentage === 0) {
|
||||
p.innerText = progressPercentage.toString() + '%';
|
||||
} else {
|
||||
p.innerText = progressPercentage.toString().padStart(2, '0') + '%';
|
||||
}
|
||||
});
|
||||
|
||||
// Create chart
|
||||
const data = [progressPercentage, remainingPercentage];
|
||||
Chart.defaults.plugins.legend = {
|
||||
display: false
|
||||
};
|
||||
Chart.defaults.plugins.tooltip = {
|
||||
enabled: false
|
||||
};
|
||||
|
||||
const isInBrightCard = !!progressListElement.closest('.right.sect');
|
||||
|
||||
new Chart(progressListElement.querySelector('canvas'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Done', 'Todo'],
|
||||
datasets: [
|
||||
{
|
||||
data,
|
||||
backgroundColor: isInBrightCard ? ['white', 'rgba(195, 178, 227, 0.5)'] : ['#9D6FF3', '#31365A']
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
elements: {
|
||||
arc: {
|
||||
borderWidth: 0
|
||||
}
|
||||
},
|
||||
cutout: '70%'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
const buttons = {
|
||||
submit: document.getElementById('submitButton'),
|
||||
unsubModal: {
|
||||
show: document.getElementById('unsubModalShowButton'),
|
||||
close: document.getElementById('unsubModalCloseButton'),
|
||||
confirm: document.getElementById('unsubModalConfirmButton'),
|
||||
},
|
||||
switchTierModal: {
|
||||
show: document.getElementById('switchTierShowButton'),
|
||||
close: document.getElementById('switchTierCloseButton'),
|
||||
confirm: document.getElementById('switchTierConfirmButton'),
|
||||
},
|
||||
};
|
||||
|
||||
const currentTierID = document.querySelector('form').dataset.currentTier || undefined;
|
||||
|
||||
const currentTierElement = document.querySelector(`#${currentTierID}`) || undefined;
|
||||
|
||||
// if the condition is met, we disable the submit button and enable the unsubscribe button
|
||||
function conditionalSubmitButton(condition, target) {
|
||||
if (condition) {
|
||||
buttons.submit.innerText = 'Already subscribed to this tier';
|
||||
buttons.unsubModal.show.innerText = `Unsubscribe from ${currentTierElement.dataset.tierName}`;
|
||||
buttons.submit.disabled = true;
|
||||
buttons.submit.classList.add('disabled');
|
||||
buttons.unsubModal.show.classList.remove('hidden');
|
||||
} else {
|
||||
buttons.submit.classList.remove('disabled');
|
||||
buttons.unsubModal.show.classList.add('hidden');
|
||||
buttons.submit.disabled = false;
|
||||
buttons.submit.innerText = `Subscribe to ${target.dataset.tierName}`;
|
||||
}
|
||||
}
|
||||
|
||||
function submitForm(cancel) {
|
||||
const form = document.querySelector('form');
|
||||
|
||||
if (cancel) {
|
||||
form.action = '/account/stripe/unsubscribe';
|
||||
} else {
|
||||
const selectedTier = form.querySelector('input[type="radio"]:checked').value;
|
||||
form.action = `/account/stripe/checkout/${selectedTier}`;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
|
||||
// If the currect tier exists, select it from the list and disable the submit button.
|
||||
if (currentTierElement) {
|
||||
currentTierElement.click();
|
||||
conditionalSubmitButton(true);
|
||||
}
|
||||
|
||||
// If a tier is selected, conditionally enable the submit button.
|
||||
document.querySelector('form').addEventListener('change', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// If the selected tier is the current tier, set the button to disabled. Else we enable the button
|
||||
conditionalSubmitButton(e.target.value === currentTierElement?.value, e.target);
|
||||
});
|
||||
|
||||
// handle the submit button
|
||||
buttons.submit.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// If the user is already subscribed to another tier, we show the confirm modal, else if this is a new subscription we submit the form.
|
||||
if (currentTierElement) {
|
||||
const oldTierNameSpan = document.querySelector('#switchtier .modal-caption span.oldtier');
|
||||
const newTierNameSpan = document.querySelector('#switchtier .modal-caption span.newtier');
|
||||
oldTierNameSpan.innerText = currentTierElement.dataset.tierName;
|
||||
newTierNameSpan.innerText = document.querySelector('input[name="tier"]:checked').dataset.tierName;
|
||||
|
||||
document.body.classList.add('modal-open');
|
||||
document.querySelector('.modal-wrapper#switchtier').classList.remove('hidden');
|
||||
} else {
|
||||
submitForm();
|
||||
}
|
||||
});
|
||||
|
||||
buttons.unsubModal.show.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const tierNameSpan = document.querySelector('#unsub .modal-caption span');
|
||||
tierNameSpan.innerText = currentTierElement.dataset.tierName;
|
||||
|
||||
// Show the unsubscribe modal
|
||||
document.body.classList.add('modal-open');
|
||||
document.querySelector('.modal-wrapper#unsub').classList.remove('hidden');
|
||||
});
|
||||
buttons.unsubModal.close.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Hide the unsubscribe modal
|
||||
document.body.classList.remove('modal-open');
|
||||
document.querySelector('.modal-wrapper#unsub').classList.add('hidden');
|
||||
});
|
||||
buttons.unsubModal.confirm.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
submitForm(true);
|
||||
});
|
||||
|
||||
buttons.switchTierModal.close.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Hide the switch tier modal
|
||||
document.body.classList.remove('modal-open');
|
||||
document.querySelector('.modal-wrapper#switchtier').classList.add('hidden');
|
||||
});
|
||||
buttons.switchTierModal.confirm.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
submitForm(false);
|
||||
});
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "Pretendo Network",
|
||||
"short_name": "Pretendo Network",
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://pretendo.network/assets/images/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "https://pretendo.network/assets/images/icons/android-chrome-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#1b1f3b",
|
||||
"background_color": "#1b1f3b",
|
||||
"display": "standalone"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 592 KiB |
185
src/cache.js
185
src/cache.js
|
|
@ -1,185 +0,0 @@
|
|||
const { GraphQLClient, gql } = require('graphql-request');
|
||||
const Stripe = require('stripe');
|
||||
const config = require('../config.json');
|
||||
|
||||
const graphql = new GraphQLClient('https://api.github.com/graphql', {
|
||||
headers: {
|
||||
Authorization: `bearer ${config.github.graphql_token}`,
|
||||
}
|
||||
});
|
||||
const stripe = new Stripe(config.stripe.secret_key);
|
||||
|
||||
const getProjectCards = gql`
|
||||
fragment ItemContent on Node {
|
||||
__typename
|
||||
... on DraftIssue {
|
||||
title
|
||||
}
|
||||
... on Issue {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
||||
fragment Itemfields on Node {
|
||||
__typename
|
||||
... on ProjectV2ItemFieldSingleSelectValue {
|
||||
name,
|
||||
field {
|
||||
... on ProjectV2SingleSelectField {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query getProjectCards($orgName: String!) {
|
||||
organization(login: $orgName) {
|
||||
projectsV2(first: 100) {
|
||||
nodes {
|
||||
repositories(first: 1) {
|
||||
nodes {
|
||||
url
|
||||
}
|
||||
}
|
||||
title
|
||||
items(first: 100) {
|
||||
nodes {
|
||||
id
|
||||
content {
|
||||
...ItemContent
|
||||
}
|
||||
fieldValues(first: 20) {
|
||||
nodes {
|
||||
...Itemfields
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
let githubProjectsCache;
|
||||
let stripeDonationCache;
|
||||
|
||||
async function getGithubProjectsCache() {
|
||||
if (!githubProjectsCache) {
|
||||
githubProjectsCache = await updateGithubProjectsCache();
|
||||
}
|
||||
|
||||
if (githubProjectsCache.update_time < Date.now() - (1000 * 60 * 60)) {
|
||||
githubProjectsCache = await updateGithubProjectsCache();
|
||||
}
|
||||
|
||||
return githubProjectsCache;
|
||||
}
|
||||
|
||||
function getProgressField(fields) {
|
||||
const found = fields.nodes.find(v => v.field?.name === 'Status');
|
||||
return found?.name ?? undefined;
|
||||
}
|
||||
|
||||
async function updateGithubProjectsCache() {
|
||||
const projectsCacheData = {
|
||||
update_time: Date.now(),
|
||||
sections: []
|
||||
};
|
||||
|
||||
const data = await graphql.request(getProjectCards, {
|
||||
orgName: 'PretendoNetwork',
|
||||
});
|
||||
|
||||
const projects = data.organization.projectsV2.nodes;
|
||||
|
||||
for (const project of projects) {
|
||||
if (!project.repositories.nodes[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const extractedData = {
|
||||
title: project.title,
|
||||
url: project.repositories.nodes[0]?.url,
|
||||
cards: {
|
||||
done: [],
|
||||
in_progress: [],
|
||||
todo: []
|
||||
}
|
||||
};
|
||||
|
||||
for (const { content, fieldValues } of project.items.nodes) {
|
||||
const progress = getProgressField(fieldValues);
|
||||
|
||||
if (!['DraftIssue'].includes(content.__typename)) {
|
||||
continue; // not a supported card, skip
|
||||
}
|
||||
|
||||
if (!progress) {
|
||||
continue; // entry does not have a status, skip
|
||||
}
|
||||
|
||||
extractedData.cards[progress.toLowerCase().replace(' ', '_')]?.push(content.title);
|
||||
}
|
||||
|
||||
projectsCacheData.sections.push(extractedData);
|
||||
}
|
||||
|
||||
return projectsCacheData;
|
||||
}
|
||||
|
||||
async function getStripeDonationCache() {
|
||||
if (!stripeDonationCache) {
|
||||
stripeDonationCache = await updateStripeDonationCache();
|
||||
}
|
||||
|
||||
if (stripeDonationCache.update_time < Date.now() - (1000 * 60 * 60)) {
|
||||
stripeDonationCache = await updateStripeDonationCache();
|
||||
}
|
||||
|
||||
return stripeDonationCache;
|
||||
}
|
||||
|
||||
async function updateStripeDonationCache() {
|
||||
const donationCache = {
|
||||
update_time: Date.now(),
|
||||
goal: config.stripe.goal_cents,
|
||||
total: 0,
|
||||
donators: 0,
|
||||
completed_real: 0,
|
||||
completed_capped: 0
|
||||
};
|
||||
|
||||
let hasMore = true;
|
||||
let lastId;
|
||||
|
||||
while (hasMore) {
|
||||
const { data: activeSubscriptions, has_more } = await stripe.subscriptions.list({
|
||||
limit: 100,
|
||||
status: 'active',
|
||||
starting_after: lastId
|
||||
});
|
||||
|
||||
donationCache.donators += activeSubscriptions.length;
|
||||
|
||||
for (const subscription of activeSubscriptions) {
|
||||
donationCache.total += subscription.plan.amount;
|
||||
lastId = subscription.id;
|
||||
}
|
||||
|
||||
hasMore = has_more;
|
||||
}
|
||||
|
||||
donationCache.goal_dollars = donationCache.goal / 100;
|
||||
donationCache.total_dollars = donationCache.total / 100;
|
||||
|
||||
donationCache.completed_real = Math.floor((donationCache.total / donationCache.goal) * 100); // real completion amount
|
||||
donationCache.completed_capped = Math.max(0, Math.min(donationCache.completed_real, 100)); // capped at 100
|
||||
|
||||
return donationCache;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getGithubProjectsCache,
|
||||
getStripeDonationCache
|
||||
};
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
const mongoose = require('mongoose');
|
||||
const PNIDSchema = require('./schema/pnid');
|
||||
const config = require('../config.json');
|
||||
|
||||
const accountServerConfig = config.database.account;
|
||||
const { uri, database, options } = accountServerConfig;
|
||||
let accountServerDBConnection;
|
||||
let PNID;
|
||||
|
||||
async function connect() {
|
||||
accountServerDBConnection = await mongoose.createConnection(`${uri}/${database}`, options);
|
||||
accountServerDBConnection.on('error', console.error.bind(console, 'Mongoose connection error:'));
|
||||
accountServerDBConnection.on('close', () => {
|
||||
accountServerDBConnection.removeAllListeners();
|
||||
});
|
||||
|
||||
await accountServerDBConnection.asPromise();
|
||||
|
||||
PNID = accountServerDBConnection.model('PNID', PNIDSchema);
|
||||
|
||||
module.exports.PNID = PNID;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
connect,
|
||||
PNID
|
||||
};
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
{
|
||||
"face": [0, 1, 8, 2, 3, 9, 4, 5, 10, 6, 7, 11],
|
||||
"hairs": [
|
||||
[33, 47, 40, 37, 32, 107, 48, 51, 55, 70, 44, 66],
|
||||
[52, 50, 38, 49, 43, 31, 56, 68, 62, 115, 76, 119],
|
||||
[64, 81, 116, 121, 22, 58, 60, 87, 125, 117, 73, 75],
|
||||
[42, 89, 57, 54, 80, 34, 23, 86, 88, 118, 39, 36],
|
||||
[45, 67, 59, 65, 41, 30, 12, 16, 10, 82, 128, 129],
|
||||
[14, 95, 105, 100, 6, 20, 93, 102, 27, 4, 17, 110],
|
||||
[123, 8, 106, 72, 3, 21, 0, 98, 63, 90, 11, 120],
|
||||
[5, 74, 108, 94, 124, 25, 99, 69, 35, 13, 122, 113],
|
||||
[53, 24, 85, 83, 71, 131, 96, 101, 29, 7, 15, 112],
|
||||
[79, 1, 109, 127, 91, 26, 61, 103, 2, 77, 18, 92],
|
||||
[84, 9, 19, 130, 97, 104, 46, 78, 28, 114, 126, 111]
|
||||
],
|
||||
"eyebrows": [
|
||||
[6, 0, 12, 1, 9, 19, 7, 21, 8, 17, 5, 4],
|
||||
[11, 10, 2, 3, 14, 20, 15, 13, 22, 18, 16, 23]
|
||||
],
|
||||
"eyes": [
|
||||
[2, 4, 0, 8, 39, 17, 1, 26, 16, 15, 27, 20],
|
||||
[33, 11, 19, 32, 9, 12, 23, 34, 21, 25, 40, 35],
|
||||
[5, 41, 13, 36, 37, 6, 24, 30, 31, 18, 28, 46],
|
||||
[7, 44, 38, 42, 45, 29, 3, 43, 22, 10, 14, 47],
|
||||
[48, 49, 50, 53, 59, 56, 54, 58, 57, 55, 51, 52]
|
||||
],
|
||||
"nose": [
|
||||
[1, 10, 2, 3, 6, 0, 5, 4, 8, 9, 7, 11],
|
||||
[13, 14, 12, 17, 16, 15]
|
||||
],
|
||||
"mouth": [
|
||||
[23, 1, 19, 21, 22, 5, 0, 8, 10, 16, 6, 13],
|
||||
[7, 9, 2, 17, 3, 4, 15, 11, 20, 18, 14, 12],
|
||||
[27, 30, 24, 25, 29, 28, 26, 35, 31, 34, 33, 32]
|
||||
],
|
||||
"arrayOf6": [0, 1, 2, 3, 4, 5],
|
||||
"arrayOf9": [0, 1, 2, 3, 4, 5, 6, 7, 8],
|
||||
"arrayOf12": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
const fs = require('fs-extra');
|
||||
require('colors');
|
||||
|
||||
const root = __dirname + '/../';
|
||||
fs.ensureDirSync(`${root}/logs`);
|
||||
|
||||
const streams = {
|
||||
latest: fs.createWriteStream(`${root}/logs/latest.log`),
|
||||
success: fs.createWriteStream(`${root}/logs/success.log`),
|
||||
error: fs.createWriteStream(`${root}/logs/error.log`),
|
||||
warn: fs.createWriteStream(`${root}/logs/warn.log`),
|
||||
info: fs.createWriteStream(`${root}/logs/info.log`)
|
||||
};
|
||||
|
||||
function success(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`;
|
||||
streams.success.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.green.bold);
|
||||
}
|
||||
|
||||
function error(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`;
|
||||
streams.error.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.red.bold);
|
||||
}
|
||||
|
||||
function warn(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`;
|
||||
streams.warn.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.yellow.bold);
|
||||
}
|
||||
|
||||
function info(input) {
|
||||
const time = new Date();
|
||||
input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`;
|
||||
streams.info.write(`${input}\n`);
|
||||
|
||||
console.log(`${input}`.cyan.bold);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
success,
|
||||
error,
|
||||
warn,
|
||||
info
|
||||
};
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
const nodemailer = require('nodemailer');
|
||||
const config = require('../config.json');
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: 'smtp.gmail.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: config.gmail.user,
|
||||
pass: config.gmail.pass
|
||||
}
|
||||
});
|
||||
|
||||
async function sendMail(options) {
|
||||
options.from = config.gmail.from;
|
||||
|
||||
return await transporter.sendMail(options);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendMail
|
||||
};
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
async function redirectMiddleware(request, response, next) {
|
||||
if (request.path.startsWith('/account/logout')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (request.method === 'POST') {
|
||||
request.redirect = request.body.redirect?.startsWith('/') ? request.body.redirect : null;
|
||||
}
|
||||
|
||||
if (request.query.redirect) {
|
||||
response.locals.redirect = request.query.redirect?.startsWith('/') ? request.query.redirect : null;
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = redirectMiddleware;
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
const util = require('../util');
|
||||
const database = require('../database');
|
||||
|
||||
async function renderDataMiddleware(request, response, next) {
|
||||
if (request.path.startsWith('/assets')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (request.path.startsWith('/account/logout')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Get user local
|
||||
const reqLocale = request.locale;
|
||||
const locale = util.getLocale(reqLocale.language, reqLocale.region);
|
||||
|
||||
response.locals.locale = locale;
|
||||
response.locals.localeString = reqLocale.toString();
|
||||
|
||||
// Get message cookies
|
||||
response.locals.success_message = request.cookies.success_message;
|
||||
response.locals.error_message = request.cookies.error_message;
|
||||
|
||||
// Reset message cookies
|
||||
response.clearCookie('success_message', { domain: '.pretendo.network' });
|
||||
response.clearCookie('error_message', { domain: '.pretendo.network' });
|
||||
|
||||
response.locals.isLoggedIn = request.cookies.access_token && request.cookies.refresh_token;
|
||||
|
||||
if (response.locals.isLoggedIn) {
|
||||
try {
|
||||
response.locals.account = await util.getUserAccountData(request, response);
|
||||
|
||||
request.pnid = await database.PNID.findOne({ pid: response.locals.account.pid });
|
||||
request.account = response.locals.account;
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/logout');
|
||||
}
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = renderDataMiddleware;
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
async function requireLoginMiddleware(request, response, next) {
|
||||
if (request.path.startsWith('/account/logout')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Verify the user is logged in
|
||||
if (!request.cookies.access_token || !request.cookies.refresh_token) {
|
||||
return response.redirect(`/account/login?redirect=${request.originalUrl}`);
|
||||
}
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
module.exports = requireLoginMiddleware;
|
||||
|
|
@ -1,435 +0,0 @@
|
|||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const DiscordOauth2 = require('discord-oauth2');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const AdmZip = require('adm-zip');
|
||||
const Stripe = require('stripe');
|
||||
const { REST: DiscordRest } = require('@discordjs/rest');
|
||||
const { Routes: DiscordRoutes } = require('discord-api-types/v10');
|
||||
const requireLoginMiddleware = require('../middleware/require-login');
|
||||
const database = require('../database');
|
||||
const cache = require('../cache');
|
||||
const util = require('../util');
|
||||
const logger = require('../logger');
|
||||
const config = require('../../config.json');
|
||||
const editorJSON = require('../json/miieditor.json');
|
||||
|
||||
const { Router } = express;
|
||||
|
||||
const stripe = new Stripe(config.stripe.secret_key);
|
||||
const router = new Router();
|
||||
const discordRest = new DiscordRest({ version: '10' }).setToken(config.discord.bot_token);
|
||||
|
||||
// Create OAuth client
|
||||
const discordOAuth = new DiscordOauth2({
|
||||
clientId: config.discord.client_id,
|
||||
clientSecret: config.discord.client_secret,
|
||||
redirectUri: `${config.http.base_url}/account/connect/discord`,
|
||||
version: 'v10'
|
||||
});
|
||||
|
||||
router.get('/', requireLoginMiddleware, async (request, response) => {
|
||||
// Setup the data to be sent to the handlebars renderer
|
||||
const renderData = {};
|
||||
|
||||
// Check for Stripe messages
|
||||
const { upgrade_success } = request.query;
|
||||
|
||||
if (upgrade_success === 'true') {
|
||||
renderData.success_message = 'Account upgraded successfully';
|
||||
} else if (upgrade_success === 'false') {
|
||||
renderData.error_message = 'Account upgrade failed';
|
||||
}
|
||||
|
||||
const { account } = request;
|
||||
const { pnid } = request;
|
||||
|
||||
renderData.tierName = pnid.get('connections.stripe.tier_name');
|
||||
renderData.tierLevel = pnid.get('connections.stripe.tier_level');
|
||||
renderData.account = account;
|
||||
renderData.isTester = account.access_level > 0;
|
||||
|
||||
// Check if a Discord account is linked to the PNID
|
||||
if (account.connections.discord.id && account.connections.discord.id.trim() !== '') {
|
||||
try {
|
||||
renderData.discordUser = await discordRest.get(DiscordRoutes.user(account.connections.discord.id));
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
}
|
||||
} else {
|
||||
// If no Discord account linked, generate an auth URL
|
||||
const discordAuthURL = discordOAuth.generateAuthUrl({
|
||||
scope: ['identify', 'guilds'],
|
||||
state: crypto.randomBytes(16).toString('hex'),
|
||||
});
|
||||
|
||||
renderData.discordAuthURL = discordAuthURL;
|
||||
}
|
||||
|
||||
response.render('account/account', renderData);
|
||||
});
|
||||
|
||||
router.get('/login', async (request, response) => {
|
||||
const renderData = {
|
||||
error: request.cookies.error_message
|
||||
};
|
||||
|
||||
response.render('account/login', renderData);
|
||||
});
|
||||
|
||||
router.post('/login', async (request, response) => {
|
||||
const { username, password } = request.body;
|
||||
|
||||
try {
|
||||
const tokens = await util.login(username, password);
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
response.redirect(request.redirect || '/account');
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/login');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/register', async (request, response) => {
|
||||
const renderData = {
|
||||
email: request.cookies.email,
|
||||
username: request.cookies.username,
|
||||
mii_name: request.cookies.mii_name,
|
||||
error: request.cookies.error_message
|
||||
};
|
||||
|
||||
response.clearCookie('email', { domain: '.pretendo.network' });
|
||||
response.clearCookie('username', { domain: '.pretendo.network' });
|
||||
response.clearCookie('mii_name', { domain: '.pretendo.network' });
|
||||
|
||||
response.render('account/register', renderData);
|
||||
});
|
||||
|
||||
router.post('/register', async (request, response) => {
|
||||
const { email, username, mii_name, password, password_confirm, 'h-captcha-response': hCaptchaResponse } = request.body;
|
||||
|
||||
response.cookie('email', email, { domain: '.pretendo.network' });
|
||||
response.cookie('username', username, { domain: '.pretendo.network' });
|
||||
response.cookie('mii_name', mii_name, { domain: '.pretendo.network' });
|
||||
|
||||
try {
|
||||
const tokens = await util.register({
|
||||
email,
|
||||
username,
|
||||
mii_name,
|
||||
password,
|
||||
password_confirm,
|
||||
hCaptchaResponse
|
||||
});
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
response.clearCookie('email', { domain: '.pretendo.network' });
|
||||
response.clearCookie('username', { domain: '.pretendo.network' });
|
||||
response.clearCookie('mii_name', { domain: '.pretendo.network' });
|
||||
|
||||
response.redirect(request.redirect || '/account');
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account/register');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/logout', async(_request, response) => {
|
||||
response.clearCookie('refresh_token', { domain: '.pretendo.network' });
|
||||
response.clearCookie('access_token', { domain: '.pretendo.network' });
|
||||
response.clearCookie('token_type', { domain: '.pretendo.network' });
|
||||
|
||||
response.redirect('/');
|
||||
});
|
||||
|
||||
router.get('/connect/discord', requireLoginMiddleware, async (request, response) => {
|
||||
const { pnid } = request;
|
||||
let tokens;
|
||||
try {
|
||||
// Attempt to get OAuth2 tokens
|
||||
tokens = await discordOAuth.tokenRequest({
|
||||
code: request.query.code,
|
||||
scope: 'identify guilds',
|
||||
grantType: 'authorization_code',
|
||||
});
|
||||
} catch (error) {
|
||||
response.cookie('error_message', 'Invalid Discord authorization code. Please try again', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
|
||||
// Get Discord user data
|
||||
const discordUser = await discordOAuth.getUser(tokens.access_token);
|
||||
|
||||
try {
|
||||
await util.updateDiscordConnection(discordUser, request, response);
|
||||
|
||||
const priceId = pnid.get('connections.stripe.price_id');
|
||||
|
||||
if (priceId && priceId.trim() !== '') {
|
||||
const price = await stripe.prices.retrieve(priceId);
|
||||
const product = await stripe.products.retrieve(price.product);
|
||||
const discordRoleId = product.metadata.discord_role_id;
|
||||
const discordId = discordUser.id;
|
||||
|
||||
await util.assignDiscordMemberSupporterRole(discordId, discordRoleId);
|
||||
|
||||
if (product.metadata.beta === 'true') {
|
||||
await util.assignDiscordMemberTesterRole(discordId);
|
||||
}
|
||||
}
|
||||
|
||||
response.cookie('success_message', 'Discord account linked successfully', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/remove/discord', requireLoginMiddleware, async (request, response) => {
|
||||
const { account, pnid } = request;
|
||||
|
||||
try {
|
||||
await util.removeDiscordConnection(request, response);
|
||||
|
||||
const priceId = pnid.get('connections.stripe.price_id');
|
||||
|
||||
if (priceId && priceId.trim() !== '') {
|
||||
const price = await stripe.prices.retrieve(priceId);
|
||||
const product = await stripe.products.retrieve(price.product);
|
||||
const discordRoleId = product.metadata.discord_role_id;
|
||||
const discordId = account.connections.discord.id;
|
||||
|
||||
await util.removeDiscordMemberSupporterRole(discordId, discordRoleId);
|
||||
|
||||
if (product.metadata.beta === 'true') {
|
||||
await util.removeDiscordMemberTesterRole(discordId);
|
||||
}
|
||||
}
|
||||
|
||||
response.cookie('success_message', 'Discord account removed successfully', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
} catch (error) {
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/online-files', requireLoginMiddleware, async (request, response) => {
|
||||
const { account } = request;
|
||||
const { password } = request.body;
|
||||
|
||||
const hashedPassword = util.nintendoPasswordHash(password, account.pid);
|
||||
|
||||
const miiNameBuffer = Buffer.alloc(0x16);
|
||||
const miiName = Buffer.from(account.mii.name, 'utf16le').swap16();
|
||||
miiName.copy(miiNameBuffer);
|
||||
|
||||
let accountDat = 'AccountInstance_00000000\n';
|
||||
accountDat += 'PersistentId=80000001\n';
|
||||
accountDat += 'TransferableIdBase=0\n';
|
||||
accountDat += `Uuid=${uuidv4().replace(/-/g, '')}\n`;
|
||||
accountDat += `MiiData=${Buffer.from(account.mii.data, 'base64').toString('hex')}\n`;
|
||||
accountDat += `MiiName=${miiNameBuffer.toString('hex')}\n`;
|
||||
accountDat += `AccountId=${account.username}\n`;
|
||||
accountDat += 'BirthYear=0\n';
|
||||
accountDat += 'BirthMonth=0\n';
|
||||
accountDat += 'BirthDay=0\n';
|
||||
accountDat += 'Gender=0\n';
|
||||
accountDat += `EmailAddress=${account.email.address}\n`;
|
||||
accountDat += 'Country=0\n';
|
||||
accountDat += 'SimpleAddressId=0\n';
|
||||
accountDat += `PrincipalId=${account.pid.toString(16)}\n`;
|
||||
accountDat += 'IsPasswordCacheEnabled=1\n';
|
||||
accountDat += `AccountPasswordCache=${hashedPassword}`;
|
||||
|
||||
const onlineFiles = new AdmZip();
|
||||
|
||||
onlineFiles.addFile('mlc01/usr/save/system/act/80000001/account.dat', Buffer.from(accountDat)); // Minimal account.dat
|
||||
onlineFiles.addFile('otp.bin', Buffer.alloc(0x400)); // nulled OTP
|
||||
onlineFiles.addFile('seeprom.bin', Buffer.alloc(0x200)); // nulled SEEPROM
|
||||
|
||||
response.status(200);
|
||||
response.set('Content-Disposition', 'attachment; filename="Cemu Pretendo Online Files.zip');
|
||||
response.set('Content-Type', 'application/zip');
|
||||
|
||||
response.end(onlineFiles.toBuffer());
|
||||
});
|
||||
|
||||
router.get('/miieditor', requireLoginMiddleware, async (request, response) => {
|
||||
const { account } = request;
|
||||
|
||||
response.render('account/miieditor', {
|
||||
encodedUserMiiData: account.mii.data,
|
||||
editorJSON
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/upgrade', requireLoginMiddleware, async (request, response) => {
|
||||
// Set user account info to render data
|
||||
const { pnid } = request;
|
||||
|
||||
const renderData = {
|
||||
error: request.cookies.error,
|
||||
currentTier: pnid.get('connections.stripe.price_id'),
|
||||
donationCache: await cache.getStripeDonationCache()
|
||||
};
|
||||
|
||||
const { data: prices } = await stripe.prices.list();
|
||||
const { data: products } = await stripe.products.list();
|
||||
|
||||
renderData.tiers = products
|
||||
.filter(product => product.active)
|
||||
.sort((a, b) => +a.metadata.tier_level - +b.metadata.tier_level)
|
||||
.map(product => {
|
||||
const price = prices.find(price => price.id === product.default_price);
|
||||
const perks = [];
|
||||
|
||||
if (product.metadata.discord_read === 'true') {
|
||||
perks.push('Read-only access to select dev channels on Discord');
|
||||
}
|
||||
|
||||
if (product.metadata.beta === 'true') {
|
||||
perks.push('Access the beta servers');
|
||||
}
|
||||
|
||||
return {
|
||||
price_id: price.id,
|
||||
thumbnail: product.images[0],
|
||||
name: product.name,
|
||||
description: product.description,
|
||||
perks,
|
||||
price: (price.unit_amount / 100).toLocaleString('en-US', { style: 'currency', currency: 'USD' }),
|
||||
};
|
||||
});
|
||||
|
||||
response.render('account/upgrade', renderData);
|
||||
});
|
||||
|
||||
router.post('/stripe/checkout/:priceId', requireLoginMiddleware, async (request, response) => {
|
||||
// Set user account info to render data
|
||||
const { account } = request;
|
||||
const pid = account.pid;
|
||||
|
||||
let customer;
|
||||
const { data: searchResults } = await stripe.customers.search({
|
||||
query: `metadata['pnid_pid']:'${pid}'`
|
||||
});
|
||||
|
||||
if (searchResults.length !== 0) {
|
||||
customer = searchResults[0];
|
||||
} else {
|
||||
customer = await stripe.customers.create({
|
||||
email: account.email.address,
|
||||
metadata: {
|
||||
pnid_pid: pid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await database.PNID.updateOne({ pid }, {
|
||||
$set: {
|
||||
'connections.stripe.customer_id': customer.id, // ensure PNID always has latest customer ID
|
||||
'connections.stripe.latest_webhook_timestamp': 0
|
||||
}
|
||||
}, { upsert: true }).exec();
|
||||
|
||||
const priceId = request.params.priceId;
|
||||
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
|
||||
if (pnid.get('access_level') >= 2) {
|
||||
response.cookie('error_message', 'Staff members do not need to purchase tiers', { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
customer: customer.id,
|
||||
mode: 'subscription',
|
||||
success_url: `${config.http.base_url}/account?upgrade_success=true`,
|
||||
cancel_url: `${config.http.base_url}/account?upgrade_success=false`
|
||||
});
|
||||
|
||||
return response.redirect(303, session.url);
|
||||
} catch (error) {
|
||||
// Maybe we need a dedicated error page?
|
||||
// Or handle this as not cookies?
|
||||
response.cookie('error_message', error.message, { domain: '.pretendo.network' });
|
||||
|
||||
return response.redirect('/account');
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/stripe/unsubscribe', requireLoginMiddleware, async (request, response) => {
|
||||
// Set user account info to render data
|
||||
const { pnid } = request;
|
||||
|
||||
const pid = pnid.get('pid');
|
||||
const subscriptionId = pnid.get('connections.stripe.subscription_id');
|
||||
const tierName = pnid.get('connections.stripe.tier_name');
|
||||
|
||||
if (subscriptionId) {
|
||||
try {
|
||||
await stripe.subscriptions.del(subscriptionId);
|
||||
|
||||
const updateData = {
|
||||
'connections.stripe.subscription_id': null,
|
||||
'connections.stripe.price_id': null,
|
||||
'connections.stripe.tier_level': 0,
|
||||
'connections.stripe.tier_name': null,
|
||||
};
|
||||
|
||||
if (pnid.get('access_level') < 2) {
|
||||
// Fail-safe for if staff members reach here
|
||||
// Mostly only useful during testing
|
||||
updateData.access_level = 0;
|
||||
}
|
||||
|
||||
await database.PNID.updateOne({ pid }, { $set: updateData }).exec();
|
||||
} catch (error) {
|
||||
logger.error(`Error canceling old user subscription | ${pnid.get('connections.stripe.customer_id')}, ${pid}, ${subscriptionId} | - ${error.message}`);
|
||||
|
||||
response.cookie('error_message', 'Error canceling subscription! Contact support if issue persists', { domain: '.pretendo.network' });
|
||||
|
||||
return response.redirect('/account');
|
||||
}
|
||||
}
|
||||
|
||||
response.cookie('success', `Unsubscribed from ${tierName}`, { domain: '.pretendo.network' });
|
||||
return response.redirect('/account');
|
||||
});
|
||||
|
||||
router.post('/stripe/webhook', async (request, response) => {
|
||||
const stripeSignature = request.headers['stripe-signature'];
|
||||
let event;
|
||||
|
||||
try {
|
||||
event = stripe.webhooks.constructEvent(request.rawBody, stripeSignature, config.stripe.webhook_secret);
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
return response.status(400).send(`Webhook Error: ${error.message}`);
|
||||
}
|
||||
|
||||
await util.handleStripeEvent(event);
|
||||
|
||||
response.json({ received: true });
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
const { Router } = require('express');
|
||||
const router = new Router();
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
response.render('aprilfools');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
const { Router } = require('express');
|
||||
const logger = require('../logger');
|
||||
const router = new Router();
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { marked } = require('marked');
|
||||
const matter = require('gray-matter');
|
||||
|
||||
const postList = () => {
|
||||
const files = fs.readdirSync('blogposts');
|
||||
|
||||
// We get the info for each blogpost, ignoring the ones starting with _
|
||||
const posts = files
|
||||
.filter(filename => !filename.startsWith('_'))
|
||||
.filter(filename => filename.endsWith('.md')) // Ignores other files/folders
|
||||
.map((filename) => {
|
||||
const slug = filename.replace('.md', '');
|
||||
const rawPost = fs.readFileSync(
|
||||
path.join('blogposts', `${filename}`),
|
||||
'utf-8'
|
||||
);
|
||||
const { data: postInfo } = matter(rawPost);
|
||||
return {
|
||||
slug,
|
||||
postInfo,
|
||||
};
|
||||
});
|
||||
|
||||
posts.sort((a, b) => {
|
||||
return new Date(b.postInfo.date) - new Date(a.postInfo.date);
|
||||
});
|
||||
|
||||
return posts;
|
||||
};
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
const renderData = {
|
||||
postList
|
||||
};
|
||||
|
||||
response.render('blog/blog', renderData);
|
||||
});
|
||||
|
||||
// RSS feed
|
||||
router.get('/feed.xml', async (request, response) => {
|
||||
|
||||
// Adds the pubDate and the cover_extension to the post array
|
||||
const posts = postList().map((post) => {
|
||||
post.postInfo.pubDate = new Date(post.postInfo.date).toUTCString();
|
||||
post.postInfo.cover_extension = post.postInfo.cover_image.substring(post.postInfo.cover_image.lastIndexOf('.') + 1);
|
||||
return post;
|
||||
});
|
||||
|
||||
response.set('Content-Type', 'application/rss+xml');
|
||||
response.render('blog/blog-rss', {
|
||||
layout: false,
|
||||
posts
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:slug', async (request, response, next) => {
|
||||
|
||||
const renderData = {
|
||||
layout: 'blog-opengraph',
|
||||
postList,
|
||||
};
|
||||
|
||||
// Get the name of the post from the URL
|
||||
const postName = request.params.slug;
|
||||
|
||||
// Get the markdown file corresponding to the post
|
||||
let rawPost;
|
||||
try {
|
||||
rawPost = fs.readFileSync(path.join('blogposts', `${postName}.md`), 'utf-8');
|
||||
} catch(err) {
|
||||
logger.error(err);
|
||||
next();
|
||||
return;
|
||||
}
|
||||
// Convert the post info into JSON and separate it and the content
|
||||
// eslint-disable-next-line prefer-const
|
||||
let { data: postInfo, content } = matter(rawPost);
|
||||
renderData.postInfo = postInfo;
|
||||
|
||||
// Replace [yt-iframe](videoID) with the full <iframe />
|
||||
content = content
|
||||
.replace(/(?<!`)\[yt-iframe]\(/g, '<div class="aspectratio-fallback"><iframe src="https://www.youtube-nocookie.com/embed/')
|
||||
.replace(/(?<=<iframe src="https:\/\/www\.youtube-nocookie\.com\/embed\/.{11})\)/g, '" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>');
|
||||
|
||||
// Convert the content into HTML
|
||||
const htmlPost = marked.parse(content);
|
||||
renderData.htmlPost = htmlPost;
|
||||
|
||||
response.render('blog/blogpost', renderData);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
const { Router } = require('express');
|
||||
const router = new Router();
|
||||
const util = require('../util');
|
||||
|
||||
const errorList = require('../../docs/common/errorList.json');
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
response.redirect('/docs/welcome');
|
||||
});
|
||||
|
||||
router.get('/welcome', async (request, response) => {
|
||||
const renderData = {
|
||||
currentPage: 'welcome',
|
||||
};
|
||||
|
||||
response.render('docs/welcome', renderData);
|
||||
});
|
||||
|
||||
router.get('/install', async (request, response) => {
|
||||
const renderData = {
|
||||
currentPage: 'install',
|
||||
};
|
||||
|
||||
response.render('docs/install', renderData);
|
||||
});
|
||||
|
||||
router.get('/search', async (request, response) => {
|
||||
const renderData = {
|
||||
errorList: JSON.stringify(errorList),
|
||||
currentPage: 'search',
|
||||
};
|
||||
response.render('docs/search', renderData);
|
||||
});
|
||||
|
||||
router.get('/:page', async (request, response, next) => {
|
||||
const renderData = {};
|
||||
|
||||
const locale = response.locals.localeString;
|
||||
const pageName = request.params.page;
|
||||
renderData.currentPage = pageName;
|
||||
|
||||
const { content, MDLocale } = util.getRawDocs(locale, '', pageName);
|
||||
if (content) {
|
||||
renderData.content = content;
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
renderData.missingInLocale = locale !== MDLocale;
|
||||
|
||||
response.render('docs/docs', renderData);
|
||||
});
|
||||
|
||||
router.get('/:subpath/:page', async (request, response, next) => {
|
||||
const locale = response.locals.localeString;
|
||||
const pageName = request.params.page;
|
||||
const subpath = request.params.subpath;
|
||||
|
||||
const renderData = {
|
||||
currentPage: `${subpath}/${pageName}`
|
||||
};
|
||||
|
||||
const { content, MDLocale } = util.getRawDocs(locale, subpath, pageName);
|
||||
|
||||
if (content) {
|
||||
renderData.content = content;
|
||||
} else {
|
||||
return next();
|
||||
}
|
||||
|
||||
renderData.missingInLocale = locale !== MDLocale;
|
||||
|
||||
response.render('docs/docs', renderData);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
const { Router } = require('express');
|
||||
const router = new Router();
|
||||
|
||||
router.get('/', (request, response) => {
|
||||
response.redirect('/#faq');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
const { Router } = require('express');
|
||||
const router = new Router();
|
||||
|
||||
const { getGithubProjectsCache } = require('../cache');
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
const renderData = {};
|
||||
|
||||
const githubProjectsCache = await getGithubProjectsCache();
|
||||
|
||||
// Builds the arrays of people for the special thanks section
|
||||
|
||||
// Shuffles the special thanks people
|
||||
const specialThanksPeople = response.locals.locale.specialThanks.people.slice();
|
||||
function shuffleArray(array) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
}
|
||||
shuffleArray(specialThanksPeople);
|
||||
|
||||
// Slices the array in half
|
||||
const specialThanksFirstRow = specialThanksPeople.slice(0, 4);
|
||||
const specialThanksSecondRow = specialThanksPeople.slice(4);
|
||||
|
||||
// Builds the final array to be sent to the view, and triples each row.
|
||||
renderData.specialThanksPeople = {
|
||||
first: specialThanksFirstRow.concat(specialThanksFirstRow).concat(specialThanksFirstRow),
|
||||
second: specialThanksSecondRow.concat(specialThanksSecondRow).concat(specialThanksSecondRow)
|
||||
};
|
||||
|
||||
// Progress
|
||||
|
||||
// Creates an object to hold the total progress
|
||||
const totalProgress = {
|
||||
title: null,
|
||||
_calc: {
|
||||
percentageSum: 0
|
||||
},
|
||||
percent: 0,
|
||||
cards: {
|
||||
todo: [],
|
||||
in_progress: [],
|
||||
done: []
|
||||
}
|
||||
};
|
||||
|
||||
// Calculates individual completion percentages and progress states
|
||||
githubProjectsCache.sections.forEach(section => {
|
||||
const { todo, in_progress, done } = section.cards;
|
||||
|
||||
// Calculates the completion percentage of the project, and sums it to the total
|
||||
const sectionCompletionPercentage = ((done.length + in_progress.length * 0.5) / (done.length + in_progress.length + todo.length)) || 0;
|
||||
totalProgress._calc.percentageSum += sectionCompletionPercentage;
|
||||
|
||||
const sectionTitle = `${section.title} [${Math.round(sectionCompletionPercentage * 100)}%]`;
|
||||
|
||||
if (done !== [] && in_progress + todo === []) {
|
||||
// if all the section tasks have been done, push to done
|
||||
totalProgress.cards.done.push(sectionTitle);
|
||||
} else if (todo !== [] && in_progress + done === []) {
|
||||
// if none the section tasks have been done or in_progress, push to todo
|
||||
totalProgress.cards.todo.push(sectionTitle);
|
||||
} else {
|
||||
// for all other combos, push to in_progress
|
||||
totalProgress.cards.in_progress.push(sectionTitle);
|
||||
}
|
||||
});
|
||||
|
||||
// Calculates global completion percentage
|
||||
totalProgress.percent = Math.round(totalProgress._calc.percentageSum / githubProjectsCache.sections.length * 100);
|
||||
|
||||
renderData.featuredFeatureList = totalProgress;
|
||||
|
||||
response.render('home', renderData);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
const { Router } = require('express');
|
||||
const router = new Router();
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
response.render('localization');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
const { Router } = require('express');
|
||||
const router = new Router();
|
||||
|
||||
const { getGithubProjectsCache, getStripeDonationCache } = require('../cache');
|
||||
|
||||
router.get('/', async (request, response) => {
|
||||
const renderData = {
|
||||
progressLists: await getGithubProjectsCache(),
|
||||
donationCache: await getStripeDonationCache()
|
||||
};
|
||||
|
||||
response.render('progress', renderData);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
const { Schema } = require('mongoose');
|
||||
|
||||
// Only define what we will be using
|
||||
const PNIDSchema = new Schema({
|
||||
pid: {
|
||||
type: Number,
|
||||
unique: true
|
||||
},
|
||||
server_access_level: String,
|
||||
access_level: Number,
|
||||
username: String,
|
||||
connections: {
|
||||
discord: {
|
||||
id: String
|
||||
},
|
||||
stripe: {
|
||||
customer_id: String,
|
||||
subscription_id: String,
|
||||
price_id: String,
|
||||
tier_level: Number,
|
||||
tier_name: String,
|
||||
latest_webhook_timestamp: Number
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = PNIDSchema;
|
||||
159
src/server.js
159
src/server.js
|
|
@ -1,159 +0,0 @@
|
|||
process.title = 'Pretendo - Website';
|
||||
|
||||
const express = require('express');
|
||||
const handlebars = require('express-handlebars');
|
||||
const morgan = require('morgan');
|
||||
const expressLocale = require('express-locale');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const Stripe = require('stripe');
|
||||
const redirectMiddleware = require('./middleware/redirect');
|
||||
const renderDataMiddleware = require('./middleware/render-data');
|
||||
const database = require('./database');
|
||||
const util = require('./util');
|
||||
const logger = require('./logger');
|
||||
const config = require('../config.json');
|
||||
|
||||
const { http: { port } } = config;
|
||||
const app = express();
|
||||
const stripe = new Stripe(config.stripe.secret_key);
|
||||
|
||||
logger.info('Setting up Middleware');
|
||||
app.use(morgan('dev'));
|
||||
//app.use(express.json());
|
||||
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
|
||||
app.use(express.urlencoded({
|
||||
extended: true
|
||||
}));
|
||||
app.use(cookieParser());
|
||||
app.use(expressLocale({
|
||||
'priority': ['cookie', 'accept-language', 'map', 'default'],
|
||||
cookie: { name: 'preferredLocale' },
|
||||
// Map unavailable regions to available locales from the same language
|
||||
map: {
|
||||
/* TODO: map more regions to the available locales */
|
||||
en: 'en-US', 'en-GB': 'en-US', 'en-AU': 'en-US', 'en-CA': 'en-US',
|
||||
ar: 'ar-AR',
|
||||
cn: 'zh-CN',
|
||||
de: 'de-DE',
|
||||
nl: 'nl-NL',
|
||||
es: 'es-ES',
|
||||
fr: 'fr-FR', 'fr-CA': 'fr-FR', 'fr-CH': 'fr-FR',
|
||||
it: 'it-IT', 'it-CH': 'it-IT',
|
||||
ja: 'ja-JP',
|
||||
ko: 'ko-KR',
|
||||
nb: 'nb-NO',
|
||||
no: 'nb-NO',
|
||||
pl: 'pl-PL',
|
||||
pt: 'pt-BR',
|
||||
ro: 'ro-RO',
|
||||
ru: 'ru-RU',
|
||||
uk: 'uk-UA',
|
||||
},
|
||||
allowed: [
|
||||
'en', 'en-US', 'en-GB', 'en-AU', 'en-CA',
|
||||
'ar', 'ar-AR',
|
||||
'cn', 'zh-CN', 'zh-HK', 'zh-TW',
|
||||
'de', 'de-DE',
|
||||
'nl', 'nl-NL',
|
||||
'es', 'es-ES',
|
||||
'fr', 'fr-FR', 'fr-CA', 'fr-CH',
|
||||
'it', 'it-IT', 'it-CH',
|
||||
'ja', 'ja-JP',
|
||||
'ko', 'ko-KR',
|
||||
'nb', 'no', 'nb-NO',
|
||||
'pl', 'pl-PL',
|
||||
'pt', 'pt-BR',
|
||||
'ro', 'ro-RO',
|
||||
'ru', 'ru-RU',
|
||||
'uk', 'uk-UA',
|
||||
],
|
||||
'default': 'en-US'
|
||||
}));
|
||||
app.use(redirectMiddleware);
|
||||
app.use(renderDataMiddleware);
|
||||
|
||||
logger.info('Setting up static public folder');
|
||||
app.use(express.static('public'));
|
||||
|
||||
logger.info('Importing routes');
|
||||
const routes = {
|
||||
home: require('./routes/home'),
|
||||
faq: require('./routes/faq'),
|
||||
docs: require('./routes/docs'),
|
||||
progress: require('./routes/progress'),
|
||||
account: require('./routes/account'),
|
||||
blog: require('./routes/blog'),
|
||||
localization: require('./routes/localization'),
|
||||
aprilfools: require('./routes/aprilfools')
|
||||
};
|
||||
|
||||
app.use('/', routes.home);
|
||||
app.use('/faq', routes.faq);
|
||||
app.use('/docs', routes.docs);
|
||||
app.use('/progress', routes.progress);
|
||||
app.use('/account', routes.account);
|
||||
app.use('/localization', routes.localization);
|
||||
app.use('/blog', routes.blog);
|
||||
app.use('/nso-legacy-pack', routes.aprilfools);
|
||||
|
||||
logger.info('Creating 404 status handler');
|
||||
// This works because it is the last router created
|
||||
// Meaning the request could not find a valid router
|
||||
app.use((request, response, next) => {
|
||||
const fullUrl = util.fullUrl(request);
|
||||
logger.warn(`HTTP 404 at ${fullUrl}`);
|
||||
next();
|
||||
});
|
||||
|
||||
logger.info('Setting up handlebars engine');
|
||||
app.engine('handlebars', handlebars({
|
||||
helpers: {
|
||||
doFaq(value, options) {
|
||||
let htmlLeft = '';
|
||||
let htmlRight = '';
|
||||
for (const [i, v] of Object.entries(value)) {
|
||||
const appendHtml = options.fn({
|
||||
...v
|
||||
}); // Tis is an HTML string
|
||||
if (i % 2 === 0) {
|
||||
htmlLeft += appendHtml;
|
||||
} else {
|
||||
htmlRight += appendHtml;
|
||||
}
|
||||
}
|
||||
return `
|
||||
<div class="left questions-left">
|
||||
${htmlLeft}
|
||||
</div>
|
||||
<div class="right questions-right">
|
||||
${htmlRight}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
eq(value1, value2) {
|
||||
return value1 === value2;
|
||||
},
|
||||
neq(value1, value2) {
|
||||
return value1 !== value2;
|
||||
},
|
||||
slug(string) {
|
||||
return string.toLowerCase().replaceAll(/ /g, '-');
|
||||
}
|
||||
}
|
||||
}));
|
||||
app.set('view engine', 'handlebars');
|
||||
|
||||
logger.info('Starting server');
|
||||
database.connect().then(() => {
|
||||
app.listen(port, async () => {
|
||||
const events = await stripe.events.list({
|
||||
delivery_success: false // failed webhooks
|
||||
});
|
||||
|
||||
for (const event of events.data) {
|
||||
await util.handleStripeEvent(event);
|
||||
}
|
||||
|
||||
logger.success(`Server listening on http://localhost:${port}`);
|
||||
});
|
||||
});
|
||||
538
src/util.js
538
src/util.js
|
|
@ -1,538 +0,0 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const got = require('got');
|
||||
const crypto = require('crypto');
|
||||
const Stripe = require('stripe');
|
||||
const { marked } = require('marked');
|
||||
const { REST: DiscordRest } = require('@discordjs/rest');
|
||||
const { Routes: DiscordRoutes } = require('discord-api-types/v10');
|
||||
const merge = require('lodash.merge');
|
||||
|
||||
const mailer = require('./mailer');
|
||||
const database = require('./database');
|
||||
const logger = require('./logger');
|
||||
|
||||
const config = require('../config.json');
|
||||
const baseLocale = require(`${__dirname}/../locales/en_US.json`);
|
||||
const discordRest = new DiscordRest({ version: '10' }).setToken(config.discord.bot_token);
|
||||
|
||||
const stripe = new Stripe(config.stripe.secret_key);
|
||||
|
||||
function fullUrl(request) {
|
||||
return `${request.protocol}://${request.hostname}${request.originalUrl}`;
|
||||
}
|
||||
|
||||
function getLocale(language, region) {
|
||||
const path = `${__dirname}/../locales/${language}_${region}.json`;
|
||||
|
||||
if (fs.pathExistsSync(path)) {
|
||||
const selectedLocale = require(path);
|
||||
const finalLocale = merge({}, baseLocale, selectedLocale);
|
||||
|
||||
return finalLocale;
|
||||
}
|
||||
|
||||
logger.warn(`Could not find locale ${language}_${region}! Loading en_US`);
|
||||
|
||||
return baseLocale;
|
||||
}
|
||||
|
||||
function getRawDocs(locale, subpath, pageName) {
|
||||
|
||||
const localePath = path.join(__dirname, '../docs', locale.replace('-', '_'), subpath, `${pageName}.md`);
|
||||
const defaultPath = path.join(__dirname, '../docs', 'en_US', subpath, `${pageName}.md`);
|
||||
|
||||
if (fs.existsSync(localePath)) {
|
||||
return {
|
||||
content: parseDocs(fs.readFileSync(localePath, 'utf8')),
|
||||
MDLocale: locale,
|
||||
};
|
||||
} else if (fs.existsSync(defaultPath)) {
|
||||
return {
|
||||
content: parseDocs(fs.readFileSync(defaultPath, 'utf8')),
|
||||
MDLocale: 'en-US'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: null,
|
||||
MDLocale: null
|
||||
};
|
||||
}
|
||||
|
||||
function parseDocs(rawDocs) {
|
||||
if (!rawDocs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let markedContent = marked(rawDocs);
|
||||
markedContent = markedContent.replaceAll(/\[yt-iframe\]\(.{11}\)/g, (match) => {
|
||||
const videoIDRegex = /(?<=\[yt-iframe\]\().*(?=\))/g;
|
||||
const videoID = match.match(videoIDRegex)[0];
|
||||
return `<div class="aspectratio-fallback"><iframe src="https://www.youtube-nocookie.com/embed/${videoID}" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`;
|
||||
});
|
||||
|
||||
const htmlContent = marked.parse(markedContent);
|
||||
return htmlContent;
|
||||
}
|
||||
|
||||
function apiGetRequest(path, headers) {
|
||||
return got.get(`${config.api_base}${path}`, {
|
||||
responseType: 'json',
|
||||
throwHttpErrors: false,
|
||||
https: {
|
||||
rejectUnauthorized: false, // Needed for self-signed certificates on localhost testing
|
||||
},
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
||||
function apiPostRequest(path, headers, json) {
|
||||
return got.post(`${config.api_base}${path}`, {
|
||||
responseType: 'json',
|
||||
throwHttpErrors: false,
|
||||
https: {
|
||||
rejectUnauthorized: false, // Needed for self-signed certificates on localhost testing
|
||||
},
|
||||
headers,
|
||||
json
|
||||
});
|
||||
}
|
||||
|
||||
function apiDeleteRequest(path, headers, json) {
|
||||
return got.delete(`${config.api_base}${path}`, {
|
||||
throwHttpErrors: false,
|
||||
https: {
|
||||
rejectUnauthorized: false, // Needed for self-signed certificates on localhost testing
|
||||
},
|
||||
headers,
|
||||
json
|
||||
});
|
||||
}
|
||||
|
||||
async function register(registerData) {
|
||||
const apiResponse = await apiPostRequest('/v1/register', {}, registerData);
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
throw new Error(apiResponse.body.error);
|
||||
}
|
||||
|
||||
return apiResponse.body;
|
||||
}
|
||||
|
||||
async function login(username, password) {
|
||||
const apiResponse = await apiPostRequest('/v1/login', {}, {
|
||||
username,
|
||||
password,
|
||||
grant_type: 'password'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
throw new Error(apiResponse.body.error);
|
||||
}
|
||||
|
||||
return apiResponse.body;
|
||||
}
|
||||
|
||||
async function refreshLogin(request, response) {
|
||||
const apiResponse = await apiPostRequest('/v1/login', {}, {
|
||||
refresh_token: request.cookies.refresh_token,
|
||||
grant_type: 'refresh_token'
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
// TODO: Error message
|
||||
throw new Error('Bad');
|
||||
}
|
||||
|
||||
const tokens = apiResponse.body;
|
||||
|
||||
response.cookie('refresh_token', tokens.refresh_token, { domain: '.pretendo.network' });
|
||||
response.cookie('access_token', tokens.access_token, { domain: '.pretendo.network' });
|
||||
response.cookie('token_type', tokens.token_type, { domain: '.pretendo.network' });
|
||||
|
||||
request.cookies.refresh_token = tokens.refresh_token;
|
||||
request.cookies.access_token = tokens.access_token;
|
||||
request.cookies.token_type = tokens.token_type;
|
||||
}
|
||||
|
||||
async function getUserAccountData(request, response, fromRetry=false) {
|
||||
const apiResponse = await apiGetRequest('/v1/user', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200 && fromRetry === true) {
|
||||
// TODO: Error message
|
||||
throw new Error('Bad');
|
||||
}
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
await refreshLogin(request, response);
|
||||
return await getUserAccountData(request, response, true);
|
||||
}
|
||||
|
||||
return apiResponse.body;
|
||||
}
|
||||
|
||||
async function updateDiscordConnection(discordUser, request, response, fromRetry=false) {
|
||||
const apiResponse = await apiPostRequest('/v1/connections/add/discord', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
}, {
|
||||
data: {
|
||||
id: discordUser.id
|
||||
}
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200 && fromRetry === true) {
|
||||
// TODO: Error message
|
||||
throw new Error('Bad');
|
||||
}
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
await refreshLogin(request, response);
|
||||
await updateDiscordConnection(discordUser, request, response, true);
|
||||
}
|
||||
}
|
||||
|
||||
async function removeDiscordConnection(request, response, fromRetry = false) {
|
||||
const apiResponse = await apiDeleteRequest('/v1/connections/remove/discord', {
|
||||
'Authorization': `${request.cookies.token_type} ${request.cookies.access_token}`
|
||||
});
|
||||
|
||||
if (apiResponse.statusCode !== 200 && fromRetry === true) {
|
||||
// TODO: Error message
|
||||
throw new Error('Bad');
|
||||
}
|
||||
|
||||
if (apiResponse.statusCode !== 200) {
|
||||
await refreshLogin(request, response);
|
||||
await removeDiscordConnection(request, response, true);
|
||||
}
|
||||
}
|
||||
|
||||
function nintendoPasswordHash(password, pid) {
|
||||
const pidBuffer = Buffer.alloc(4);
|
||||
pidBuffer.writeUInt32LE(pid);
|
||||
|
||||
const unpacked = Buffer.concat([
|
||||
pidBuffer,
|
||||
Buffer.from('\x02\x65\x43\x46'),
|
||||
Buffer.from(password)
|
||||
]);
|
||||
|
||||
const hashed = crypto.createHash('sha256').update(unpacked).digest().toString('hex');
|
||||
|
||||
return hashed;
|
||||
}
|
||||
|
||||
async function handleStripeEvent(event) {
|
||||
if (event.type === 'customer.subscription.updated' || event.type === 'customer.subscription.deleted') {
|
||||
const subscription = event.data.object;
|
||||
const product = await stripe.products.retrieve(subscription.plan.product);
|
||||
const customer = await stripe.customers.retrieve(subscription.customer);
|
||||
|
||||
if (!customer?.metadata?.pnid_pid) {
|
||||
// No PNID PID linked to customer
|
||||
if (subscription.status !== 'canceled' && subscription.status !== 'unpaid') {
|
||||
// Abort and refund!
|
||||
logger.error(`Stripe user ${customer.id} has no PNID linked! Refunding order`);
|
||||
|
||||
try {
|
||||
await stripe.subscriptions.del(subscription.id);
|
||||
|
||||
const invoice = await stripe.invoices.retrieve(subscription.latest_invoice);
|
||||
await stripe.refunds.create({
|
||||
payment_intent: invoice.payment_intent
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error refunding subscription | ${customer.id}, ${subscription.id} | - ${error.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: customer.email,
|
||||
subject: 'Pretendo Network Subscription Failed - No Linked PNID',
|
||||
text: `Your recent subscription to Pretendo Network has failed.\nThis is due to no PNID PID being linked to the Stripe customer account used. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending email | ${customer.id}, ${customer.email} | - ${error.message}`);
|
||||
}
|
||||
} else {
|
||||
logger.error(`Stripe user ${customer.id} has no PNID linked!`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const pid = Number(customer.metadata.pnid_pid);
|
||||
const pnid = await database.PNID.findOne({ pid });
|
||||
|
||||
if (!pnid) {
|
||||
// PNID does not exist
|
||||
if (subscription.status !== 'canceled' && subscription.status !== 'unpaid') {
|
||||
// Abort and refund!
|
||||
logger.error(`PNID PID ${pid} does not exist! Found on Stripe user ${customer.id}! Refunding order`);
|
||||
|
||||
try {
|
||||
await stripe.subscriptions.del(subscription.id);
|
||||
|
||||
const invoice = await stripe.invoices.retrieve(subscription.latest_invoice);
|
||||
await stripe.refunds.create({
|
||||
payment_intent: invoice.payment_intent
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error refunding subscription | ${customer.id}, ${subscription.id} | - ${error.message}`);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: customer.email,
|
||||
subject: 'Pretendo Network Subscription Failed - PNID Not Found',
|
||||
text: `Your recent subscription to Pretendo Network has failed.\nThis is due to the provided PNID not being found. The subscription has been canceled and refunded. Please contact Jon immediately.\nStripe Customer ID: ${customer.id}\nPNID PID: ${pid}`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending email | ${customer.id}, ${customer.email} | - ${error.message}`);
|
||||
}
|
||||
} else {
|
||||
logger.error(`PNID PID ${pid} does not exist! Found on Stripe user ${customer.id}!`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const latestWebhookTimestamp = pnid.get('connections.stripe.latest_webhook_timestamp');
|
||||
|
||||
if (latestWebhookTimestamp && latestWebhookTimestamp >= event.created) {
|
||||
// Do nothing, this webhook is older than the latest seen
|
||||
return;
|
||||
}
|
||||
|
||||
const currentSubscriptionId = pnid.get('connections.stripe.subscription_id');
|
||||
const discordId = pnid.get('connections.discord.id');
|
||||
|
||||
if (subscription.status === 'canceled' && currentSubscriptionId && subscription.id !== currentSubscriptionId) {
|
||||
// Canceling old subscription, do nothing but update webhook date and remove Discord roles
|
||||
if (product.metadata.beta === 'true') {
|
||||
removeDiscordMemberTesterRole(discordId).catch(error => {
|
||||
logger.error(`Error removing user Discord tester role | ${customer.id}, ${discordId}, ${pid} | - ${error.message}`);
|
||||
});
|
||||
}
|
||||
|
||||
removeDiscordMemberSupporterRole(discordId, product.metadata.discord_role_id).catch(error => {
|
||||
logger.error(`Error removing user Discord supporter role | ${customer.id}, ${discordId}, ${pid}, ${product.metadata.discord_role_id} | - ${error.message}`);
|
||||
});
|
||||
|
||||
const updateData = {
|
||||
'connections.stripe.latest_webhook_timestamp': event.created
|
||||
};
|
||||
|
||||
await database.PNID.updateOne({
|
||||
pid,
|
||||
'connections.stripe.latest_webhook_timestamp': {
|
||||
$lte: event.created
|
||||
}
|
||||
}, { $set: updateData }).exec();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
'connections.stripe.subscription_id': subscription.status === 'active' ? subscription.id : null,
|
||||
'connections.stripe.price_id': subscription.status === 'active' ? subscription.plan.id : null,
|
||||
'connections.stripe.tier_level': subscription.status === 'active' ? Number(product.metadata.tier_level || 0) : 0,
|
||||
'connections.stripe.tier_name': subscription.status === 'active' ? product.name : null,
|
||||
'connections.stripe.latest_webhook_timestamp': event.created,
|
||||
};
|
||||
|
||||
if (product.metadata.beta === 'true') {
|
||||
switch (subscription.status) {
|
||||
case 'active':
|
||||
if (pnid.access_level < 2) { // only change access level if not staff member
|
||||
updateData.access_level = 1;
|
||||
}
|
||||
|
||||
assignDiscordMemberTesterRole(discordId).catch(error => {
|
||||
logger.error(`Error assigning user Discord tester role | ${customer.id}, ${discordId}, ${pid} | - ${error.message}`);
|
||||
});
|
||||
break;
|
||||
|
||||
case 'canceled': // Subscription was canceled
|
||||
case 'unpaid': // User missed too many payments
|
||||
if (pnid.access_level < 2) { // only change access level if not staff member
|
||||
updateData.access_level = 0;
|
||||
}
|
||||
|
||||
removeDiscordMemberTesterRole(discordId).catch(error => {
|
||||
logger.error(`Error removing user Discord tester role | ${customer.id}, ${discordId}, ${pid} | - ${error.message}`);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await database.PNID.updateOne({
|
||||
pid,
|
||||
'connections.stripe.latest_webhook_timestamp': {
|
||||
$lte: event.created
|
||||
}
|
||||
}, { $set: updateData }).exec();
|
||||
|
||||
if (subscription.status === 'active') {
|
||||
// Get all the customers active subscriptions
|
||||
const { data: activeSubscriptions } = await stripe.subscriptions.list({
|
||||
limit: 100,
|
||||
status: 'active',
|
||||
customer: customer.id
|
||||
});
|
||||
|
||||
// Order subscriptions by creation time and remove the latest one
|
||||
const orderedActiveSubscriptions = activeSubscriptions.sort((a, b) => b.created - a.created);
|
||||
const pastSubscriptions = orderedActiveSubscriptions.slice(1);
|
||||
|
||||
// Remove any old past subscriptions that might still be hanging around
|
||||
for (const pastSubscription of pastSubscriptions) {
|
||||
try {
|
||||
await stripe.subscriptions.del(pastSubscription.id);
|
||||
} catch (error) {
|
||||
logger.error(`Error canceling old user subscription | ${customer.id}, ${pid}, ${pastSubscription.id} | - ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: customer.email,
|
||||
subject: 'Pretendo Network Subscription - Active',
|
||||
text: `Thank you for purchasing the ${product.name} tier! We greatly value your support, thank you for helping keep Pretendo Network alive!\nIt may take a moment for your account dashboard to reflect these changes. Please wait a moment and refresh the dashboard to see them!`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending email | ${customer.id}, ${customer.email}, ${pid} | - ${error.message}`);
|
||||
}
|
||||
|
||||
assignDiscordMemberSupporterRole(discordId, product.metadata.discord_role_id).catch(error => {
|
||||
logger.error(`Error assigning user Discord supporter role | ${customer.id}, ${discordId}, ${pid}, ${product.metadata.discord_role_id} | - ${error.message}`);
|
||||
});
|
||||
|
||||
for (const email of config.stripe.notification_emails) {
|
||||
// * Send notification emails for new sub
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
subject: `[Pretendo] - New ${product.name} subscription`,
|
||||
text: `${pnid.get('username')} just became a ${product.name} tier subscriber`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending notification email | ${email} | - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subscription.status === 'canceled') {
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: customer.email,
|
||||
subject: 'Pretendo Network Subscription - Canceled',
|
||||
text: `Your subscription for the ${product.name} tier has been canceled. We thank for your previous support, and hope you still enjoy the network! `
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending email | ${customer.id}, ${customer.email}, ${pid} | - ${error.message}`);
|
||||
}
|
||||
|
||||
removeDiscordMemberSupporterRole(discordId, product.metadata.discord_role_id).catch(error => {
|
||||
logger.error(`Error removing user Discord supporter role | ${customer.id}, ${discordId}, ${pid}, ${product.metadata.discord_role_id} | - ${error.message}`);
|
||||
});
|
||||
|
||||
for (const email of config.stripe.notification_emails) {
|
||||
// * Send notification emails for new sub
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
subject: `[Pretendo] - Canceled ${product.name} subscription`,
|
||||
text: `${pnid.get('username')} just canceled their ${product.name} tier subscription`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending notification email | ${email} | - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subscription.status === 'unpaid') {
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: customer.email,
|
||||
subject: 'Pretendo Network Subscription - Unpaid',
|
||||
text: `Your subscription for the ${product.name} tier has been canceled due to non payment. We thank for your previous support, and hope you still enjoy the network! `
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending email | ${customer.id}, ${customer.email}, ${pid} | - ${error.message}`);
|
||||
}
|
||||
|
||||
removeDiscordMemberSupporterRole(discordId, product.metadata.discord_role_id).catch(error => {
|
||||
logger.error(`Error removing user Discord supporter role | ${customer.id}, ${discordId}, ${pid}, ${product.metadata.discord_role_id} | - ${error.message}`);
|
||||
});
|
||||
|
||||
for (const email of config.stripe.notification_emails) {
|
||||
// * Send notification emails for new sub
|
||||
try {
|
||||
await mailer.sendMail({
|
||||
to: email,
|
||||
subject: `[Pretendo] - Removed ${product.name} subscription`,
|
||||
text: `${pnid.get('username')}'s ${product.name} tier subscription has been canceled due to non payment`
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Error sending notification email | ${email} | - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function assignDiscordMemberSupporterRole(memberId, roleId) {
|
||||
if (memberId && memberId.trim() !== '') {
|
||||
await discordRest.put(DiscordRoutes.guildMemberRole(config.discord.guild_id, memberId, config.discord.roles.supporter));
|
||||
await discordRest.put(DiscordRoutes.guildMemberRole(config.discord.guild_id, memberId, roleId));
|
||||
}
|
||||
}
|
||||
|
||||
async function assignDiscordMemberTesterRole(memberId) {
|
||||
if (memberId && memberId.trim() !== '') {
|
||||
await discordRest.put(DiscordRoutes.guildMemberRole(config.discord.guild_id, memberId, config.discord.roles.tester));
|
||||
}
|
||||
}
|
||||
|
||||
async function removeDiscordMemberSupporterRole(memberId, roleId) {
|
||||
if (memberId && memberId.trim() !== '') {
|
||||
await discordRest.delete(DiscordRoutes.guildMemberRole(config.discord.guild_id, memberId, config.discord.roles.supporter));
|
||||
await discordRest.delete(DiscordRoutes.guildMemberRole(config.discord.guild_id, memberId, roleId));
|
||||
}
|
||||
}
|
||||
|
||||
async function removeDiscordMemberTesterRole(memberId) {
|
||||
if (memberId && memberId.trim() !== '') {
|
||||
await discordRest.delete(DiscordRoutes.guildMemberRole(config.discord.guild_id, memberId, config.discord.roles.tester));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fullUrl,
|
||||
getLocale,
|
||||
getRawDocs,
|
||||
parseDocs,
|
||||
apiGetRequest,
|
||||
apiPostRequest,
|
||||
apiDeleteRequest,
|
||||
register,
|
||||
login,
|
||||
refreshLogin,
|
||||
getUserAccountData,
|
||||
updateDiscordConnection,
|
||||
removeDiscordConnection,
|
||||
nintendoPasswordHash,
|
||||
handleStripeEvent,
|
||||
assignDiscordMemberSupporterRole,
|
||||
assignDiscordMemberTesterRole,
|
||||
removeDiscordMemberSupporterRole,
|
||||
removeDiscordMemberTesterRole
|
||||
};
|
||||
64
styles/globals.css
Normal file
64
styles/globals.css
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
:root {
|
||||
/* 1 is the base color, <1 are darker variations and >1 are lighter */
|
||||
|
||||
--bg-shade-0: #131733;
|
||||
--bg-shade-1: #1B1F3B;
|
||||
--bg-shade-2: #23274A;
|
||||
--bg-shade-2-5: #2D3258;
|
||||
--bg-shade-3: #373C65;
|
||||
--bg-shade-3-5: #404673;
|
||||
--bg-shade-4: #494F81;
|
||||
|
||||
--accent-shade-0: #673DB6;
|
||||
--accent-shade-1: #9D6FF3;
|
||||
--accent-shade-2: #A185D6;
|
||||
--accent-shade-3: #CAB1FB;
|
||||
|
||||
--text-shade-0: #8990C1;
|
||||
--text-shade-1: #A1A8D9;
|
||||
--text-shade-2: #CAC1F5;
|
||||
--text-shade-3: #fff;
|
||||
|
||||
--green-shade-0: #37a985;
|
||||
--green-shade-1: #59c9a5;
|
||||
|
||||
--red-shade-1: #a9375b;
|
||||
|
||||
--border: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg-shade-1);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
body {
|
||||
width: 100%;
|
||||
max-width: 100vw;
|
||||
position: relative; /* This fixes overflow-x not hiding on Safari on mobile */
|
||||
overflow-x: hidden;
|
||||
margin: 0;
|
||||
color: var(--text-shade-3);
|
||||
justify-content: center;
|
||||
font-family: Poppins, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
/* Scrollbar styling 'cause it's fancy */
|
||||
*::-webkit-scrollbar {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
*::-webkit-scrollbar-track {
|
||||
background: var(--bg-shade-1);
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: var(--text-shade-0);
|
||||
border-radius: 24px;
|
||||
border: 3px solid var(--bg-shade-1);
|
||||
}
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background-color: var(--accent-shade-1);
|
||||
}
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--text-shade-0) var(--bg-shade-1);
|
||||
}
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/account.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="account-wrapper">
|
||||
<div class="account-sidebar">
|
||||
<div class="user">
|
||||
<a href="/account/miieditor" class="mii">
|
||||
<img src="{{account.mii.image_url}}" alt="Mii image" />
|
||||
</a>
|
||||
<p class="miiname">{{account.mii.name}}</p>
|
||||
<p class="username" value="{{account.username}}">PNID: {{account.username}}</p>
|
||||
{{#if tierName}}
|
||||
<p class="tier-name tier-level-{{tierLevel}}" value="{{tierName}}">{{tierName}}</p>
|
||||
{{else}}
|
||||
{{#if (neq account.access_level -1)}}
|
||||
<p class="tier-name access-level-{{account.access_level}}" value="{{ lookup locale.account.accountLevel account.access_level }}">{{ lookup locale.account.accountLevel account.access_level }}</p>
|
||||
{{else}}
|
||||
<p class="tier-name access-level-banned" value="{{ locale.account.banned }}">{{ locale.account.banned }}</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="button secondary" id="download-cemu-files" href="/account/online-files" download>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
|
||||
<p class="caption">{{ locale.account.settings.downloadFiles }}</p>
|
||||
<p class="cemu-warning">{{ locale.account.settings.downloadFilesDescription }}</p>
|
||||
</a>
|
||||
|
||||
<a class="button secondary" id="account-upgrade" href="/account/upgrade">
|
||||
<p class="caption">{{ locale.account.settings.upgrade }}</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-wrapper">
|
||||
<h2 class="section-header" id="user-settings">{{ locale.account.settings.settingCards.userSettings }}</h2>
|
||||
<div class="setting-card">
|
||||
<h2 class="header">{{ locale.account.settings.settingCards.profile }}</h2>
|
||||
<a href="/account/edit/profile" class="edit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<p class="label">{{ locale.account.settings.settingCards.nickname }}</p>
|
||||
<p class="value">{{account.mii.name}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">{{ locale.account.settings.settingCards.birthDate }}</p>
|
||||
<p class="value">{{account.birthdate}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">{{ locale.account.settings.settingCards.gender }}</p>
|
||||
<p class="value">{{account.gender}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">{{ locale.account.settings.settingCards.country }}</p>
|
||||
<p class="value">{{account.country}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">{{ locale.account.settings.settingCards.timezone }}</p>
|
||||
<p class="value">{{account.timezone.name}}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="setting-card">
|
||||
<h2 class="header">{{ locale.account.settings.settingCards.serverEnv }}</h2>
|
||||
<fieldset {{#unless isTester}}disabled{{/unless}}>
|
||||
<form class="server-selection" id="server">
|
||||
<input type="radio" id="prod" name="server_selection" value="prod" checked="{{ eq account.server_access_level 'prod'}}">
|
||||
<label for="prod">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
||||
<polyline points="7.5 4.21 12 6.81 16.5 4.21"></polyline>
|
||||
<polyline points="7.5 19.79 7.5 14.6 3 12"></polyline>
|
||||
<polyline points="21 12 16.5 14.6 16.5 19.79"></polyline>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12"></line>
|
||||
</svg>
|
||||
<h2>{{ locale.account.settings.settingCards.production }}</h2>
|
||||
</label>
|
||||
{{#if isTester}}
|
||||
<input type="radio" id="beta" name="server_selection" value="beta" checked="{{ neq account.server_access_level 'prod'}}">
|
||||
<label for="beta">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="2,21 22,21 14,11.5 14,5 10,3 10,11.5"></polygon>
|
||||
</svg>
|
||||
<h2>{{ locale.account.settings.settingCards.beta }}</h2>
|
||||
</label>
|
||||
{{/if}}
|
||||
</form>
|
||||
</fieldset>
|
||||
{{#unless isTester}}
|
||||
<p>{{{ locale.account.settings.settingCards.upgradePrompt }}}</p>
|
||||
{{else}}
|
||||
<p>{{ locale.account.settings.settingCards.hasAccessPrompt }}</p>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
<h2 class="section-header" id="security">{{ locale.account.settings.settingCards.signInSecurity }}</h2>
|
||||
<div class="setting-card">
|
||||
<h2 class="header">{{ locale.account.account }}</h2>
|
||||
<a href="/account/edit/login-info" class="edit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<ul class="setting-list">
|
||||
<li>
|
||||
<p class="label">{{ locale.account.settings.settingCards.email }}</p>
|
||||
<p class="value">{{account.email.address}}</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="label">{{ locale.account.settings.settingCards.password }}</p>
|
||||
<p class="value">●●●●●●●●</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p>{{ locale.account.settings.settingCards.passwordResetNotice }}</p>
|
||||
</div>
|
||||
|
||||
<div class="setting-card sign-in-history">
|
||||
<h2 class="header">{{ locale.account.settings.settingCards.signInHistory }}</h2>
|
||||
<p>{{ locale.account.settings.settingCards.no_signins_notice }}</p>
|
||||
<ul class="setting-list">
|
||||
{{#each account.devices }}
|
||||
<li>
|
||||
<p class="label">{{this.device_attributes.name}}</p>
|
||||
<p class="value">{{this.device_attributes.created_date}}</p>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<a href="/account/sign-in-history">
|
||||
<button class="button secondary">{{ locale.account.settings.settingCards.fullSignInHistory }}</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 class="section-header" id="other">{{ locale.account.settings.settingCards.otherSettings }}</h2>
|
||||
<div class="setting-card">
|
||||
<h2 class="header">{{ locale.account.settings.settingCards.discord }}</h2>
|
||||
{{#if discordUser}}
|
||||
<p>{{ locale.account.settings.settingCards.connectedToDiscord }} {{ discordUser.username }}#{{ discordUser.discriminator }}</p>
|
||||
<a href="/account/remove/discord">
|
||||
<button class="button secondary" id="remove-discord-connection">{{ locale.account.settings.settingCards.removeDiscord }}</button>
|
||||
</a>
|
||||
{{else}}
|
||||
<p>{{ locale.account.settings.settingCards.noDiscordLinked }} <a href="{{ discordAuthURL }}">{{ locale.account.settings.settingCards.linkDiscord }}</a></p>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="setting-card">
|
||||
<h2 class="header">{{ locale.account.settings.settingCards.newsletter }}</h2>
|
||||
<p>{{ locale.account.settings.settingCards.no_newsletter_notice }}</p>
|
||||
<!--
|
||||
<form id="other">
|
||||
<input type="checkbox" id="marketing" name="marketing" {{#if account.flags.marketing}}checked{{/if}}>
|
||||
<label for="marketing">{{ locale.account.settings.settingCards.newsletterPrompt }}</label>
|
||||
</form>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{> footer }}
|
||||
</div>
|
||||
|
||||
{{#if success_message}}
|
||||
<div class="banner-notice success">
|
||||
<div>
|
||||
<p>{{success_message}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if error_message}}
|
||||
<div class="banner-notice error">
|
||||
<div>
|
||||
<p>{{error_message}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="modal-wrapper hidden" id="onlinefiles">
|
||||
<div class="modal">
|
||||
<h1 class="title dot">{{ locale.account.settings.settingCards.password }}</h1>
|
||||
<p class="modal-caption">{{ locale.account.settings.settingCards.passwordPrompt }}</p>
|
||||
<input name="password" id="password" type="password" required />
|
||||
<div class="modal-button-wrapper">
|
||||
<button class="button cancel" id="onlineFilesCloseButton">{{ locale.modals.cancel }}</button>
|
||||
<button class="button primary confirm" id="onlineFilesConfirmButton">{{ locale.modals.confirm }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-wrapper hidden" id="edit-settings">
|
||||
<div class="modal">
|
||||
<h1 class="title dot">{{ locale.account.settings.unavailable }}</h1>
|
||||
<p class="modal-caption">{{ locale.account.settings.settingCards.no_edit_from_dashboard }}</p>
|
||||
<div class="modal-button-wrapper">
|
||||
<button class="button primary confirm" id="editSettingsCloseButton">{{ locale.modals.close }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/account.js"></script>
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/login.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="account-form-wrapper">
|
||||
<form action="/account/login" method="post" class="account">
|
||||
<h2>{{ locale.account.loginForm.login }}</h2>
|
||||
<p>{{ locale.account.loginForm.detailsPrompt }}</p>
|
||||
<div>
|
||||
<label for="username">{{ locale.account.loginForm.username }}</label>
|
||||
<input name="username" id="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">{{ locale.account.loginForm.password }}</label>
|
||||
<input name="password" id="password" type="password" required>
|
||||
<a href="/account/passwordreset" class="pwdreset">{{ locale.account.loginForm.forgotPassword }}</a>
|
||||
</div>
|
||||
<input name="grant_type" id="grant_type" type="hidden" value="password">
|
||||
<input name="redirect" id="redirect" type="hidden" value="{{redirect}}">
|
||||
<div class="buttons">
|
||||
<button type="submit">{{ locale.account.loginForm.login }}</button>
|
||||
<a href="/account/register{{#if redirect}}?redirect={{redirect}}{{/if}}" class="register">{{ locale.account.loginForm.registerPrompt }}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if error}}
|
||||
<div class="banner-notice error">
|
||||
<div>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
@ -1,573 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/miieditor.css" />
|
||||
<div class="miieditor-wrapper">
|
||||
|
||||
<svg class="logotype" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 39.876" preserveAspectRatio="xMinYMin meet">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
|
||||
<g id="XMLID_6_" transform="translate(8.222 1.418)">
|
||||
<path id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
|
||||
<path id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
|
||||
<path id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
|
||||
</g>
|
||||
</g>
|
||||
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
|
||||
font-family="Poppins-Bold, Poppins" font-weight="700">
|
||||
<tspan x="0" y="0">Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="canvas-wrapper">
|
||||
<canvas id="miiCanvas" width="512" height="1080">
|
||||
Your browser does not support the canvas element.
|
||||
</canvas>
|
||||
</div>
|
||||
|
||||
<div class="params-wrapper">
|
||||
<div class="tabs">
|
||||
<button class="tabbtn active" id="faceTypeButton" style="--assetcol: 0"></button>
|
||||
<button class="tabbtn" id="hairTypeButton" style="--assetcol: 1"></button>
|
||||
<button class="tabbtn" id="eyebrowTypeButton" style="--assetcol: 2"></button>
|
||||
<button class="tabbtn" id="eyeTypeButton" style="--assetcol: 3"></button>
|
||||
<button class="tabbtn" id="noseTypeButton" style="--assetcol: 4"></button>
|
||||
<button class="tabbtn" id="mouthTypeButton" style="--assetcol: 5"></button>
|
||||
<button class="tabbtn" id="glassesTypeButton" style="--assetcol: 6"></button>
|
||||
<button class="tabbtn" id="beardTypeButton"style="--assetcol: 7"></button>
|
||||
<button class="tabbtn" id="moleEnabledButton" style="--assetcol: 8"></button>
|
||||
<button class="tabbtn" id="miiscButton" style="--assetcol: 9"></button>
|
||||
<button class="tabbtn" id="saveButton" style="--assetcol: 10"></button>
|
||||
</div>
|
||||
<form class="params">
|
||||
<div class="tab active" id="faceTypeTab">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="faceTypeSubButton" style="--assetcol: 0"></button>
|
||||
<button class="subtabbtn" id="makeupTypeSubButton" style="--assetcol: 1"></button>
|
||||
<button class="subtabbtn" id="wrinklesTypeSubButton" style="--assetcol: 2"></button>
|
||||
<button class="subtabbtn" id="skinColorSubButton" style="--assetcol: 12"></button>
|
||||
</div>
|
||||
|
||||
<fieldset class="subtab active" id="faceType" style="--assetrow: 0">
|
||||
{{#each editorJSON.face}}
|
||||
<input type="radio" name="faceType" id="faceType{{this}}" value="{{this}}" />
|
||||
<label for="faceType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="skinColor" class="subtab color">
|
||||
<input type="radio" name="skinColor" id="skinColor0" value="0" />
|
||||
<label for="skinColor0" style="background: #FFCE98 !important;" ></label>
|
||||
<input type="radio" name="skinColor" id="skinColor1" value="1" />
|
||||
<label for="skinColor1" style="background: #FFBA64 !important;" ></label>
|
||||
<input type="radio" name="skinColor" id="skinColor2" value="2" />
|
||||
<label for="skinColor2" style="background: #FF7C3E !important;" ></label>
|
||||
<input type="radio" name="skinColor" id="skinColor3" value="3" />
|
||||
<label for="skinColor3" style="background: #FFB184 !important;" ></label>
|
||||
<input type="radio" name="skinColor" id="skinColor4" value="4" />
|
||||
<label for="skinColor4" style="background: #CA5326 !important;" ></label>
|
||||
<input type="radio" name="skinColor" id="skinColor5" value="5" />
|
||||
<label for="skinColor5" style="background: #752E17 !important;" ></label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="makeupType" class="subtab" style="--assetrow: 1">
|
||||
{{#each editorJSON.arrayOf12}}
|
||||
<input type="radio" name="makeupType" id="makeupType{{this}}" value="{{this}}" />
|
||||
<label for="makeupType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="wrinklesType" class="subtab" style="--assetrow: 2">
|
||||
{{#each editorJSON.arrayOf12}}
|
||||
<input type="radio" name="wrinklesType" id="wrinklesType{{this}}" value="{{this}}" />
|
||||
<label for="wrinklesType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="tab" id="hairTypeTab" style="--assetrow: 3">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="hairTypeSubButton" style="--assetcol: 9"></button>
|
||||
<button class="subtabbtn" id="hairColorSubButton" style="--assetcol: 12"></button>
|
||||
</div>
|
||||
<fieldset id="hairType" class="subtab has-subpages active">
|
||||
{{#each editorJSON.hairs}}
|
||||
<div class="subpage" style="--subpage: {{@key}}">
|
||||
{{#each this}}
|
||||
<input type="radio" name="hairType" id="hairType{{this}}" value="{{this}}" />
|
||||
<label for="hairType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
<div class="pagination">
|
||||
<button class="previous page-btn index-{{@index}} {{#if @first }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,0,35.438,18,17.434,17.434,0,0,0,18,.563Zm8.156,20.531H18v4.985a.844.844,0,0,1-1.441.6L8.522,18.6a.836.836,0,0,1,0-1.188l8.037-8.086a.844.844,0,0,1,1.441.6v4.985h8.156A.846.846,0,0,1,27,15.75v4.5A.846.846,0,0,1,26.156,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span>
|
||||
<span class="current-page-index">{{@index}}</span>
|
||||
/ 11
|
||||
</span>
|
||||
<button class="next page-btn index-{{@index}} {{#if @last }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="pointer-events: none;"="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,1,.563,18,17.434,17.434,0,0,1,18,.563ZM9.844,21.094H18v4.985a.844.844,0,0,0,1.441.6L27.478,18.6a.836.836,0,0,0,0-1.188L19.441,9.323a.844.844,0,0,0-1.441.6v4.985H9.844A.846.846,0,0,0,9,15.75v4.5A.846.846,0,0,0,9.844,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="hairColor" class="subtab color">
|
||||
<input type="radio" name="hairColor" id="hairColor0" value="0" />
|
||||
<label for="hairColor0" style="background: #0A0A09 !important;" ></label>
|
||||
<input type="radio" name="hairColor" id="hairColor1" value="1" />
|
||||
<label for="hairColor1" style="background: #562D1B !important;" ></label>
|
||||
<input type="radio" name="hairColor" id="hairColor2" value="2" />
|
||||
<label for="hairColor2" style="background: #772314 !important;" ></label>
|
||||
<input type="radio" name="hairColor" id="hairColor3" value="3" />
|
||||
<label for="hairColor3" style="background: #9C481E !important;" ></label>
|
||||
<input type="radio" name="hairColor" id="hairColor4" value="4" />
|
||||
<label for="hairColor4" style="background: #988B8D !important;" ></label>
|
||||
<input type="radio" name="hairColor" id="hairColor5" value="5" />
|
||||
<label for="hairColor5" style="background: #664C1A !important;" ></label>
|
||||
<input type="radio" name="hairColor" id="hairColor6" value="6" />
|
||||
<label for="hairColor6" style="background: #AA6822 !important;" ></label>
|
||||
<input type="radio" name="hairColor" id="hairColor7" value="7" />
|
||||
<label for="hairColor7" style="background: #FEB454 !important;" ></label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="eyebrowTypeTab">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="eyebrowTypeSubButton" style="--assetcol: 3"></button>
|
||||
<button class="subtabbtn" id="eyebrowPositionSubButton" style="--assetcol: 13"></button>
|
||||
<button class="subtabbtn" id="eyebrowColorSubButton" style="--assetcol: 12"></button>
|
||||
</div>
|
||||
<fieldset id="eyebrowType" class="subtab has-subpages active" style="--assetrow: 4">
|
||||
{{#each editorJSON.eyebrows}}
|
||||
<div class="subpage" style="--subpage: {{@key}}">
|
||||
{{#each this}}
|
||||
<input type="radio" name="eyebrowType" id="eyebrowType{{this}}" value="{{this}}" />
|
||||
<label for="eyebrowType{{this}}" style="--assetcol: {{@key}}"><div></div></label>
|
||||
{{/each}}
|
||||
<div class="pagination">
|
||||
<button class="previous page-btn index-{{@index}} {{#if @first }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,0,35.438,18,17.434,17.434,0,0,0,18,.563Zm8.156,20.531H18v4.985a.844.844,0,0,1-1.441.6L8.522,18.6a.836.836,0,0,1,0-1.188l8.037-8.086a.844.844,0,0,1,1.441.6v4.985h8.156A.846.846,0,0,1,27,15.75v4.5A.846.846,0,0,1,26.156,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="current-page-index">{{@index}}</span> / 2
|
||||
<button class="next page-btn index-{{@index}} {{#if @last }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="pointer-events: none;"="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,1,.563,18,17.434,17.434,0,0,1,18,.563ZM9.844,21.094H18v4.985a.844.844,0,0,0,1.441.6L27.478,18.6a.836.836,0,0,0,0-1.188L19.441,9.323a.844.844,0,0,0-1.441.6v4.985H9.844A.846.846,0,0,0,9,15.75v4.5A.846.846,0,0,0,9.844,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="eyebrowPosition" class="subtab has-sliders">
|
||||
<label for="eyebrowYPosition" style="--assetcol: 2"></label>
|
||||
<input type="range" list="tickmarks" class="invert" name="eyebrowYPosition" id="eyebrowYPosition" min="3" max="18" />
|
||||
<label for="eyebrowSpacing" style="--assetcol: 4"></label>
|
||||
<input type="range" list="tickmarks" name="eyebrowSpacing" id="eyebrowSpacing" min="0" max="12" />
|
||||
<label for="eyebrowRotation" style="--assetcol: 5"></label>
|
||||
<input type="range" list="tickmarks" class="invert" name="eyebrowRotation" id="eyebrowRotation" min="0" max="11" />
|
||||
<label for="eyebrowScale" style="--assetcol: 6"></label>
|
||||
<input type="range" list="tickmarks" name="eyebrowScale" id="eyebrowScale" min="0" max="8" />
|
||||
<label for="eyebrowVerticalStretch" style="--assetcol: 7"></label>
|
||||
<input type="range" list="tickmarks" name="eyebrowVerticalStretch" id="eyebrowVerticalStretch" min="0" max="6" />
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="eyebrowColor" class="subtab color">
|
||||
<input type="radio" name="eyebrowColor" id="eyebrowColor0" value="0" />
|
||||
<label for="eyebrowColor0" style="background: #0A0A09 !important;" ></label>
|
||||
<input type="radio" name="eyebrowColor" id="eyebrowColor1" value="1" />
|
||||
<label for="eyebrowColor1" style="background: #562D1B !important;" ></label>
|
||||
<input type="radio" name="eyebrowColor" id="eyebrowColor2" value="2" />
|
||||
<label for="eyebrowColor2" style="background: #772314 !important;" ></label>
|
||||
<input type="radio" name="eyebrowColor" id="eyebrowColor3" value="3" />
|
||||
<label for="eyebrowColor3" style="background: #9C481E !important;" ></label>
|
||||
<input type="radio" name="eyebrowColor" id="eyebrowColor4" value="4" />
|
||||
<label for="eyebrowColor4" style="background: #988B8D !important;" ></label>
|
||||
<input type="radio" name="eyebrowColor" id="eyebrowColor5" value="5" />
|
||||
<label for="eyebrowColor5" style="background: #664C1A !important;" ></label>
|
||||
<input type="radio" name="eyebrowColor" id="eyebrowColor6" value="6" />
|
||||
<label for="eyebrowColor6" style="background: #AA6822 !important;" ></label>
|
||||
<input type="radio" name="eyebrowColor" id="eyebrowColor7" value="7" />
|
||||
<label for="eyebrowColor7" style="background: #FEB454 !important;" ></label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="eyeTypeTab">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="eyeTypeSubButton" style="--assetcol: 4"></button>
|
||||
<button class="subtabbtn" id="eyePositionSubButton" style="--assetcol: 13"></button>
|
||||
<button class="subtabbtn" id="eyeColorSubButton" style="--assetcol: 12"></button>
|
||||
</div>
|
||||
<fieldset id="eyeType" class="subtab has-subpages active" style="--assetrow: 5">
|
||||
{{#each editorJSON.eyes}}
|
||||
<div class="subpage" style="--subpage: {{@key}}">
|
||||
{{#each this}}
|
||||
<input type="radio" name="eyeType" id="eyeType{{this}}" value="{{this}}" />
|
||||
<label for="eyeType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
<div class="pagination">
|
||||
<button class="previous page-btn index-{{@index}} {{#if @first }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,0,35.438,18,17.434,17.434,0,0,0,18,.563Zm8.156,20.531H18v4.985a.844.844,0,0,1-1.441.6L8.522,18.6a.836.836,0,0,1,0-1.188l8.037-8.086a.844.844,0,0,1,1.441.6v4.985h8.156A.846.846,0,0,1,27,15.75v4.5A.846.846,0,0,1,26.156,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="current-page-index">{{@index}}</span> / 5
|
||||
<button class="next page-btn index-{{@index}} {{#if @last }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="pointer-events: none;"="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,1,.563,18,17.434,17.434,0,0,1,18,.563ZM9.844,21.094H18v4.985a.844.844,0,0,0,1.441.6L27.478,18.6a.836.836,0,0,0,0-1.188L19.441,9.323a.844.844,0,0,0-1.441.6v4.985H9.844A.846.846,0,0,0,9,15.75v4.5A.846.846,0,0,0,9.844,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="eyeColor" class="subtab color">
|
||||
<input type="radio" name="eyeColor" id="eyeColor0" value="0" />
|
||||
<label for="eyeColor0" style="background: #070606 !important;" ></label>
|
||||
<input type="radio" name="eyeColor" id="eyeColor1" value="1" />
|
||||
<label for="eyeColor1" style="background: #887E75 !important;" ></label>
|
||||
<input type="radio" name="eyeColor" id="eyeColor2" value="2" />
|
||||
<label for="eyeColor2" style="background: #814631 !important;" ></label>
|
||||
<input type="radio" name="eyeColor" id="eyeColor3" value="3" />
|
||||
<label for="eyeColor3" style="background: #796A34 !important;" ></label>
|
||||
<input type="radio" name="eyeColor" id="eyeColor4" value="4" />
|
||||
<label for="eyeColor4" style="background: #565BA4 !important;" ></label>
|
||||
<input type="radio" name="eyeColor" id="eyeColor5" value="5" />
|
||||
<label for="eyeColor5" style="background: #4C8260 !important;" ></label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="eyePosition" class="subtab has-sliders">
|
||||
<label for="eyeYPosition" style="--assetcol: 2"></label>
|
||||
<input type="range" list="tickmarks" class="invert" name="eyeYPosition" id="eyeYPosition" min="0" max="18" />
|
||||
<label for="eyeSpacing" style="--assetcol: 4"></label>
|
||||
<input type="range" list="tickmarks" name="eyeSpacing" id="eyeSpacing" min="0" max="12" />
|
||||
<label for="eyeRotation" style="--assetcol: 5"></label>
|
||||
<input type="range" class="invert" list="tickmarks" name="eyeRotation" id="eyeRotation" min="0" max="7" />
|
||||
<label for="eyeScale" style="--assetcol: 6"></label>
|
||||
<input type="range" list="tickmarks" name="eyeScale" id="eyeScale" min="0" max="7" />
|
||||
<label for="eyeVerticalStretch" style="--assetcol: 7"></label>
|
||||
<input type="range" list="tickmarks" name="eyeVerticalStretch" id="eyeVerticalStretch" min="0" max="6" />
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="noseTypeTab">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="noseTypeSubButton" style="--assetcol: 5"></button>
|
||||
<button class="subtabbtn" id="nosePositionSubButton" style="--assetcol: 13"></button>
|
||||
</div>
|
||||
<fieldset id="noseType" class="subtab has-subpages active" style="--assetrow: 6">
|
||||
{{#each editorJSON.nose}}
|
||||
<div class="subpage" style="--subpage: {{@key}}">
|
||||
{{#each this}}
|
||||
<input type="radio" name="noseType" id="noseType{{this}}" value="{{this}}" />
|
||||
<label for="noseType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
<div class="pagination">
|
||||
<button class="previous page-btn index-{{@index}} {{#if @first }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,0,35.438,18,17.434,17.434,0,0,0,18,.563Zm8.156,20.531H18v4.985a.844.844,0,0,1-1.441.6L8.522,18.6a.836.836,0,0,1,0-1.188l8.037-8.086a.844.844,0,0,1,1.441.6v4.985h8.156A.846.846,0,0,1,27,15.75v4.5A.846.846,0,0,1,26.156,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="current-page-index">{{@index}}</span> / 2
|
||||
<button class="next page-btn index-{{@index}} {{#if @last }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="pointer-events: none;"="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,1,.563,18,17.434,17.434,0,0,1,18,.563ZM9.844,21.094H18v4.985a.844.844,0,0,0,1.441.6L27.478,18.6a.836.836,0,0,0,0-1.188L19.441,9.323a.844.844,0,0,0-1.441.6v4.985H9.844A.846.846,0,0,0,9,15.75v4.5A.846.846,0,0,0,9.844,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="nosePosition" class="subtab has-sliders">
|
||||
<label for="noseYPosition" style="--assetcol: 2"></label>
|
||||
<input type="range" list="tickmarks" class="invert" name="noseYPosition" id="noseYPosition" min="0" max="18" />
|
||||
<label for="noseScale" style="--assetcol: 6"></label>
|
||||
<input type="range" list="tickmarks" name="noseScale" id="noseScale" min="0" max="8" />
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="mouthTypeTab">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="mouthTypeSubButton" style="--assetcol: 6"></button>
|
||||
<button class="subtabbtn" id="mouthPositionSubButton" style="--assetcol: 13"></button>
|
||||
<button class="subtabbtn" id="mouthColorSubButton" style="--assetcol: 12"></button>
|
||||
</div>
|
||||
<fieldset id="mouthType" class="subtab has-subpages active" style="--assetrow: 7">
|
||||
{{#each editorJSON.mouth}}
|
||||
<div class="subpage" style="--subpage: {{@key}}">
|
||||
{{#each this}}
|
||||
<input type="radio" name="mouthType" id="mouthType{{this}}" value="{{this}}" />
|
||||
<label for="mouthType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
<div class="pagination">
|
||||
<button class="previous page-btn index-{{@index}} {{#if @first }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,0,35.438,18,17.434,17.434,0,0,0,18,.563Zm8.156,20.531H18v4.985a.844.844,0,0,1-1.441.6L8.522,18.6a.836.836,0,0,1,0-1.188l8.037-8.086a.844.844,0,0,1,1.441.6v4.985h8.156A.846.846,0,0,1,27,15.75v4.5A.846.846,0,0,1,26.156,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="current-page-index">{{@index}}</span> / 3
|
||||
<button class="next page-btn index-{{@index}} {{#if @last }}disabled{{/if}}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="pointer-events: none;"="34.875" height="34.875" viewBox="0 0 34.875 34.875">
|
||||
<path id="Icon_awesome-arrow-alt-circle-right" data-name="Icon awesome-arrow-alt-circle-right" d="M18,.563A17.438,17.438,0,1,1,.563,18,17.434,17.434,0,0,1,18,.563ZM9.844,21.094H18v4.985a.844.844,0,0,0,1.441.6L27.478,18.6a.836.836,0,0,0,0-1.188L19.441,9.323a.844.844,0,0,0-1.441.6v4.985H9.844A.846.846,0,0,0,9,15.75v4.5A.846.846,0,0,0,9.844,21.094Z" transform="translate(-0.563 -0.563)" fill="#9d6ff3"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="mouthColor" class="subtab color">
|
||||
<input type="radio" name="mouthColor" id="mouthColor0" value="0" />
|
||||
<label for="mouthColor0" style="background: #FF5D0C !important;" ></label>
|
||||
<input type="radio" name="mouthColor" id="mouthColor1" value="1" />
|
||||
<label for="mouthColor1" style="background: #FF110C !important;" ></label>
|
||||
<input type="radio" name="mouthColor" id="mouthColor2" value="2" />
|
||||
<label for="mouthColor2" style="background: #FF524B !important;" ></label>
|
||||
<input type="radio" name="mouthColor" id="mouthColor3" value="3" />
|
||||
<label for="mouthColor3" style="background: #FFAA77 !important;" ></label>
|
||||
<input type="radio" name="mouthColor" id="mouthColor4" value="4" />
|
||||
<label for="mouthColor4" style="background: #181312 !important;" ></label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="mouthPosition" class="subtab has-sliders">
|
||||
<label for="mouthYPosition" style="--assetcol: 2"></label>
|
||||
<input type="range" list="tickmarks" class="invert" name="mouthYPosition" id="mouthYPosition" min="0" max="18" />
|
||||
<label for="mouthScale" style="--assetcol: 6"></label>
|
||||
<input type="range" list="tickmarks" name="mouthScale" id="mouthScale" min="0" max="8" />
|
||||
<label for="mouthHorizontalStretch" style="--assetcol: 7"></label>
|
||||
<input type="range" list="tickmarks" name="mouthHorizontalStretch" id="mouthHorizontalStretch" min="0" max="6" />
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="glassesTypeTab">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="glassesTypeSubButton" style="--assetcol: 7"></button>
|
||||
<button class="subtabbtn" id="glassesPositionSubButton" style="--assetcol: 13"></button>
|
||||
<button class="subtabbtn" id="glassesColorSubButton" style="--assetcol: 12"></button>
|
||||
</div>
|
||||
<fieldset id="glassesType" class="subtab active" style="--assetrow: 8">
|
||||
{{#each editorJSON.arrayOf9}}
|
||||
<input type="radio" name="glassesType" id="glassesType{{this}}" value="{{this}}" />
|
||||
<label for="glassesType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="glassesColor" class="subtab color">
|
||||
<input type="radio" name="glassesColor" id="glassesColor0" value="0" />
|
||||
<label for="glassesColor0" style="background: #000000 !important;" ></label>
|
||||
<input type="radio" name="glassesColor" id="glassesColor1" value="1" />
|
||||
<label for="glassesColor1" style="background: #7A4011 !important;" ></label>
|
||||
<input type="radio" name="glassesColor" id="glassesColor2" value="2" />
|
||||
<label for="glassesColor2" style="background: #D51208 !important;" ></label>
|
||||
<input type="radio" name="glassesColor" id="glassesColor3" value="3" />
|
||||
<label for="glassesColor3" style="background: #29376E !important;" ></label>
|
||||
<input type="radio" name="glassesColor" id="glassesColor4" value="4" />
|
||||
<label for="glassesColor4" style="background: #D56D00 !important;" ></label>
|
||||
<input type="radio" name="glassesColor" id="glassesColor5" value="5" />
|
||||
<label for="glassesColor5" style="background: #987F6E !important;" ></label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="glassesPosition" class="subtab has-sliders">
|
||||
<label for="glassesYPosition" style="--assetcol: 2"></label>
|
||||
<input type="range" list="tickmarks" class="invert" name="glassesYPosition" id="glassesYPosition" min="0" max="20" />
|
||||
<label for="glassesScale" style="--assetcol: 6"></label>
|
||||
<input type="range" list="tickmarks" name="glassesScale" id="glassesScale" min="0" max="7" />
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="beardTypeTab">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="beardTypeSubButton" style="--assetcol: 8"></button>
|
||||
<button class="subtabbtn" id="mustacheTypeSubButton" style="--assetcol: 9"></button>
|
||||
<button class="subtabbtn" id="facialHairPositionSubButton" style="--assetcol: 13"></button>
|
||||
<button class="subtabbtn" id="facialHairColorSubButton" style="--assetcol: 12"></button>
|
||||
</div>
|
||||
<fieldset id="beardType" class="subtab active" style="--assetrow: 9">
|
||||
{{#each editorJSON.arrayOf6}}
|
||||
<input type="radio" name="beardType" id="beardType{{this}}" value="{{this}}" />
|
||||
<label for="beardType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="facialHairColor" class="subtab color">
|
||||
<input type="radio" name="facialHairColor" id="facialHairColor0" value="0" />
|
||||
<label for="facialHairColor0" style="background: #0A0A09 !important;" ></label>
|
||||
<input type="radio" name="facialHairColor" id="facialHairColor1" value="1" />
|
||||
<label for="facialHairColor1" style="background: #562D1B !important;" ></label>
|
||||
<input type="radio" name="facialHairColor" id="facialHairColor2" value="2" />
|
||||
<label for="facialHairColor2" style="background: #772314 !important;" ></label>
|
||||
<input type="radio" name="facialHairColor" id="facialHairColor3" value="3" />
|
||||
<label for="facialHairColor3" style="background: #9C481E !important;" ></label>
|
||||
<input type="radio" name="facialHairColor" id="facialHairColor4" value="4" />
|
||||
<label for="facialHairColor4" style="background: #988B8D !important;" ></label>
|
||||
<input type="radio" name="facialHairColor" id="facialHairColor5" value="5" />
|
||||
<label for="facialHairColor5" style="background: #664C1A !important;" ></label>
|
||||
<input type="radio" name="facialHairColor" id="facialHairColor6" value="6" />
|
||||
<label for="facialHairColor6" style="background: #AA6822 !important;" ></label>
|
||||
<input type="radio" name="facialHairColor" id="facialHairColor7" value="7" />
|
||||
<label for="facialHairColor7" style="background: #FEB454 !important;" ></label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="mustacheType" class="subtab active" style="--assetrow: 10">
|
||||
{{#each editorJSON.arrayOf6}}
|
||||
<input type="radio" name="mustacheType" id="mustacheType{{this}}" value="{{this}}" />
|
||||
<label for="mustacheType{{this}}" style="--assetcol: {{@key}}"></label>
|
||||
{{/each}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="facialHairPosition" class="subtab has-sliders">
|
||||
<label for="mustacheYPosition" style="--assetcol: 2"></label>
|
||||
<input type="range" list="tickmarks" class="invert" name="mustacheYPosition" id="mustacheYPosition" min="0" max="16" />
|
||||
<label for="mustacheScale" style="--assetcol: 6"></label>
|
||||
<input type="range" list="tickmarks" name="mustacheScale" id="mustacheScale" min="0" max="8" />
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="moleEnabledTab">
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="moleEnabledSubButton" style="--assetcol: 10"></button>
|
||||
<button class="subtabbtn" id="molePositionSubButton" style="--assetcol: 13"></button>
|
||||
</div>
|
||||
<fieldset id="moleEnabled" class="subtab active" style="--assetrow: 11">
|
||||
<input type="radio" name="moleEnabled" id="moleEnabledfalse" value="false" />
|
||||
<label for="moleEnabledfalse" style="--assetcol: 0"></label>
|
||||
<input type="radio" name="moleEnabled" id="moleEnabledtrue" value="true" />
|
||||
<label for="moleEnabledtrue" style="--assetcol: 1"></label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="molePosition" class="subtab has-sliders">
|
||||
<label for="moleYPosition" style="--assetcol: 2"></label>
|
||||
<input type="range" list="tickmarks" class="invert" name="moleYPosition" id="moleYPosition" min="0" max="30" />
|
||||
<label for="moleXPosition" style="--assetcol: 3"></label>
|
||||
<input type="range" list="tickmarks" name="moleXPosition" id="moleXPosition" min="0" max="16" />
|
||||
<label for="moleScale" style="--assetcol: 6"></label>
|
||||
<input type="range" list="tickmarks" name="moleScale" id="moleScale" min="0" max="8" />
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="miiscTab"><!--Get it it's funny 'cause 'mii' and 'misc'-->
|
||||
<div class="subtabs">
|
||||
<button class="subtabbtn active" id="sizeSubButton" style="--assetcol: 14"></button>
|
||||
<button class="subtabbtn" id="genderSubButton" style="--assetcol: 11"></button>
|
||||
<button class="subtabbtn" id="favoriteColorSubButton" style="--assetcol: 12"></button>
|
||||
<button class="subtabbtn" id="infoSubButton" style="--assetcol: 15"></button>
|
||||
</div>
|
||||
|
||||
<fieldset id="size" class="subtab active has-sliders">
|
||||
<label for="height" style="--assetcol: 0"></label>
|
||||
<input type="range" list="tickmarks" name="height" id="height" min="0" max="127" />
|
||||
<label for="build" style="--assetcol: 1"></label>
|
||||
<input type="range" list="tickmarks" name="build" id="build" min="0" max="127" />
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="gender" class="subtab" style="--assetrow: 12">
|
||||
<input type="radio" name="gender" id="gender0" value="0" />
|
||||
<label for="gender0" style="--assetcol: 0"></label>
|
||||
<input type="radio" name="gender" id="gender1" value="1" />
|
||||
<label for="gender1" style="--assetcol: 1"></label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="info" class="subtab active has-textinput">
|
||||
<div>
|
||||
<label for="miiName">Nickname</label>
|
||||
<input type="text" name="miiName" id="miiName" minLength="1" maxLength="10" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="creatorName">Creator</label>
|
||||
<input type="text" name="creatorName" id="creatorName" minLength="1" maxLength="10" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="birthDate">Birthday</label>
|
||||
<input type="date" name="birthDate" id="birthDate" min="2024-01-01" max="2024-12-31"/>
|
||||
</div>
|
||||
<div class="icons">
|
||||
<div>
|
||||
<label for="favorite">Favorite</label>
|
||||
<input type="checkbox" name="favorite" id="favorite" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="disableSharing">Sharing</label>
|
||||
<input type="checkbox" name="disableSharing" id="disableSharing" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="allowCopying">Copying</label>
|
||||
<input type="checkbox" name="allowCopying" id="allowCopying" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="favoriteColor" class="subtab">
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor0" value="0" />
|
||||
<label for="favoriteColor0" style="background: #FF2216 !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor1" value="1" />
|
||||
<label for="favoriteColor1" style="background: #FF7A1A !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor2" value="2" />
|
||||
<label for="favoriteColor2" style="background: #FFED22 !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor3" value="3" />
|
||||
<label for="favoriteColor3" style="background: #91F321 !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor4" value="4" />
|
||||
<label for="favoriteColor4" style="background: #008332 !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor5" value="5" />
|
||||
<label for="favoriteColor5" style="background: #0D50BE !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor6" value="6" />
|
||||
<label for="favoriteColor6" style="background: #49BBE4 !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor7" value="7" />
|
||||
<label for="favoriteColor7" style="background: #FF6381 !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor8" value="8" />
|
||||
<label for="favoriteColor8" style="background: #8B2DB2 !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor9" value="9" />
|
||||
<label for="favoriteColor9" style="background: #573D18 !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor10" value="10" />
|
||||
<label for="favoriteColor10" style="background: #FFFFFF !important;" ></label>
|
||||
<input type="radio" name="favoriteColor" id="favoriteColor11" value="11" />
|
||||
<label for="favoriteColor11" style="background: #1D1A15 !important;" ></label>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="tab" id="saveTab">
|
||||
<p class="save-prompt">Save Mii? This will permanently overwrite your current Mii.</p>
|
||||
<div class="mii-comparison-animation-wrapper">
|
||||
<div class="mii-comparison unconfirmed">
|
||||
<img class="old-mii" src=""></img>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--mdi" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="#888888" d="M4 15V9h8V4.16L19.84 12L12 19.84V15H4Z"></path></svg>
|
||||
<div class="new-mii-wrapper">
|
||||
<img class="new-mii" src=""></img>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mii-comparison confirmed">
|
||||
<img class="old-mii" src=""></img>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--mdi" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill="#888888" d="M4 15V9h8V4.16L19.84 12L12 19.84V15H4Z"></path></svg>
|
||||
<div class="new-mii-wrapper">
|
||||
<img class="new-mii" src=""></img>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="button primary" id="saveButton">Save!</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script id="encodedUserMiiData" type="text/plain">
|
||||
{{encodedUserMiiData}}
|
||||
</script>
|
||||
<script src="/assets/js/miieditor.bundled.js"></script>
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
||||
<link rel="stylesheet" href="/assets/css/login.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="account-form-wrapper">
|
||||
<form action="/account/register" method="post" class="account register">
|
||||
<h2>{{ locale.account.loginForm.register }}</h2>
|
||||
<p>{{ locale.account.loginForm.detailsPrompt }}</p>
|
||||
<div class="email">
|
||||
<label for="email">{{ locale.account.loginForm.email }}</label>
|
||||
<input name="email" id="email" type="email" value="{{ email }}" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">{{ locale.account.loginForm.username }}</label>
|
||||
<input name="username" id="username" value="{{ username }}" minlength=6 maxlength=16 required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="mii_name">{{ locale.account.loginForm.miiName }}</label>
|
||||
<input name="mii_name" id="mii_name" value="{{ mii_name }}" maxlength=10 required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">{{ locale.account.loginForm.password }}</label>
|
||||
<input name="password" id="password" type="password" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password_confirm">{{ locale.account.loginForm.confirmPassword }}</label>
|
||||
<input name="password_confirm" id="password_confirm" type="password" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div class="h-captcha" data-sitekey="cf3fd74e-93ca-47e6-9fa0-5fc439de06d4"></div>
|
||||
<input name="redirect" id="redirect" type="hidden" value="{{redirect}}">
|
||||
<div class="buttons">
|
||||
<button type="submit">{{ locale.account.loginForm.register }}</button>
|
||||
<a href="/account/login{{#if redirect}}?redirect={{redirect}}{{/if}}" class="register">{{ locale.account.loginForm.loginPrompt }}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if error}}
|
||||
<div class="banner-notice error">
|
||||
<div>
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/upgrade.css" />
|
||||
<div class="wrapper">
|
||||
<a href="/account" class="back-arrow">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-left"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
|
||||
<span>{{ locale.upgrade.back }}</span>
|
||||
</a>
|
||||
|
||||
<div class="account-form-wrapper">
|
||||
<a class="logotype" href="/">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="39.876">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
|
||||
<g id="XMLID_6_" transform="translate(8.222 1.418)">
|
||||
<path id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
|
||||
<path id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
|
||||
<path id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
|
||||
</g>
|
||||
</g>
|
||||
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
|
||||
font-family="Poppins-Bold, Poppins" font-weight="700">
|
||||
<tspan x="0" y="0">Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<h1 class="title dot">{{ locale.upgrade.title }}</h1>
|
||||
<p class="caption">{{ locale.upgrade.description }}</p>
|
||||
|
||||
<div class="progress-bar-wrapper">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar-inner" style="width: {{ donationCache.completed_capped }}%;" ></div>
|
||||
</div>
|
||||
<p class="localeReplace">
|
||||
{{{ locale.donation.progress }}}
|
||||
</p>
|
||||
</div>
|
||||
<form method="post" data-current-tier="{{currentTier}}">
|
||||
{{#each tiers}}
|
||||
<input type="radio" class="tier-radio" data-tier-name="{{this.name}}" name="tier" value="{{this.price_id}}" id="{{this.price_id}}" />
|
||||
<label class="tier" for="{{this.price_id}}">
|
||||
<div class="tier-thumbnail">
|
||||
<img src="{{this.thumbnail}}" width="100%" height="auto" alt="Tier icon" />
|
||||
</div>
|
||||
<div class="tier-text">
|
||||
<p class="tier-name">{{this.name}}</p>
|
||||
<div class="tier-perks">
|
||||
{{#each this.perks}}
|
||||
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
<p>{{this}}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<p class="price">
|
||||
<span>{{this.price}}</span> / {{ ../locale.upgrade.month }}
|
||||
</p>
|
||||
</label>
|
||||
{{/each}}
|
||||
|
||||
<div class="button-wrapper">
|
||||
<button class="disabled" type="submit" id="submitButton">{{ locale.upgrade.tierSelectPrompt }}</button>
|
||||
<button class="unsubscribe hidden" id="unsubModalShowButton">{{ locale.upgrade.unsub }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-wrapper hidden" id="unsub">
|
||||
<div class="modal">
|
||||
<h1 class="title dot">{{ locale.upgrade.unsub }}</h1>
|
||||
<p class="modal-caption">{{{ locale.upgrade.unsubPrompt }}}</p>
|
||||
<div class="modal-button-wrapper">
|
||||
<button class="button cancel" id="unsubModalCloseButton">{{ locale.modals.cancel }}</button>
|
||||
<button class="button primary confirm" id="unsubModalConfirmButton">{{ locale.upgrade.unsubConfirm }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-wrapper hidden" id="switchtier">
|
||||
<div class="modal">
|
||||
<h1 class="title dot">{{ locale.upgrade.changeTier }}</h1>
|
||||
<p class="modal-caption">{{{ locale.upgrade.changeTierPrompt }}}</p>
|
||||
<div class="modal-button-wrapper">
|
||||
<button class="button cancel" id="switchTierCloseButton">{{ locale.modals.cancel }}</button>
|
||||
<button class="button primary confirm" id="switchTierConfirmButton">{{ locale.modals.confirm }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* this jank is needed to have data mixed in with a localized string */
|
||||
document.querySelector('.localeReplace').innerHTML = document.querySelector('.localeReplace').innerHTML
|
||||
.replace('${totd}', '{{donationCache.total_dollars}}');
|
||||
document.querySelector('.localeReplace').innerHTML = document.querySelector('.localeReplace').innerHTML
|
||||
.replace('${goald}', '{{donationCache.goal_dollars}}');
|
||||
document.querySelector('.localeReplace').innerHTML = document.querySelector('.localeReplace').innerHTML
|
||||
.replace('${perc}', '{{donationCache.completed_real}}');
|
||||
</script>
|
||||
|
||||
<script src="/assets/js/upgrade.js" />
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/aprilfools.css" />
|
||||
|
||||
<!-- The same font Big N uses. Licence legally obtained through Adobe Fonts. -->
|
||||
<link rel="stylesheet" href="https://use.typekit.net/gok5tsu.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="announcement-hero new-font">
|
||||
<p><span class="pretendo">Pretendo Network</span> is now</p>
|
||||
<h1 class="new-font">Nintendo Switch Online<br>+<br>Legacy Pack!</h1>
|
||||
</div>
|
||||
|
||||
<div class="subscribe bro-what new-font">
|
||||
<h1>Power up now with one easy click!
|
||||
<a href="#footnote-1">
|
||||
<sup>1</sup>
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
<a href="https://youtu.be/2Gc5cuekQto?t=10" class="buy-now" target="_blank">
|
||||
<button class="button primary">
|
||||
Buy now
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bro-what new-font">
|
||||
<h1>What is Nintendo Switch Online + Legacy Pack?</h1>
|
||||
<p>Nintendo Switch Online + Legacy Pack is the new membership that gives you access to all first-party online servers for Nintendo Wii U and Nintendo 3DS, with no added costs
|
||||
<a href="#footnote-2">
|
||||
<sup>2</sup>
|
||||
</a>
|
||||
!</p>
|
||||
|
||||
<p>Experience the Nintendo Wii U's infinite
|
||||
<a href="#footnote-3">
|
||||
<sup>3</sup>
|
||||
</a>
|
||||
library of first party games like never before, with fan-favorite titles such as Mario Kart 8 and Super Mario Maker making a return, and challenge your friends on the go with the Nintendo 3DS's Super Smash Bros. and Miitopia!</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bro-what dotted-bg new-font">
|
||||
<h1>Why subscribe to Nintendo Switch Online + Legacy Pack when the online servers are still up and most first-party games have already been ported to the Nintendo Switch?</h1>
|
||||
<p>Uhhhhhhhhhhhhhhhhh next question.</p>
|
||||
</div>
|
||||
|
||||
<div class="bro-what new-font">
|
||||
<h1>Is this an April Fools' joke?</h1>
|
||||
<p>No.
|
||||
<a href="#footnote-4">
|
||||
<sup>4</sup>
|
||||
</a>
|
||||
<br>
|
||||
We've been working closely with Nintendo behind the scenes for quite some time
|
||||
<a href="#footnote-5">
|
||||
<sup>5</sup>
|
||||
</a>
|
||||
, and today's date was chosen completely at random. You can check out Nintendo's announcement in the latest Nintendo Direct here: <a href="https://youtu.be/yPYZpwSpKmA" target="_blank">What you need to know about the new Nintendo Switch Online + Legacy Pack membership plan.</a></p>
|
||||
</div>
|
||||
|
||||
<div class="bro-what footnotes new-font">
|
||||
<p id="footnote-1"><sup>1</sup> May require signing your soul over to Nintendo Co., Ltd.</p>
|
||||
<p id="footnote-2"><sup>2</sup> Excluding costs for other subscriptions required to use the Nintendo Switch Online + Legacy Pack subscription plan. Required subscriptions include but are not limited to: Nintendo Switch Online, Nintendo Switch Online + Expansion Pack, Nintendo Switch Online + Rumble Pak, Nintendo Switch Online + Offline Play Pack, Nintendo Switch Online + Yet Another Port Pack, Nintendo Switch Online + Dr. Kawashima's Brain Training / Brain Age "You Really Liked The Nintendo Wii U Virtual Console Title, So We're Bringing It Back" Pack. Total costs (Excl. Tax) $299.99 billed twice every 2 months.</p>
|
||||
<p id="footnote-3"><sup>3</sup> Statement not legally binding. Rounding up.</p>
|
||||
<p id="footnote-4"><sup>4</sup> For legal reasons, that's a joke.
|
||||
<p id="footnote-5"><sup>5</sup> Pretendo Network is in no way associated with Nintendo Co., Ltd. or any of its subsidiaries.</p>
|
||||
<p>Nintendo, Nintendo Wii U, Nintendo 3DS, Mario Kart 8, Super Mario Maker, Super Smash Bros., Miitopia, Nintendo Switch, Nintendo Switch Online, Nintendo Switch Online +, Nintendo Switch Online + Expansion Pack, and Rumble Pak are copyrights of Nintendo Co., Ltd. and/or its subsidiaries.<br>Happy April Fools'!</p>
|
||||
</div>
|
||||
|
||||
{{> footer }}
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelector("#Pretendo").setAttribute("font-family", "museo-sans, sans-serif");
|
||||
document.querySelector("#Pretendo > tspan").innerHTML = "Nintendo Switch Online + Legacy Pack";
|
||||
document.querySelector(".logo-link > svg").setAttribute("width", 350);
|
||||
</script>
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
|
||||
|
||||
<channel>
|
||||
<title>Pretendo Network Blog</title>
|
||||
<link>https://pretendo.network/blog</link>
|
||||
<atom:link href="https://pretendo.network/blog/feed.xml" rel="self" type="application/rss+xml" />
|
||||
<description>The latest updates in condensed chunks.</description>
|
||||
<pubDate>{{ posts.[0].postInfo.pubDate }}</pubDate>
|
||||
<ttl>30</ttl>
|
||||
<category>Web development</category>
|
||||
<image>
|
||||
<url>https://pretendo.network/assets/images/opengraph/opengraph-image.png</url>
|
||||
<title>Pretendo Network Blog</title>
|
||||
<link>https://pretendo.network/blog</link>
|
||||
</image>
|
||||
|
||||
{{#each posts }}
|
||||
<item>
|
||||
<title>{{ this.postInfo.title }}</title>
|
||||
<description>{{ this.postInfo.caption }}</description>
|
||||
<link>https://pretendo.network/blog/{{this.slug}}</link>
|
||||
<guid>https://pretendo.network/blog/{{this.slug}}</guid>
|
||||
<pubDate>{{ this.postInfo.pubDate }}</pubDate>
|
||||
<enclosure url="{{ this.postInfo.cover_image }}" length="0" type="image/{{ this.postInfo.cover_extension }}"/>
|
||||
</item>
|
||||
{{/each}}
|
||||
|
||||
</channel>
|
||||
|
||||
</rss>
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/blog.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper blog">
|
||||
|
||||
<div class="progress-hero">
|
||||
<div class="hero-meta reduced-margin">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48.87" height="71.093" viewBox="0 0 48.87 71.093"><g id="XMLID_6_" transform="translate(0)"><path id="XMLID_15_" d="M69.581,29.593c-2.029,1.068-.249,4.129,1.78,3.061,5.162-2.67,11.463-2.6,16.981-1.1,4.735,1.282,9.5,3.845,12.246,8.045,1.246,1.922,4.307.142,3.061-1.78C96.921,27.386,80.3,24.04,69.581,29.593Z" transform="translate(-60.112 -20.086)" fill="#9d6ff3"/><path id="XMLID_14_" d="M103.359,21.045c-3.951-6.159-10.751-10-17.657-11.89C77.763,6.948,68.721,7.019,61.281,10.9c-2.029,1.068-.249,4.129,1.78,3.061,6.586-3.453,14.667-3.311,21.644-1.388,5.981,1.638,12.1,4.913,15.521,10.252C101.507,24.783,104.569,23,103.359,21.045Z" transform="translate(-54.766 -7.693)" fill="#9d6ff3"/><path id="XMLID_9_" d="M65.995,47.8a20.7,20.7,0,0,0-12.958,4.45H47.27a2.579,2.579,0,0,0-2.67,2.456v47.239a2.763,2.763,0,0,0,2.67,2.67h5.838a2.639,2.639,0,0,0,2.528-2.67V87.564A21.228,21.228,0,1,0,65.995,47.8Zm0,33.178a12,12,0,1,1,12-12A12,12,0,0,1,65.995,80.978Z" transform="translate(-44.6 -33.522)" fill="#9d6ff3"/></g></svg>
|
||||
<h1 class="title dot">{{ locale.blogPage.title }}</h1>
|
||||
<p class="text">{{{ locale.blogPage.description }}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#each postList }}
|
||||
<a href="/blog/{{this.slug}}" class="purple-card blog-card">
|
||||
<div class="post-info">
|
||||
<h2 class="title">{{{ this.postInfo.title }}}</h2>
|
||||
<p class="caption">{{{ this.postInfo.caption }}}</p>
|
||||
<div class="pub-info">
|
||||
<span>{{ ../locale.blogPage.published }}</span>
|
||||
<div class="profile">
|
||||
<img class="profile-picture" src="{{ this.postInfo.author_image }}" alt>
|
||||
<span>{{{ this.postInfo.author }}}</span>
|
||||
</div>
|
||||
<span>{{ ../locale.blogPage.publishedOn }}
|
||||
<span class="date">{{{ this.postInfo.date }}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cover" style="background: no-repeat center/cover url({{this.postInfo.cover_image}}">
|
||||
</div>
|
||||
</a>
|
||||
{{/each}}
|
||||
|
||||
<div class="buttons">
|
||||
<!-- TODO: implement pagination -->
|
||||
<a href="/blog/feed.xml" target="_blank" title="RSS feed">
|
||||
<button class="button secondary github icon-btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rss"><path d="M4 11a9 9 0 0 1 9 9"></path><path d="M4 4a16 16 0 0 1 16 16"></path><circle cx="5" cy="19" r="1"></circle></svg> </button>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{> footer }}
|
||||
</div>
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/blogpost.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper blog">
|
||||
|
||||
<div class="card-wrap">
|
||||
<div class="purple-card blog-card">
|
||||
|
||||
<h1 class="title">{{{ postInfo.title }}}</h1>
|
||||
<div class="pub-info">
|
||||
<span>{{ locale.blogPage.published }}</span>
|
||||
<div class="profile">
|
||||
<img class="profile-picture" src="{{ postInfo.author_image }}" alt="{{ postInfo.author }}">
|
||||
<span>{{{ postInfo.author }}}</span>
|
||||
</div>
|
||||
<span>{{ locale.blogPage.publishedOn }}
|
||||
<span class="date">{{{ postInfo.date }}}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{{{ htmlPost }}}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{> footer }}
|
||||
|
||||
</div>
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
|
||||
<link rel="stylesheet" href="/assets/css/highlightjs.css">
|
||||
<script>hljs.highlightAll();</script>
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/documentation.css" />
|
||||
|
||||
<div class="docs-wrapper">
|
||||
|
||||
<a href="/" class="logo-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="39.876">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
|
||||
<g id="XMLID_6_" transform="translate(8.222 1.418)">
|
||||
<path id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
|
||||
<path id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
|
||||
<path id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
|
||||
</g>
|
||||
</g>
|
||||
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
|
||||
font-family="Poppins-Bold, Poppins" font-weight="700">
|
||||
<tspan x="0" y="0">Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div class="header-wrapper">
|
||||
<button id="openSidebar" class="nostyle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
||||
</button>
|
||||
{{> header}}
|
||||
</div>
|
||||
|
||||
{{> docs-sidebar}}
|
||||
|
||||
<div class="content">
|
||||
<div class="content-inner">
|
||||
|
||||
{{#if missingInLocale}}
|
||||
<p class="missing-in-locale-notice">{{ locale.docs.missingInLocale }}</p>
|
||||
{{/if}}
|
||||
|
||||
{{{ content }}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/docssidebarhandler.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
|
||||
<link rel="stylesheet" href="/assets/css/highlightjs.css">
|
||||
<script>hljs.highlightAll();</script>
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/documentation.css" />
|
||||
|
||||
<div class="docs-wrapper">
|
||||
|
||||
<a href="/" class="logo-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="39.876">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
|
||||
<g id="XMLID_6_" transform="translate(8.222 1.418)">
|
||||
<path id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
|
||||
<path id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
|
||||
<path id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
|
||||
</g>
|
||||
</g>
|
||||
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
|
||||
font-family="Poppins-Bold, Poppins" font-weight="700">
|
||||
<tspan x="0" y="0">Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div class="header-wrapper">
|
||||
<button id="openSidebar" class="nostyle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
||||
</button>
|
||||
{{> header}}
|
||||
</div>
|
||||
|
||||
{{> docs-sidebar}}
|
||||
|
||||
<div class="content">
|
||||
<div class="content-inner">
|
||||
<h1>Installation</h1>
|
||||
<p>
|
||||
Please choose your platform below.
|
||||
</p>
|
||||
<div class="platform-grid">
|
||||
<a href="/docs/install/wiiu">
|
||||
<img src="/assets/images/docs/install/icons/wiiu-gamepad.svg" alt="Illustration of a Wii U Gamepad" />
|
||||
<span>Wii U</span>
|
||||
</a>
|
||||
<a href="/docs/install/3ds">
|
||||
<img src="/assets/images/docs/install/icons/2ds.svg" alt="Illustration of a New Nintendo 2DS XL" />
|
||||
<span>3DS Family</span>
|
||||
</a>
|
||||
<a href="/docs/install/cemu">
|
||||
<img src="/assets/images/docs/install/icons/cemu.svg" alt="Cemu logo" />
|
||||
<span>Cemu</span>
|
||||
</a>
|
||||
<a href="/docs/install/citra">
|
||||
<img src="/assets/images/docs/install/icons/citra.svg" alt="Citra logo" />
|
||||
<span>Citra</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/docssidebarhandler.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
|
||||
<link rel="stylesheet" href="/assets/css/highlightjs.css">
|
||||
<script>hljs.highlightAll();</script>
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/documentation.css" />
|
||||
|
||||
<div class="docs-wrapper">
|
||||
|
||||
<a href="/" class="logo-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="39.876">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
|
||||
<g id="XMLID_6_" transform="translate(8.222 1.418)">
|
||||
<path id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
|
||||
<path id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
|
||||
<path id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
|
||||
</g>
|
||||
</g>
|
||||
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
|
||||
font-family="Poppins-Bold, Poppins" font-weight="700">
|
||||
<tspan x="0" y="0">Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div class="header-wrapper">
|
||||
<button id="openSidebar" class="nostyle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>
|
||||
</button>
|
||||
{{> header}}
|
||||
</div>
|
||||
|
||||
{{> docs-sidebar}}
|
||||
|
||||
<div class="content search">
|
||||
<div class="content-inner">
|
||||
<div class="purple-card">
|
||||
<h1>{{ locale.docs.search.title }}</h1>
|
||||
<p>{{ locale.docs.search.caption }}</p>
|
||||
<label for="errorCode">{{ locale.docs.search.label }}</label>
|
||||
<div class="input-wrapper">
|
||||
<input id="errorCode" placeholder="ABC-012-3456" required />
|
||||
<div class="matches">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/docssidebarhandler.js"></script>
|
||||
|
||||
<script id="errorList" type="application/json">
|
||||
{{{errorList}}}
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/fuse.js@6.6.2/dist/fuse.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/cleave.js@1.6.0/dist/cleave.min.js"></script>
|
||||
|
||||
<script>
|
||||
// Get the error list from the script tag
|
||||
let rawErrorList = JSON.parse(document.getElementById('errorList').innerHTML);
|
||||
|
||||
// Convert the error list to a single flat array
|
||||
rawErrorList = Object.values(rawErrorList).flat();
|
||||
|
||||
// We remove the unknown characters (e.g. the X in JXT-598-2XXX), so fuse.js will be able to match the error codes
|
||||
const errorList = rawErrorList.map(error => {
|
||||
return error.replace(/X*$/g, '');
|
||||
});
|
||||
|
||||
const fuse = new Fuse(errorList, {includeMatches: true, threshold: 0.4});
|
||||
const cleave = new Cleave('input#errorCode', {
|
||||
delimiter: '-',
|
||||
blocks: [3, 3, 4],
|
||||
uppercase: true
|
||||
});
|
||||
|
||||
const matchesContainer = document.querySelector('.matches');
|
||||
const input = document.querySelector('input#errorCode');
|
||||
|
||||
input.addEventListener('input', (e) => {
|
||||
const errorCode = e.target.value;
|
||||
const matches = fuse.search(errorCode);
|
||||
|
||||
if (!errorCode) {
|
||||
matchesContainer.innerHTML = '';
|
||||
input.classList.remove('has-matches');
|
||||
return
|
||||
}
|
||||
|
||||
matchesContainer.innerHTML = '';
|
||||
input.classList.add('has-matches');
|
||||
|
||||
if (matches.length > 0) {
|
||||
matches.forEach(match => {
|
||||
const errorCode = match.item.padEnd(12, 'X');
|
||||
const matchLink = document.createElement('a');
|
||||
matchLink.innerText = errorCode;
|
||||
matchLink.href = `/docs/errors/${errorCode}`;
|
||||
matchesContainer.appendChild(matchLink);
|
||||
});
|
||||
} else {
|
||||
const noMatch = document.createElement('p');
|
||||
noMatch.innerText = '{{ locale.docs.search.no_match }}';
|
||||
matchesContainer.appendChild(noMatch);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/documentation.css" />
|
||||
|
||||
<div class="docs-wrapper">
|
||||
|
||||
<a href="/" class="logo-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="39.876">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
|
||||
<g id="XMLID_6_" transform="translate(8.222 1.418)">
|
||||
<path id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
|
||||
<path id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
|
||||
<path id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
|
||||
</g>
|
||||
</g>
|
||||
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
|
||||
font-family="Poppins-Bold, Poppins" font-weight="700">
|
||||
<tspan x="0" y="0">Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<div class="header-wrapper">
|
||||
<button id="openSidebar" class="nostyle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-menu">
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
{{> header}}
|
||||
</div>
|
||||
|
||||
{{> docs-sidebar}}
|
||||
|
||||
<div class="content">
|
||||
<div class="content-inner">
|
||||
<h1>{{ locale.docs.quickLinks.header }}</h1>
|
||||
<div class="quick-links-grid">
|
||||
<a href="/docs/install">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-download">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||
<polyline points="7 10 12 15 17 10"></polyline>
|
||||
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="header">{{ locale.docs.quickLinks.links.[0].header }}</p>
|
||||
<p>{{ locale.docs.quickLinks.links.[0].caption }}</p>
|
||||
</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-chevron-right">
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<a href="/docs/search">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-search">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
<div>
|
||||
<p class="header">{{ locale.docs.quickLinks.links.[1].header }}</p>
|
||||
<p>{{ locale.docs.quickLinks.links.[1].caption }}</p>
|
||||
</div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-chevron-right">
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h1>Welcome</h1>
|
||||
<p>Welcome to the official documentation for Pretendo Network. Here you'll be able to find installation instructions, error
|
||||
codes, and more.</p>
|
||||
<p>To begin, check out the quick links or take a look at the sidebar</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/docssidebarhandler.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
|
||||
<link rel="stylesheet" href="/assets/css/highlightjs.css">
|
||||
<script>hljs.highlightAll();</script>
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<section class="hero">
|
||||
<div class="hero-meta">
|
||||
<h3 class="subtitle">{{ locale.hero.subtitle }}</h3>
|
||||
<h1 class="title dot" {{#if locale.hero.titleSuffix}}data-title-suffix="{{locale.hero.titleSuffix}}"{{/if}}>{{ locale.hero.title }}</h1>
|
||||
<p class="text">{{ locale.hero.text }}</p>
|
||||
<div class="buttons">
|
||||
<a href="/#about" class="do-button-margin">
|
||||
<button class="button primary">
|
||||
{{ locale.hero.buttons.readMore }}
|
||||
</button>
|
||||
</a>
|
||||
<a href="https://invite.gg/pretendo" class="do-button-margin" target="_blank" aria-label="Open Discord">
|
||||
<button class="button secondary discord icon-btn" focusable="false">
|
||||
<svg alt="" width="71" height="55" viewBox="0 0 71 55" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" fill="#ffffff"/></g><defs><clipPath id="clip0"><rect width="71" height="55" fill="white"/></clipPath></defs></svg>
|
||||
</button>
|
||||
</a>
|
||||
<a href="https://github.com/PretendoNetwork/" class="do-button-margin" target="_blank" aria-label="Open Github">
|
||||
<button class="button secondary github icon-btn" focusable="false">
|
||||
<svg alt="" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
||||
</button>
|
||||
</a>
|
||||
<a href="/account/upgrade" target="_blank" aria-label="Upgrade account">
|
||||
<button class="button secondary github icon-btn" focusable="false">
|
||||
<svg alt="" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-image">
|
||||
<div class="light-purple-circle">
|
||||
<img class="n2ds" src="/assets/images/n2ds.png" alt="">
|
||||
<div class="deco">
|
||||
|
||||
<svg alt="" xmlns="http://www.w3.org/2000/svg" width="839.371" height="893.406" viewBox="0 0 839.371 893.406">
|
||||
<g id="deco" transform="translate(-1064.958 -142.958)">
|
||||
<g id="Ellipse_12" data-name="Ellipse 12" transform="translate(1314 265)" fill="none" stroke="#9d6ff3" stroke-width="26">
|
||||
<circle cx="56" cy="56" r="56" stroke="none"/>
|
||||
<circle cx="56" cy="56" r="69" fill="none"/>
|
||||
</g>
|
||||
<g id="Ellipse_22" data-name="Ellipse 22" transform="translate(1361 771)" fill="none" stroke="#59c9a5" stroke-width="26">
|
||||
<circle cx="77" cy="77" r="77" stroke="none"/>
|
||||
<circle cx="77" cy="77" r="90" fill="none"/>
|
||||
</g>
|
||||
<g id="Ellipse_13" data-name="Ellipse 13" transform="translate(1801.405 273.601) rotate(1)" fill="none" stroke="#9d6ff3" stroke-width="26">
|
||||
<circle cx="23" cy="23" r="23" stroke="none"/>
|
||||
<circle cx="23" cy="23" r="36" fill="none"/>
|
||||
</g>
|
||||
<g id="Ellipse_23" data-name="Ellipse 23" transform="matrix(0.839, -0.545, 0.545, 0.839, 1651.184, 609.237)" fill="none" stroke="#25224f" stroke-width="26">
|
||||
<circle cx="78.5" cy="78.5" r="78.5" stroke="none"/>
|
||||
<circle cx="78.5" cy="78.5" r="91.5" fill="none"/>
|
||||
</g>
|
||||
<circle class="animateDot" id="Ellipse_15" data-name="Ellipse 15" cx="23" cy="23" r="23" transform="translate(1586.473 353) rotate(-45)" fill="#9d6ff3"/>
|
||||
<ellipse class="animateDot" id="Ellipse_21" data-name="Ellipse 21" cx="11" cy="10.5" rx="11" ry="10.5" transform="translate(1588.958 188.514) rotate(-45)" fill="#9d6ff3"/>
|
||||
<ellipse id="Ellipse_25" data-name="Ellipse 25" cx="11" cy="10.5" rx="11" ry="10.5" transform="translate(1143.958 1021.514) rotate(-45)" fill="#9d6ff3"/>
|
||||
<ellipse id="Ellipse_26" data-name="Ellipse 26" cx="11" cy="10.5" rx="11" ry="10.5" transform="translate(1064.958 158.514) rotate(-45)" fill="#9d6ff3"/>
|
||||
<circle class="animateDot" id="Ellipse_16" data-name="Ellipse 16" cx="23" cy="23" r="23" transform="translate(1169.473 524) rotate(-45)" fill="#59c9a5"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="misc">
|
||||
<div class="purple-circle"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="progress column-2">
|
||||
<div class="left sect">
|
||||
<div class="left-meta">
|
||||
<h2 class="dot title" id="about" {{#if locale.aboutUs.titleSuffix}}data-title-suffix="{{locale.aboutUs.titleSuffix}}"{{/if}}>{{ locale.aboutUs.title }}</h2>
|
||||
{{#each locale.aboutUs.paragraphs}}
|
||||
<p class="text">{{ this }}</p>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="right sect">
|
||||
<h2 class="title">
|
||||
<a href="/progress">{{ locale.progress.title }}</a>
|
||||
</h2>
|
||||
{{> progress-list data=featuredFeatureList purple=true }}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="faq">
|
||||
<div class="sect-top sect">
|
||||
<h2 class="dot title" id="faq" {{#if locale.faq.titleSuffix}}data-title-suffix="{{locale.faq.titleSuffix}}"{{/if}}>{{ locale.faq.title }}</h2>
|
||||
<p class="text">{{ locale.faq.text }}</p>
|
||||
</div>
|
||||
<div class="questions column-2">
|
||||
{{#doFaq locale.faq.QAs}}
|
||||
<details class="question-and-answer">
|
||||
<summary>
|
||||
{{ question }}
|
||||
</summary>
|
||||
<p class="text">{{{ answer }}}</p>
|
||||
</details>
|
||||
{{/doFaq}}
|
||||
</div>
|
||||
{{!-- Tmp! --}}
|
||||
<script>document.querySelector("details").setAttribute("open", true)</script>
|
||||
</section>
|
||||
|
||||
<section class="showcase">
|
||||
<div class="sect-top sect">
|
||||
<h2 class="dot title" id="showcase" {{#if locale.showcase.titleSuffix}}data-title-suffix="{{locale.showcase.titleSuffix}}"{{/if}}>{{ locale.showcase.title }}</h2>
|
||||
<p class="text">{{ locale.showcase.text }}</p>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="item highlight">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="server" class="svg-inline--fa fa-server fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M480 160H32c-17.673 0-32-14.327-32-32V64c0-17.673 14.327-32 32-32h448c17.673 0 32 14.327 32 32v64c0 17.673-14.327 32-32 32zm-48-88c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24zm-64 0c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24zm112 248H32c-17.673 0-32-14.327-32-32v-64c0-17.673 14.327-32 32-32h448c17.673 0 32 14.327 32 32v64c0 17.673-14.327 32-32 32zm-48-88c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24zm-64 0c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24zm112 248H32c-17.673 0-32-14.327-32-32v-64c0-17.673 14.327-32 32-32h448c17.673 0 32 14.327 32 32v64c0 17.673-14.327 32-32 32zm-48-88c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24zm-64 0c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24z"></path></svg>
|
||||
<h1>{{ locale.showcase.cards.0.title }}</h1>
|
||||
<p>{{ locale.showcase.cards.0.caption }}</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="users" class="svg-inline--fa fa-users fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M96 224c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm448 0c35.3 0 64-28.7 64-64s-28.7-64-64-64-64 28.7-64 64 28.7 64 64 64zm32 32h-64c-17.6 0-33.5 7.1-45.1 18.6 40.3 22.1 68.9 62 75.1 109.4h66c17.7 0 32-14.3 32-32v-32c0-35.3-28.7-64-64-64zm-256 0c61.9 0 112-50.1 112-112S381.9 32 320 32 208 82.1 208 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C179.6 288 128 339.6 128 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zm-223.7-13.4C161.5 263.1 145.6 256 128 256H64c-35.3 0-64 28.7-64 64v32c0 17.7 14.3 32 32 32h65.9c6.3-47.4 34.9-87.3 75.2-109.4z"></path></svg>
|
||||
<h1>{{ locale.showcase.cards.1.title }}</h1>
|
||||
<p>{{ locale.showcase.cards.1.caption }}</p>
|
||||
</div>
|
||||
<div class="item">
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="gamepad" class="svg-inline--fa fa-gamepad fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M480.07 96H160a160 160 0 1 0 114.24 272h91.52A160 160 0 1 0 480.07 96zM248 268a12 12 0 0 1-12 12h-52v52a12 12 0 0 1-12 12h-24a12 12 0 0 1-12-12v-52H84a12 12 0 0 1-12-12v-24a12 12 0 0 1 12-12h52v-52a12 12 0 0 1 12-12h24a12 12 0 0 1 12 12v52h52a12 12 0 0 1 12 12zm216 76a40 40 0 1 1 40-40 40 40 0 0 1-40 40zm64-96a40 40 0 1 1 40-40 40 40 0 0 1-40 40z"></path></svg>
|
||||
<h1>{{ locale.showcase.cards.2.title }}</h1>
|
||||
<p>{{ locale.showcase.cards.2.caption }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="team">
|
||||
<div class="sect-top sect team-top">
|
||||
<h2 class="dot title" id="credits" {{#if locale.credits.titleSuffix}}data-title-suffix="{{locale.credits.titleSuffix}}"{{/if}}>{{ locale.credits.title }}</h2>
|
||||
<p class="text">{{ locale.credits.text }}</p>
|
||||
</div>
|
||||
<div class="team-cards">
|
||||
{{#each locale.credits.people}}
|
||||
<div class="card">
|
||||
<div class="card-left">
|
||||
<img src="{{ picture }}" class="pfp" alt="">
|
||||
</div>
|
||||
<div class="card-core">
|
||||
{{#if special}}<span class="sub">{{ special }}</span>{{/if}}
|
||||
<h3 class="title">
|
||||
<span>{{ name }}</span>
|
||||
<a href="{{ github }}" class="github" target="_blank" aria-label="Open Github">
|
||||
<svg alt="" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
||||
</a>
|
||||
</h3>
|
||||
<p class="text">{{ caption }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="team-helpers">
|
||||
<div class="sect-top sect">
|
||||
<h2 class="dot title" id="special-thanks" {{#if locale.specialThanks.titleSuffix}}data-title-suffix="{{locale.specialThanks.titleSuffix}}"{{/if}}>{{ locale.specialThanks.title }}</h2>
|
||||
<p class="text">{{ locale.specialThanks.text }}</p>
|
||||
</div>
|
||||
<div class="animation-wrapper">
|
||||
<div class="row-wrapper">
|
||||
<div class="row first">
|
||||
<div class="team-helpers-cards">
|
||||
{{#each specialThanksPeople.first}}
|
||||
<a {{#if github}}href="{{ github }}" target="_blank"{{/if}} class="helper-card{{#if special}} special{{/if}}">
|
||||
<div class="img-wrapper">
|
||||
<img src="{{ picture }}" class="pfp" alt="">
|
||||
</div>
|
||||
<span>{{ name }}</span>
|
||||
<p>{{ caption }}</p>
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row second">
|
||||
<div class="team-helpers-cards">
|
||||
{{#each specialThanksPeople.second}}
|
||||
<a {{#if github}}href="{{ github }}"{{/if}} class="helper-card{{#if special}} special{{/if}}">
|
||||
<div class="img-wrapper">
|
||||
<img src="{{ picture }}" class="pfp" alt="">
|
||||
</div>
|
||||
<span>{{ name }}</span>
|
||||
<p>{{ caption }}</p>
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="update-signup">
|
||||
<div class="sect hero-meta reduced-margin">
|
||||
<svg alt="" xmlns="http://www.w3.org/2000/svg" width="48.87" height="71.093" viewBox="0 0 48.87 71.093"><g id="XMLID_6_" transform="translate(0)"><path id="XMLID_15_" d="M69.581,29.593c-2.029,1.068-.249,4.129,1.78,3.061,5.162-2.67,11.463-2.6,16.981-1.1,4.735,1.282,9.5,3.845,12.246,8.045,1.246,1.922,4.307.142,3.061-1.78C96.921,27.386,80.3,24.04,69.581,29.593Z" transform="translate(-60.112 -20.086)" fill="#9d6ff3"/><path id="XMLID_14_" d="M103.359,21.045c-3.951-6.159-10.751-10-17.657-11.89C77.763,6.948,68.721,7.019,61.281,10.9c-2.029,1.068-.249,4.129,1.78,3.061,6.586-3.453,14.667-3.311,21.644-1.388,5.981,1.638,12.1,4.913,15.521,10.252C101.507,24.783,104.569,23,103.359,21.045Z" transform="translate(-54.766 -7.693)" fill="#9d6ff3"/><path id="XMLID_9_" d="M65.995,47.8a20.7,20.7,0,0,0-12.958,4.45H47.27a2.579,2.579,0,0,0-2.67,2.456v47.239a2.763,2.763,0,0,0,2.67,2.67h5.838a2.639,2.639,0,0,0,2.528-2.67V87.564A21.228,21.228,0,1,0,65.995,47.8Zm0,33.178a12,12,0,1,1,12-12A12,12,0,0,1,65.995,80.978Z" transform="translate(-44.6 -33.522)" fill="#9d6ff3"/></g></svg>
|
||||
<h2 class="dot title" id="discord-join" {{#if locale.discordJoin.titleSuffix}}data-title-suffix="{{locale.discordJoin.titleSuffix}}"{{/if}}>{{ locale.discordJoin.title }}</h2>
|
||||
<p class="text">{{ locale.discordJoin.text }}</p>
|
||||
</div>
|
||||
<div class="floating-serverjoin">
|
||||
<p>{{ locale.discordJoin.widget.text }}</p>
|
||||
<a href="https://invite.gg/pretendo" target="_blank">{{ locale.discordJoin.widget.button }}</a>
|
||||
</div>
|
||||
<div class="circle"></div>
|
||||
</section>
|
||||
|
||||
{{> footer }}
|
||||
|
||||
</div>
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- general -->
|
||||
<title>Pretendo Network</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="manifest" href="/assets/site.webmanifest">
|
||||
<meta name="msapplication-config" content="/assets/browserconfig.xml">
|
||||
|
||||
<!-- windows/ios/chrome -->
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta name="apple-mobile-web-app-title" content="Pretendo Network">
|
||||
<meta name="application-name" content="Pretendo Network">
|
||||
<meta name="msapplication-TileColor" content="#1b1f3b">
|
||||
<meta name="theme-color" content="#1b1f3b">
|
||||
|
||||
<!-- open graph/embeds -->
|
||||
<meta property="og:title" content="{{ postInfo.title }} | Pretendo Network Blog">
|
||||
<meta property="og:description" content="{{ postInfo.caption }}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://pretendo.network/">
|
||||
<meta property="og:image" content="{{ postInfo.cover_image }}">
|
||||
<meta property="og:image:alt" content="Pretendo Network">
|
||||
<meta property="og:site_name" content="Pretendo Network">
|
||||
|
||||
<!-- twitter embeds -->
|
||||
<meta name="twitter:url" content="https://pretendo.network/">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:site" content="@PretendoNetwork">
|
||||
<meta name="twitter:title" content="{{ postInfo.title }} | Pretendo Network Blog">
|
||||
<meta name="twitter:description" content="{{ postInfo.caption }}">
|
||||
<meta name="twitter:image" content="{{ postInfo.cover_image }}">
|
||||
|
||||
<!-- google seo -->
|
||||
<meta name="description" content="An open source Nintendo Network replacement that aims to build custom servers for the WiiU and 3DS family of consoles">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
<!-- RSS feed -->
|
||||
<link rel="alternate" type="application/rss+xml" title="Pretendo Network Blog" href="/blog/feed.xml">
|
||||
|
||||
<!-- favicon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/icons/favicon-16x16.png">
|
||||
<link rel="mask-icon" href="/assets/images/icons/safari-pinned-tab.svg" color="#1b1f3b">
|
||||
<link rel="shortcut icon" href="/assets/images/icons/favicon.ico">
|
||||
|
||||
<!-- css files -->
|
||||
<link rel="stylesheet" href="/assets/css/dropdown.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css">
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Twemoji setup -->
|
||||
<script src="https://twemoji.maxcdn.com/v/latest/twemoji.min.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
twemoji.parse(document.body, {folder: 'svg', ext: '.svg'});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "ea6695987d664a7f90874c9c0dee9385"}'></script><!-- End Cloudflare Web Analytics -->
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-body">
|
||||
{{{ body }}}
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
|
||||
<script src="/assets/js/progress-charts.js"></script>
|
||||
<script src="/assets/js/locale-dropdown-handler.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- general -->
|
||||
<title>Pretendo Network</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="manifest" href="/assets/site.webmanifest">
|
||||
<meta name="msapplication-config" content="/assets/browserconfig.xml">
|
||||
|
||||
<!-- windows/ios/chrome -->
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta name="apple-mobile-web-app-title" content="Pretendo Network">
|
||||
<meta name="application-name" content="Pretendo Network">
|
||||
<meta name="msapplication-TileColor" content="#1b1f3b">
|
||||
<meta name="theme-color" content="#1b1f3b">
|
||||
|
||||
<!-- open graph/embeds -->
|
||||
<meta property="og:title" content="Pretendo Network">
|
||||
<meta property="og:description" content="An open source Nintendo Network replacement that aims to build custom servers for the WiiU and 3DS family of consoles">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://pretendo.network/">
|
||||
<meta property="og:image" content="https://pretendo.network/assets/images/opengraph/opengraph-image.png">
|
||||
<meta property="og:image:alt" content="Pretendo Network">
|
||||
<meta property="og:site_name" content="Pretendo Network">
|
||||
|
||||
<!-- twitter embeds -->
|
||||
<meta name="twitter:url" content="https://pretendo.network/">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:site" content="@PretendoNetwork">
|
||||
<meta name="twitter:title" content="Pretendo Network">
|
||||
<meta name="twitter:description" content="An open source Nintendo Network replacement that aims to build custom servers for the WiiU and 3DS family of consoles">
|
||||
<meta name="twitter:image" content="https://pretendo.network/assets/images/opengraph/opengraph-image.png">
|
||||
|
||||
<!-- google seo -->
|
||||
<meta name="description" content="An open source Nintendo Network replacement that aims to build custom servers for the WiiU and 3DS family of consoles">
|
||||
<meta name="robots" content="index, follow">
|
||||
|
||||
<!-- RSS feed -->
|
||||
<link rel="alternate" type="application/rss+xml" title="Pretendo Network Blog" href="/blog/feed.xml">
|
||||
|
||||
<!-- favicon -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/images/icons/favicon-16x16.png">
|
||||
<link rel="mask-icon" href="/assets/images/icons/safari-pinned-tab.svg" color="#1b1f3b">
|
||||
<link rel="shortcut icon" href="/assets/images/icons/favicon.ico">
|
||||
|
||||
<!-- css files -->
|
||||
<link rel="stylesheet" href="/assets/css/dropdown.css" />
|
||||
<link rel="stylesheet" href="/assets/css/main.css" />
|
||||
<link rel="stylesheet" href="/assets/css/components.css" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Twemoji setup -->
|
||||
<script src="https://twemoji.maxcdn.com/v/latest/twemoji.min.js" crossorigin="anonymous"></script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
twemoji.parse(document.body, {folder: 'svg', ext: '.svg'});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-body">
|
||||
{{{ body }}}
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
|
||||
<script src="/assets/js/progress-charts.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/localization.css" />
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="localization-wrapper">
|
||||
<div class="localization-widget">
|
||||
<h1 class="title dot">{{ locale.localizationPage.title" }}</h1>
|
||||
<p class="caption">{{ locale.localizationPage.description" }}</p>
|
||||
<a href="https://github.com/PretendoNetwork/Pretendo/blob/master/CONTRIBUTING.md#localization" target="_blank" class="localization-instr">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--accent-shade-2)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
|
||||
{{ locale.localizationPage.instructions" }}
|
||||
</a>
|
||||
<form class="localization-form" autocomplete="off">
|
||||
<label for="url">{{ locale.localizationPage.fileInput" }}</label>
|
||||
<div class="input-wrapper">
|
||||
<input type="url" id="url" placeholder="{{ locale.localizationPage.filePlaceholder" }}" required />
|
||||
<button type="submit" class="submit-btn">{{ locale.localizationPage.button" }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{> footer }}
|
||||
|
||||
<script src="/assets/js/locale-tester-handler.js" />
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<div class="sidebar">
|
||||
<div class="section">
|
||||
<h5>{{ locale.docs.sidebar.getting_started }}</h5>
|
||||
<a href="/docs/welcome">{{ locale.docs.sidebar.welcome }}</a>
|
||||
<a href="/docs/install">{{ locale.docs.sidebar.install_extended }}</a>
|
||||
<a href="/docs/search">{{ locale.docs.sidebar.search }}</a>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>{{ locale.docs.sidebar.install }}</h5>
|
||||
<a href="/docs/install/wiiu">Wii U</a>
|
||||
<a href="/docs/install/3ds">3DS</a>
|
||||
<a href="/docs/install/cemu">Cemu</a>
|
||||
<a href="/docs/install/citra">Citra</a>
|
||||
<a href="/docs/install/juxt">Juxt</a>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h5>{{ locale.docs.sidebar.juxt_err }}</h5>
|
||||
<a href="/docs/errors/JXT-598-0009">JXT-598-0009</a>
|
||||
<a href="/docs/errors/JXT-598-0010">JXT-598-0010</a>
|
||||
<a href="/docs/errors/JXT-598-0011">JXT-598-0011</a>
|
||||
<a href="/docs/errors/JXT-598-0020">JXT-598-0020</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function selectSidebarElement(element) {
|
||||
element.scrollIntoView({ block: "center" });
|
||||
element.classList.add('active');
|
||||
}
|
||||
|
||||
selectSidebarElement(document.querySelector("div.sidebar a[href='/docs/{{currentPage}}']"));
|
||||
</script>
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/partials/footer.css" /></link>
|
||||
|
||||
<footer>
|
||||
<div>
|
||||
<svg class="logotype" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 39.876" preserveAspectRatio="xMinYMin meet">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
|
||||
<g id="XMLID_6_" transform="translate(8.222 1.418)">
|
||||
<path id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
|
||||
<path id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
|
||||
<path id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
|
||||
</g>
|
||||
</g>
|
||||
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
|
||||
font-family="Poppins-Bold, Poppins" font-weight="700">
|
||||
<tspan x="0" y="0">Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
<p>Copyright 2022</p>
|
||||
<p>Site by <a href="https://mrjvs.com/" target="_blank">mrjvs</a>, <a href="https://jipfr.nl/" target="_blank">jipfr</a> & <a href="https://ashm.dev/" target="_blank">monty</a></p>
|
||||
</div>
|
||||
<div>
|
||||
<h1>{{ locale.footer.socials }}</h1>
|
||||
<a href="https://twitter.com/PretendoNetwork/" target="_blank">Twitter</a>
|
||||
<a href="https://invite.gg/pretendo" target="_blank">Discord</a>
|
||||
<a href="https://github.com/PretendoNetwork" target="_blank">GitHub</a>
|
||||
</div>
|
||||
<div>
|
||||
<h1>{{ locale.footer.usefulLinks }}</h1>
|
||||
<a href="/docs">{{ locale.nav.docs }}</a>
|
||||
<a href="/#faq">{{ locale.nav.faq }}</a>
|
||||
<a href="/progress">{{ locale.nav.progress }}</a>
|
||||
<a href="/blog">{{ locale.nav.blog }}</a>
|
||||
<a href="/account">{{ locale.nav.account }}</a>
|
||||
<a href="/account/upgrade">{{ locale.nav.donate }}</a>
|
||||
</div>
|
||||
<div class="discord-server-card-wrapper">
|
||||
<div class="discord-server-card">
|
||||
<h1>{{ locale.footer.widget.captions.[0] }}</h1>
|
||||
<h2>{{ locale.footer.widget.captions.[1] }}</h2>
|
||||
<a href="https://invite.gg/pretendo">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-arrow-right">
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
<polyline points="12 5 19 12 12 19"></polyline>
|
||||
</svg>
|
||||
{{ locale.footer.widget.button }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="bandwidth-raccoon-wrapper">
|
||||
<div class="text-bubble">
|
||||
<p>I'm a raccoon, and I love eating grapes!</p>
|
||||
</div>
|
||||
<img src="/assets/images/bandwidth.svg" class="bandwidth-raccoon" />
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
const bandwidthRaccoon = document.querySelector("footer img.bandwidth-raccoon");
|
||||
const bandwidthRaccoonWrapper = document.querySelector("footer .bandwidth-raccoon-wrapper");
|
||||
const bandwidthRaccoonBubbleText = document.querySelector("footer .bandwidth-raccoon-wrapper .text-bubble p");
|
||||
let i = 0;
|
||||
|
||||
function unescapeHTML(string) {
|
||||
const el = document.createElement("span");
|
||||
el.innerHTML = string;
|
||||
return el.innerText;
|
||||
}
|
||||
|
||||
|
||||
const randomSentences = [
|
||||
{{#each locale.footer.bandwidthRaccoonQuotes}}
|
||||
`${unescapeHTML("{{this}}")}`,
|
||||
{{/each}}
|
||||
]
|
||||
|
||||
bandwidthRaccoon.addEventListener("click", () =>
|
||||
{
|
||||
bandwidthRaccoonWrapper.classList.add("speak");
|
||||
if (!randomSentences[i]) {
|
||||
i = 0
|
||||
}
|
||||
bandwidthRaccoonBubbleText.innerText = randomSentences[i];
|
||||
i += 1;
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1,348 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/partials/header.css" ></link>
|
||||
|
||||
<header class="transparent">
|
||||
<div class="left-section">
|
||||
<button class="dropdown-button" id="mobile-button">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M224 128a8 8 0 0 1-8 8H40a8 8 0 0 1 0-16h176a8 8 0 0 1 8 8ZM40 72h176a8 8 0 0 0 0-16H40a8 8 0 0 0 0 16Zm176 112H40a8 8 0 0 0 0 16h176a8 8 0 0 0 0-16Z"/></svg>
|
||||
</button>
|
||||
<a href="/" class="logo-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="39.876">
|
||||
<g id="logo_type" data-name="logo type" transform="translate(-553 -467)">
|
||||
<g id="logo" transform="translate(553 467)">
|
||||
<rect id="XMLID_158_" width="39.876" height="39.876" fill="#9d6ff3" opacity="0" />
|
||||
<g id="XMLID_6_" transform="translate(8.222 1.418)">
|
||||
<path id="XMLID_15_"
|
||||
d="M69.149,28.312c-1.051.553-.129,2.139.922,1.585a12.365,12.365,0,0,1,8.794-.571,10.829,10.829,0,0,1,6.342,4.166c.645,1,2.231.074,1.585-.922C83.308,27.169,74.7,25.436,69.149,28.312Z"
|
||||
transform="translate(-64.246 -23.389)" fill="#9d6ff3" />
|
||||
<path id="XMLID_14_"
|
||||
d="M82.64,14.608A15.565,15.565,0,0,0,73.5,8.45a17.535,17.535,0,0,0-12.647.9c-1.051.553-.129,2.139.922,1.585,3.411-1.788,7.6-1.714,11.209-.719,3.1.848,6.268,2.544,8.038,5.309C81.681,16.543,83.267,15.622,82.64,14.608Z"
|
||||
transform="translate(-57.476 -7.693)" fill="#9d6ff3" />
|
||||
<path id="XMLID_9_"
|
||||
d="M55.68,47.8a10.719,10.719,0,0,0-6.71,2.3H45.983A1.336,1.336,0,0,0,44.6,51.376V75.84a1.431,1.431,0,0,0,1.383,1.383h3.023a1.367,1.367,0,0,0,1.309-1.383V68.392A10.993,10.993,0,1,0,55.68,47.8Zm0,17.182a6.213,6.213,0,1,1,6.213-6.213A6.216,6.216,0,0,1,55.68,64.982Z"
|
||||
transform="translate(-44.6 -40.406)" fill="#9d6ff3" />
|
||||
</g>
|
||||
</g>
|
||||
<text id="Pretendo" transform="translate(593 492)" fill="#fff" font-size="17"
|
||||
font-family="Poppins-Bold, Poppins" font-weight="700">
|
||||
<tspan x="0" y="0">Pretendo</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<div class="dropdown-button-wrapper hide-on-mobile">
|
||||
<button class="dropdown-button" id="about-button">{{ locale.nav.about }}</button>
|
||||
<button class="dropdown-button" id="documentation-button">{{ locale.nav.docs }}</button>
|
||||
</div>
|
||||
<a href="/progress" class="hide-on-mobile">
|
||||
<button>{{ locale.nav.progress }}</button>
|
||||
</a>
|
||||
<a href="/account/upgrade" class="donate">
|
||||
<button>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-heart"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>
|
||||
<span>{{ locale.nav.donate }}</span>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-arrow hide-on-mobile" id="navbar-dropdown-arrow"></div>
|
||||
|
||||
<div class="dropdown" id="navbar-dropdown">
|
||||
<div class="dropdown-content" id="about-dropdown-content">
|
||||
<div class="top">
|
||||
<a href="/#credits">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M166.5 192.8a7.8 7.8 0 0 1 .6 8.3a8.1 8.1 0 0 1-7.1 4.3H16a8.1 8.1 0 0 1-7.1-4.3a7.8 7.8 0 0 1 .6-8.3a95.5 95.5 0 0 1 45.3-34.9a60 60 0 1 1 66.4 0a95.5 95.5 0 0 1 45.3 34.9Zm81.6 0a96.3 96.3 0 0 0-45.4-34.9A59.9 59.9 0 0 0 169.5 48a64 64 0 0 0-16.3 2.2a8.2 8.2 0 0 0-5.4 5.2a8 8 0 0 0 1.2 7.3a75.8 75.8 0 0 1 3.8 84.9a8.1 8.1 0 0 0 2.1 10.6q4.5 3.5 8.7 7.2l.5.5a112.6 112.6 0 0 1 25.5 34.9a7.9 7.9 0 0 0 7.2 4.6h44.7a8.1 8.1 0 0 0 7.1-4.3a8 8 0 0 0-.5-8.3Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.credits }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.credits }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/#about">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M128 24a104 104 0 1 0 104 104A104.1 104.1 0 0 0 128 24Zm-2 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12Zm10 112h-8a8 8 0 0 1-8-8v-48a8 8 0 0 1 0-16h8a8 8 0 0 1 8 8v48a8 8 0 0 1 0 16Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.about }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.about }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/#faq">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M128 24a104 104 0 1 0 104 104A104.1 104.1 0 0 0 128 24Zm0 168a12 12 0 1 1 12-12a12 12 0 0 1-12 12Zm8-48.9v.9a8 8 0 0 1-16 0v-8a8 8 0 0 1 8-8a20 20 0 1 0-20-20a8 8 0 0 1-16 0a36 36 0 1 1 44 35.1Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.faq }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.faq }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/blog">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M216 48H56a16 16 0 0 0-16 16v120a8 8 0 0 1-16 0V88a8 8 0 0 0-16 0v96a24 24 0 0 0 24 24h176a24.1 24.1 0 0 0 24-24V64a16 16 0 0 0-16-16Zm-40 104H96a8 8 0 0 1 0-16h80a8 8 0 0 1 0 16Zm0-32H96a8 8 0 0 1 0-16h80a8 8 0 0 1 0 16Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.blog }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.blog }}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bottom">
|
||||
<a href="https://invite.gg/pretendo" target="_blank" aria-label="A link to our Discord server">
|
||||
<svg width="71" height="55" viewBox="0 0 71 55" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" fill="#ffffff"></path></g><defs><clipPath id="clip0"><rect width="71" height="55" fill="white"></rect></clipPath></defs></svg>
|
||||
</a>
|
||||
<a href="https://twitter.com/PretendoNetwork" target="_blank" aria-label="A link to our Twitter account">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path fill="currentColor" d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"></path></svg>
|
||||
</a>
|
||||
<a href="https://github.com/PretendoNetwork" target="_blank" aria-label="A link to our GitHub organization">
|
||||
<svg data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown-content" id="documentation-dropdown-content">
|
||||
<div class="top">
|
||||
<a href="/docs">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M74.3 85.7a8.1 8.1 0 0 1 11.4-11.4l34.3 34.4V24a8 8 0 0 1 16 0v84.7l34.3-34.4a8.1 8.1 0 0 1 11.4 11.4l-48 48a8.2 8.2 0 0 1-11.4 0ZM240 136v64a16 16 0 0 1-16 16H32a16 16 0 0 1-16-16v-64a16 16 0 0 1 16-16h52.4a3.6 3.6 0 0 1 2.8 1.2L111 145a24.1 24.1 0 0 0 34 0l23.8-23.8a3.6 3.6 0 0 1 2.8-1.2H224a16 16 0 0 1 16 16Zm-40 32a12 12 0 1 0-12 12a12 12 0 0 0 12-12Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.docs.quickLinks.links.[0].header }}</p>
|
||||
<p class="caption">{{ locale.docs.quickLinks.links.[0].caption }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/#faq">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M236.7 188L148.8 36a24 24 0 0 0-41.6 0L19.3 188A23.9 23.9 0 0 0 40 224h176a23.9 23.9 0 0 0 20.7-36ZM120 104a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0Zm8 88a12 12 0 1 1 12-12a12 12 0 0 1-12 12Z"/></svg> </div>
|
||||
<div>
|
||||
<p class="title">{{ locale.docs.quickLinks.links.[1].header }}</p>
|
||||
<p class="caption">{{ locale.docs.quickLinks.links.[1].caption }}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- repeating this is less than ideal, but a rewrite is already planned; this is more of a hotfix than anything -->
|
||||
<div class="dropdown-content" id="mobile-dropdown-content">
|
||||
<div class="top">
|
||||
<a href="/#credits">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M166.5 192.8a7.8 7.8 0 0 1 .6 8.3a8.1 8.1 0 0 1-7.1 4.3H16a8.1 8.1 0 0 1-7.1-4.3a7.8 7.8 0 0 1 .6-8.3a95.5 95.5 0 0 1 45.3-34.9a60 60 0 1 1 66.4 0a95.5 95.5 0 0 1 45.3 34.9Zm81.6 0a96.3 96.3 0 0 0-45.4-34.9A59.9 59.9 0 0 0 169.5 48a64 64 0 0 0-16.3 2.2a8.2 8.2 0 0 0-5.4 5.2a8 8 0 0 0 1.2 7.3a75.8 75.8 0 0 1 3.8 84.9a8.1 8.1 0 0 0 2.1 10.6q4.5 3.5 8.7 7.2l.5.5a112.6 112.6 0 0 1 25.5 34.9a7.9 7.9 0 0 0 7.2 4.6h44.7a8.1 8.1 0 0 0 7.1-4.3a8 8 0 0 0-.5-8.3Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.credits }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.credits }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/#about">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M128 24a104 104 0 1 0 104 104A104.1 104.1 0 0 0 128 24Zm-2 48a12 12 0 1 1-12 12a12 12 0 0 1 12-12Zm10 112h-8a8 8 0 0 1-8-8v-48a8 8 0 0 1 0-16h8a8 8 0 0 1 8 8v48a8 8 0 0 1 0 16Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.about }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.about }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/#faq">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M128 24a104 104 0 1 0 104 104A104.1 104.1 0 0 0 128 24Zm0 168a12 12 0 1 1 12-12a12 12 0 0 1-12 12Zm8-48.9v.9a8 8 0 0 1-16 0v-8a8 8 0 0 1 8-8a20 20 0 1 0-20-20a8 8 0 0 1-16 0a36 36 0 1 1 44 35.1Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.faq }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.faq }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/blog">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M216 48H56a16 16 0 0 0-16 16v120a8 8 0 0 1-16 0V88a8 8 0 0 0-16 0v96a24 24 0 0 0 24 24h176a24.1 24.1 0 0 0 24-24V64a16 16 0 0 0-16-16Zm-40 104H96a8 8 0 0 1 0-16h80a8 8 0 0 1 0 16Zm0-32H96a8 8 0 0 1 0-16h80a8 8 0 0 1 0 16Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.blog }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.blog }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/progress">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M96 8a8 8 0 0 1 8-8h48a8 8 0 0 1 0 16h-48a8 8 0 0 1-8-8Zm128 120a96 96 0 1 1-96-96a96.2 96.2 0 0 1 96 96Zm-50.7-45.3a8.1 8.1 0 0 0-11.4 0l-39.6 39.6a8.1 8.1 0 0 0 0 11.4a8.2 8.2 0 0 0 11.4 0l39.6-39.6a8.1 8.1 0 0 0 0-11.4Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.nav.progress }}</p>
|
||||
<p class="caption">{{ locale.nav.dropdown.captions.blog }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/docs">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M74.3 85.7a8.1 8.1 0 0 1 11.4-11.4l34.3 34.4V24a8 8 0 0 1 16 0v84.7l34.3-34.4a8.1 8.1 0 0 1 11.4 11.4l-48 48a8.2 8.2 0 0 1-11.4 0ZM240 136v64a16 16 0 0 1-16 16H32a16 16 0 0 1-16-16v-64a16 16 0 0 1 16-16h52.4a3.6 3.6 0 0 1 2.8 1.2L111 145a24.1 24.1 0 0 0 34 0l23.8-23.8a3.6 3.6 0 0 1 2.8-1.2H224a16 16 0 0 1 16 16Zm-40 32a12 12 0 1 0-12 12a12 12 0 0 0 12-12Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<p class="title">{{ locale.docs.quickLinks.links.[0].header }}</p>
|
||||
<p class="caption">{{ locale.docs.quickLinks.links.[0].caption }}</p>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/#faq">
|
||||
<div class="icon">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M236.7 188L148.8 36a24 24 0 0 0-41.6 0L19.3 188A23.9 23.9 0 0 0 40 224h176a23.9 23.9 0 0 0 20.7-36ZM120 104a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0Zm8 88a12 12 0 1 1 12-12a12 12 0 0 1-12 12Z"/></svg> </div>
|
||||
<div>
|
||||
<p class="title">{{ locale.docs.quickLinks.links.[1].header }}</p>
|
||||
<p class="caption">{{ locale.docs.quickLinks.links.[1].caption }}</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bottom">
|
||||
<a href="https://invite.gg/pretendo" target="_blank" aria-label="A link to our Discord server">
|
||||
<svg width="71" height="55" viewBox="0 0 71 55" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" fill="#ffffff"></path></g><defs><clipPath id="clip0"><rect width="71" height="55" fill="white"></rect></clipPath></defs></svg>
|
||||
</a>
|
||||
<a href="https://twitter.com/PretendoNetwork" target="_blank" aria-label="A link to our Twitter account">
|
||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Twitter</title><path fill="currentColor" d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"></path></svg>
|
||||
</a>
|
||||
<a href="https://github.com/PretendoNetwork" target="_blank" aria-label="A link to our GitHub organization">
|
||||
<svg data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="right-section">
|
||||
<!-- Ordered the locales in the same way Google orders them -->
|
||||
<div class="select-box locale-dropdown" data-dropdown>
|
||||
<div class="options-container">
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="en-US" name="category" />
|
||||
<label for="en-US">
|
||||
<div class="item"><span class="locale-names">English</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="de-DE" name="category" />
|
||||
<label for="de-DE">
|
||||
<div class="item"><span class="locale-names">Deutsch</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="es-ES" name="category" />
|
||||
<label for="es-ES">
|
||||
<div class="item"><span class="locale-names">Español</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="fr-FR" name="category" />
|
||||
<label for="fr-FR">
|
||||
<div class="item"><span class="locale-names">Français</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="it-IT" name="category" />
|
||||
<label for="it-IT">
|
||||
<div class="item"><span class="locale-names">Italiano</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="nl-NL" name="category" />
|
||||
<label for="nl-NL">
|
||||
<div class="item"><span class="locale-names">Nederlands</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="nb-NO" name="category" />
|
||||
<label for="nb-NO">
|
||||
<div class="item"><span class="locale-names">Norsk</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="pl-PL" name="category" />
|
||||
<label for="pl-PL">
|
||||
<div class="item"><span class="locale-names">Polski</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="pt-BR" name="category" />
|
||||
<label for="pt-BR">
|
||||
<div class="item"><span class="locale-names">Português</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="ro-RO" name="category" />
|
||||
<label for="ro-RO">
|
||||
<div class="item"><span class="locale-names">Română</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="ru-RU" name="category" />
|
||||
<label for="ru-RU">
|
||||
<div class="item"><span class="locale-names">Pусский</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="uk-UA" name="category" />
|
||||
<label for="uk-UA">
|
||||
<div class="item"><span class="locale-names">Yкраї́нська</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="ar-AR" name="category" />
|
||||
<label for="ar-AR">
|
||||
<div class="item"><span class="locale-names">اَلْعَرَبِيَّةُ</span></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="zh-CN" name="category" />
|
||||
<label for="zh-CN">
|
||||
<div class="item"><span class="locale-names">中文(简体)</span></div>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="ja-JP" name="category" />
|
||||
<label for="ja-JP">
|
||||
<div class="item"><span class="locale-names">日本語</span></div>
|
||||
</div>
|
||||
<div class="option">
|
||||
<input type="radio" class="radio" id="ko-KR" name="category" />
|
||||
<label for="ko-KR">
|
||||
<div class="item"><span class="locale-names">한국어</span></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="locale-dropdown-toggle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if isLoggedIn}}
|
||||
<div class="user-widget-wrapper logged-in">
|
||||
<div class="user-widget-toggle">
|
||||
<img src="{{ account.mii.image_url }}" alt="{{ account.mii.name }}" />
|
||||
</div>
|
||||
<div class="user-widget">
|
||||
<div class="user-avatar">
|
||||
<img src="{{ account.mii.image_url }}" alt="{{ account.mii.name }}" />
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="mii-name">{{ account.mii.name }}</div>
|
||||
<div class="pnid">{{ account.username }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a href="/account">
|
||||
<button class="button primary">
|
||||
{{ locale.nav.accountWidget.settings }}
|
||||
</button>
|
||||
</a>
|
||||
<a href="/account/logout">
|
||||
<button class="button logout">
|
||||
{{ locale.nav.accountWidget.logout }}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="user-widget-wrapper">
|
||||
<a class="login-link" href="/account/login">
|
||||
<svg width="32" height="32" viewBox="0 0 256 256"><path fill="currentColor" d="M232 128a104 104 0 1 0-174.2 76.7l1.3 1.2a104 104 0 0 0 137.8 0l1.3-1.2A103.7 103.7 0 0 0 232 128Zm-192 0a88 88 0 1 1 153.8 58.4a79.2 79.2 0 0 0-36.1-28.7a48 48 0 1 0-59.4 0a79.2 79.2 0 0 0-36.1 28.7A87.6 87.6 0 0 1 40 128Zm56-8a32 32 0 1 1 32 32a32.1 32.1 0 0 1-32-32Zm-21.9 77.5a64 64 0 0 1 107.8 0a87.8 87.8 0 0 1-107.8 0Z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<script src="/assets/js/header-handler.js"></script>
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
<div class="feature-list-wrapper {{#if purple}}purple{{/if}}">
|
||||
<div class="feature-list-top">
|
||||
<div>
|
||||
<div class="feature-progress-chart">
|
||||
<p class="percentage-label">{{ data.percent }}</p>
|
||||
<canvas class="percentage-chart" {{#if data.percent}}data-percentageoverride="{{data.percent}}"{{/if}}></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="core">
|
||||
{{#if data.title}}<div class="progress-title">
|
||||
<h3>{{ data.title }}</h3>
|
||||
<div>
|
||||
<a href="{{this.url}}" class="github" target="_blank">
|
||||
<svg data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
||||
<span>{{ locale.progress.githubRepo }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>{{/if}}
|
||||
<div class="feature-list">
|
||||
{{#each data.cards.done}}
|
||||
<div class="feature">
|
||||
<div class="custom-checkbox done">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check">
|
||||
<polyline points="20 6 9 17 4 12"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<span>{{ this }}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
{{#each data.cards.in_progress}}
|
||||
<div class="feature">
|
||||
<div class="custom-checkbox ongoing">
|
||||
<div class="small-dot"></div>
|
||||
</div>
|
||||
<span>{{ this }}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
{{#each data.cards.todo}}
|
||||
<div class="feature">
|
||||
<div class="custom-checkbox incomplete"></div>
|
||||
<span>{{ this }}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<link rel="stylesheet" href="/assets/css/progress.css">
|
||||
|
||||
{{> header}}
|
||||
|
||||
<div class="wrapper">
|
||||
|
||||
<div class="progress-hero">
|
||||
<div class="hero-meta reduced-margin">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48.87" height="71.093" viewBox="0 0 48.87 71.093"><g id="XMLID_6_" transform="translate(0)"><path id="XMLID_15_" d="M69.581,29.593c-2.029,1.068-.249,4.129,1.78,3.061,5.162-2.67,11.463-2.6,16.981-1.1,4.735,1.282,9.5,3.845,12.246,8.045,1.246,1.922,4.307.142,3.061-1.78C96.921,27.386,80.3,24.04,69.581,29.593Z" transform="translate(-60.112 -20.086)" fill="#9d6ff3"/><path id="XMLID_14_" d="M103.359,21.045c-3.951-6.159-10.751-10-17.657-11.89C77.763,6.948,68.721,7.019,61.281,10.9c-2.029,1.068-.249,4.129,1.78,3.061,6.586-3.453,14.667-3.311,21.644-1.388,5.981,1.638,12.1,4.913,15.521,10.252C101.507,24.783,104.569,23,103.359,21.045Z" transform="translate(-54.766 -7.693)" fill="#9d6ff3"/><path id="XMLID_9_" d="M65.995,47.8a20.7,20.7,0,0,0-12.958,4.45H47.27a2.579,2.579,0,0,0-2.67,2.456v47.239a2.763,2.763,0,0,0,2.67,2.67h5.838a2.639,2.639,0,0,0,2.528-2.67V87.564A21.228,21.228,0,1,0,65.995,47.8Zm0,33.178a12,12,0,1,1,12-12A12,12,0,0,1,65.995,80.978Z" transform="translate(-44.6 -33.522)" fill="#9d6ff3"/></g></svg>
|
||||
<h1 class="title dot">{{ locale.progressPage.title }}</h1>
|
||||
<p class="text">{{ locale.progressPage.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="all-progress-lists">
|
||||
<div class="donation-progress">
|
||||
<h1 class="title dot">Donation goal</h1>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-bar-inner" style="width: {{ donationCache.completed_capped }}%;" ></div>
|
||||
</div>
|
||||
<p class="localeReplace">{{{ locale.donation.progress }}} {{{ locale.donation.upgradePush }}}</p>
|
||||
</div>
|
||||
|
||||
<div id="quick-nav">
|
||||
<h1 class="title dot">Quick Nav</h1>
|
||||
<ul>
|
||||
{{#each progressLists.sections}}
|
||||
<li>
|
||||
<a href="#{{slug this.title}}">{{this.title}}</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
<br>
|
||||
{{#each progressLists.sections}}
|
||||
<div class="purple-card" id="{{slug this.title}}">
|
||||
{{> progress-list data=this }}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
{{> footer }}
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* this jank is needed to have data mixed in with a localized string */
|
||||
document.querySelector('.localeReplace').innerHTML = document.querySelector('.localeReplace').innerHTML
|
||||
.replace('${totd}', '{{donationCache.total_dollars}}');
|
||||
document.querySelector('.localeReplace').innerHTML = document.querySelector('.localeReplace').innerHTML
|
||||
.replace('${goald}', '{{donationCache.goal_dollars}}');
|
||||
document.querySelector('.localeReplace').innerHTML = document.querySelector('.localeReplace').innerHTML
|
||||
.replace('${perc}', '{{donationCache.completed_real}}');
|
||||
</script>
|
||||
Loading…
Reference in New Issue
Block a user