Initial Release

This commit is contained in:
2026-05-24 21:58:23 -07:00
commit 0a782e0096
209 changed files with 11657 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
dist
+62
View File
@@ -0,0 +1,62 @@
# 🌎 @suzzy/site
Homepage for blogs, documentation, accounts, and projects.
## 📄 Articles
This application has support for a custom article rendering system using XML
and JSON syntax. Create a folder in the **views** directory and organize your
folder as show below:
```py
|__ /articles
| |__ ... # Your articles here
|__ manifest.json # Your Manifest with Authors, Categories, and Paths
|__ browser.pug # View for Article Directory
|__ post.pug # View for Article Rendering
```
### Article Syntax
Articles follow HTML/XML like Syntax, a root `<article>` tag must be created
with all following content tags placed inside.
The following attributes **MUST** be present on the article tag:
| Name | Description |
| :--------: | :-------------------------------------------------- |
| id | Unique Article ID for CategoryId |
| authorId | Relevant Author ID |
| categoryId | Relevant Category ID |
| title | Article Title |
| created | Article Date (Should be parsable with `new Date()`) |
| snippet | Article Snippet |
| noindex | If value is `true` this article will not be indexed |
Afterwards you can place all your content tags inside below are all currently
supported tags:
### Tag: text
A text element with support for formatting using the following subtags:
- `<h>: Hint` Makes text grey and small
- `<c>: Code` Highlights text and adds a dark background
- `<a>: Link` Similar to Anchor, supports **target** and **href** attributes
- `<b>: Bold` Renders text as bold or extra thicc
- `<i>: Italic` Render Text as Italicized
- `<s>: Strike` Renders text with a line in the middle of characters (̶E̶x̶a̶m̶p̶l̶e)
### Tag: header
Extra large highlighted text that will appear in the sidebar or chapter select.
Will be rendered with a normalized ID attribute for anchors.
### Tag: subheader
Large highlighted text.
Will be rendered with a normalized ID attribute for anchors.
### Tag: quote
Prefixes text content with `>` and renders it as grey with small text.
### Tag: code
Renders text as a big code block with syntax highlighting using the `syntax`
attribute. Supports horizontal overflow and adds support for has JavaScript
for a **Copy to Cliboard** action.
### Tag: image
+19
View File
@@ -0,0 +1,19 @@
Originally an indie studio was going to be made between me and another friend
under the moniker "suzzy games". We planned to make multiplayer games, mostly
ones that ran in the web browser so I researched and designed infastructure to
allow that. However this idea was scrapped and I opted to use my personal
brand 'pancakz' instead.
This website is forever stuck in limbo due to an unfinished refactor.
A lot of it's code was canabalized. Deleted and reafactored into 'template-auth'
which was itself was also canabalized and now feeds into every other project
that requires an authentication framework.
If you wish to see what this website looks like for yourself run the following
commands:
* npm install
* npm run preview
* explorer.exe http://localhost:8080
+56
View File
@@ -0,0 +1,56 @@
extends _TemplateSpecial.pug
block pageHeader
link(rel='stylesheet' href='file://styles/ui-account.css')
link(rel='stylesheet' href='file://styles/unique-oauth2.css')
meta(property='og:title' content='suzzy - Authorize App')
meta(property='og:description' content='Connect an application to your suzzy games account.')
meta(name='description' content='Connect an application to your suzzy games account.')
title suzzy - Authorize App
block pageContent
div(class='modal-wrapper modal-static')
//- Error Modal
div(id='oauth2-fail' class='oauth2-centered oauth2-container modal-container' hidden)
div(class='modal-content')
img(class='oauth2-icon' aria-hidden='true' src='/assets/images/icons/icon-cross.svg' alt='Icon for Cross')
p(class='text-header') An Error has Occurred
p(class='text-tip' id='fail-reason') Unknown Error
//- Loading Modal
div(id='oauth2-load' class='oauth2-centered oauth2-container modal-container')
div(class='oauth2-centered modal-content')
img(aria-hidden='true' src='/assets/images/icons/diagram-loading.svg' alt='Icon for Spinner' height=64)
p Loading Application
//- Content Modal
div(id='oauth2-flow' class='oauth2-container modal-container' hidden)
div(class='oauth2-content modal-content')
//- Header
div(class='divider-spacer')
div(class='oauth2-header')
div(class='oauth2-icons')
img(class='application-icon' id='image-user' onload='this.style.opacity=1')
img(aria-hidden='true' class='oauth2-diagram' src='/assets/images/icons/diagram-connection.svg' alt='Diagram for Connection')
img(class='application-icon' id='image-app' onload='this.style.opacity=1')
p(id='content-aname' class='text-header')
button(id='content-login' class='small' onclick='SignOut(false, true)')
div(class='divider-spacer')
div(class='divider-line')
//- Information
p(class='text-tip') This application will be allowed to:
div(id='content-scopes' class='scopes-wrapper text-box')
div(class='divider-line')
p(id='content-created' class='text-tip text-muted')
| &bull; Active since
p(id='content-redirect' class='text-tip text-muted')
| &bull; You'll be redirected to: #[br]
//- Actions
div(class='modal-actions')
p(class='modal-tooltip text-error' id='action-error')
button(class='default' id='action-cancel') Cancel
button(class='default' id='action-authorize') Authorize
block pageEscaped
script(src='/assets/scripts/login-oauth2.js')
@@ -0,0 +1,291 @@
(() => {
// Support Functions
const trimDataURL = (s, x = undefined) => s.slice(s.indexOf(',') + 1, x)
const redirectsToString = s => s.sort().join()
function PrepareTemplate(selector) {
const elem = $(selector)
elem.removeAttribute('hidden')
elem.removeAttribute('id')
elem.remove()
return elem
}
// No Applications Warning
let appCount = 0
function DisplayNoApps(newCount) {
_ActionCreate.removeAttribute('hidden')
appCount = newCount
appCount === 0
? _AlertEmpty.removeAttribute('hidden')
: _AlertEmpty.setAttribute('hidden', true)
}
// Displays Modal with Secret Key
function DisplaySecret(secretKey) {
CreateModal({
behaviour: {
title: 'Your Secret Key',
tooltip: 'Do not share this key with anyone!',
submitText: 'Close',
cancelText: '',
},
_secret: {
type: 'text',
title: '',
value: secretKey,
readonly: true,
}
})
}
// Use Application Template
const
_AlertEmpty = $('#alert-empty'),
_AlertLoad = $('#alert-load'),
_ActionCreate = $('#action-create'),
_Collection = $('#application-collection'),
_LinkTemplate = PrepareTemplate('.input-redirect'),
_ItemTemplate = PrepareTemplate('#application-template')
function DisplayApplication(a) {
let removingIcon = false
let unsavedChanges = false
const
item = _ItemTemplate.cloneNode(true),
elemStatus = $$(item, '#status-message'),
elemOptions = $$(item, '.application-options'),
elemRedirects = $$(item, '#container-redirects'),
previewIcon = $$(item, '#app-icon'),
previewName = $$(item, '#app-name'),
previewDesc = $$(item, '#app-desc'),
inputName = $$(item, '#input-name'),
inputDesc = $$(item, '#input-desc'),
inputIcon = $$(item, '#input-icon'),
inputIconFile = $$(item, '#input-icon-file'),
inputIconRemove = $$(item, '#input-icon-remove'),
inputRedirectNew = $$(item, '#input-redirect-new'),
inputSave = $$(item, '#input-save'),
inputDiscard = $$(item, '#input-discard'),
inputResetSecret = $$(item, '#input-reset'),
inputDelete = $$(item, '#input-delete'),
inputEdit = $$(item, '#input-edit')
function CreateRedirect(givenURL) {
const elem = _LinkTemplate.cloneNode(true)
const inputUrl = elem.firstChild
inputUrl.value = givenURL
inputUrl.addEventListener('change', UpdateForm)
elem.lastChild.addEventListener('click', () => {
elem.remove()
UpdateForm()
})
elemRedirects.appendChild(elem)
}
function ResetForm() {
removingIcon = false
previewName.textContent = a.name?.trim() || 'Untitled'
previewDesc.textContent = a.description?.trim() || 'No Description Provided.'
previewIcon.src = ImageURL('icons', a.id, a.icon, false, 128)
previewIcon.alt = `Icon for ${a.name}`
inputIconFile.value = null
inputDesc.value = a.description
inputName.value = a.name
elemRedirects.innerHTML = ''
a.redirects.forEach(CreateRedirect)
UpdateForm()
}
function UpdateForm() {
// New Redirect
elemRedirects.childElementCount >= 10
? inputRedirectNew.setAttribute('hidden', true)
: inputRedirectNew.removeAttribute('hidden')
// Remove Icon
!removingIcon && (a.icon !== null || inputIconFile.files.length > 0)
? inputIconRemove.removeAttribute('disabled')
: inputIconRemove.setAttribute('disabled', true)
// Save/Discard Changes
unsavedChanges =
Object.keys(CollectBody()).length &&
Array.from(elemRedirects.childNodes).every(e => e.firstChild.checkValidity())
if (unsavedChanges) {
inputDiscard.removeAttribute('disabled')
inputSave.removeAttribute('disabled')
} else {
inputDiscard.setAttribute('disabled', true)
inputSave.setAttribute('disabled', true)
}
}
function CollectBody() {
const body = {}
if ((inputName.value.trim() || a.name) !== a.name)
body['name'] = inputName.value
if (inputDesc.value.trim() !== (a.description || ''))
body['description'] = inputDesc.value
if (removingIcon && body.icon)
body['icon'] = ''
if (inputIconFile.files.length)
body['icon'] = trimDataURL(previewIcon.src)
const collectedRedirects = Array
.from(elemRedirects.childNodes)
.filter(e => e.firstChild.checkValidity())
.map(e => e.firstChild.value)
if (redirectsToString(collectedRedirects) !== redirectsToString(a.redirects)) {
body.redirects = collectedRedirects
}
return body
}
// Set/Remove Application Icon
inputIconRemove.addEventListener('click', () => {
previewIcon.src = ImageURL('icon', a.id, null)
inputIconFile.value = null
removingIcon = true
UpdateForm()
})
inputIconFile.addEventListener('change', ev => {
const file = ev.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = e => {
previewIcon.src = e.target.result
removingIcon = false
UpdateForm()
}
reader.readAsDataURL(file)
})
// Field Updating
inputName.addEventListener('input', () => previewName.textContent = inputName.value.trim() || a.name)
inputDesc.addEventListener('input', () => previewDesc.textContent = inputDesc.value.trim() || a.description)
for (const needsListener of [inputName, inputDesc]) {
needsListener.addEventListener('change', UpdateForm)
}
// Button Listeners
inputRedirectNew.addEventListener('click',
() => CreateRedirect('')
)
inputDiscard.addEventListener('click',
() => ResetForm()
)
inputIcon.addEventListener('click',
() => inputIconFile.click()
)
inputEdit.addEventListener('click', () => {
elemOptions.classList.toggle('options-hidden')
ResetForm()
})
inputSave.addEventListener('click', () => {
if (!unsavedChanges) return
const unlock = LockInteraction()
elemStatus.textContent = 'Saving Changes...'
SendRequest('PATCH', `/api/v1/users/@me/applications/${a.id}`, CollectBody(), 1)
.then(([_, patchedBody]) => {
elemStatus.textContent = 'Changes Saved!'
elemStatus.classList.remove('text-error')
a = patchedBody
unlock()
ResetForm()
})
.catch(e => {
elemStatus.textContent = e
elemStatus.classList.add('text-error')
unlock()
})
// we dont use finally block here because race condition :p
})
inputResetSecret.addEventListener('click', () => CreateModal({
behaviour: {
title: 'Reset Secret Key?',
tooltip: `${a.name} will stop functioning until you update the secret key in your code.`,
submitText: 'Reset',
onSubmit: () => new Promise(ok => {
SendRequest('DELETE', `/api/v1/users/@me/applications/${a.id}/reset`, undefined, 2)
.then(([_, body]) => {
DisplaySecret(body.secret_key)
ok()
})
.catch(ok)
})
}
}))
inputDelete.addEventListener('click', () => CreateModal({
behaviour: {
title: 'Delete Application?',
tooltip: `${a.name} will be deleted and all connections will be severed. This action cannot be undone.`,
submitText: 'Delete',
onSubmit: () => new Promise(ok => {
SendRequest('DELETE', `/api/v1/users/@me/applications/${a.id}`, undefined, 2)
.then(() => {
item.remove()
DisplayNoApps(appCount - 1)
ok()
})
.catch(ok)
})
}
}))
// Append to top
// Official Application?
if ((a.flags & 1 << 3) !== 0) {
$$(item, '#verified-message').removeAttribute('hidden')
}
$$(item, '#input-clientid').textContent += a.id
ResetForm()
_Collection.appendChild(item)
}
// Fetch Connections
SendRequest('GET', '/api/v1/users/@me/applications', null, 1)
.then(([_, body]) => {
// Display Applications
body
.sort((a, b) => new Date(b.created) - new Date(a.created))
.forEach(DisplayApplication)
DisplayNoApps(body.length)
// Bind Create Application
_ActionCreate.addEventListener('click', function ActionCreate() {
CreateModal({
behaviour: {
title: 'Create Application',
tooltip: 'Name it something cool like Phase Connect.',
onSubmit: ({ name }) => new Promise(ok => {
SendRequest('POST', '/api/v1/users/@me/applications', { name }, 1)
.then(([_, body]) => {
DisplaySecret(body.secret_key)
DisplayNoApps(appCount + 1)
DisplayApplication(body)
ok()
})
.catch(ok)
})
},
_divider: { type: 'divider-line' },
name: {
type: 'text',
required: true,
title: 'Name',
validator: function ValidateName(v) {
if (v.length > 24) return 'Application name cannot be longer than 32 characters'
if (v.length < 1) return 'Application name cannot be shorter than 1 character'
return true
}
},
})
})
})
.catch(e => {
// Display Error Message
$('#alert-error').removeAttribute('hidden')
$('#error-message').textContent = e
console.log(e)
})
.finally(() => {
// Hide Loading Symbol
_AlertLoad.setAttribute('hidden', true)
})
})()
@@ -0,0 +1,94 @@
(async () => {
const _AlertEmpty = $('#alert-empty')
const _AlertLoad = $('#alert-load')
const _Collection = $('#application-collection')
// Prepare Templates
function prepareTemplate(name) {
const elem = $(name)
elem.removeAttribute('hidden')
elem.removeAttribute('id')
elem.remove()
return elem
}
const _ItemTemplate = prepareTemplate('#application-template')
const _ScopeTemplate = prepareTemplate('#scope-template')
// Fetch Connections
SendRequest('GET', '/api/v1/users/@me/connections', null, 1)
.then(([_, body]) => {
if (body.length === 0) {
_AlertEmpty.removeAttribute('hidden')
return
}
body
.sort((a, b) => new Date(b.created) - new Date(a.created))
.forEach(connection => {
// Metadata
const { name, flags, description, id, icon } = connection.application
const Template = _ItemTemplate.cloneNode(true)
$$(Template, '.text-header').textContent = name
$$(Template, '.text-tip').textContent = description || 'No Description Provided.'
// Images
const Image = $$(Template, '.application-icon')
Image.src = ImageURL('icons', id, icon, false, 128)
Image.alt = `Icon for ${name}`
// Scopes
const ScopesParent = Template.querySelector('.scopes')
_Scopes.forEach(s => {
if ((connection.scopes & s[0]) !== 0) {
const Scope = _ScopeTemplate.cloneNode(true)
Scope.lastChild.textContent = s[1]
ScopesParent.appendChild(Scope)
}
})
// Official Application?
if ((flags & 1 << 3) !== 0) {
$$(Template, '#verified-message').removeAttribute('hidden')
}
// Buttons
const Button = $$(Template, 'button')
Button.title = `Disconnect Application ${name}`
Button.addEventListener('click', async () => {
await CreateModal({
behaviour: {
title: 'Disconnect Application?',
tooltip: 'The application ' + name + ' will be disconnected from your account.'
}
})
SendRequest('DELETE', `/api/v1/users/@me/connections/${connection.id}`, null, 2)
.then(() => {
// Remove Item from List
body.length--
Template.remove()
if (body.length === 0) {
_AlertEmpty.removeAttribute('hidden')
_AlertLoad.setAttribute('hidden', true)
return
}
})
.catch(e => {
$$(Template, '#error-message').textContent = e
})
})
// Append
_Collection.appendChild(Template)
})
})
.catch(e => {
// Display Error Message
$('#alert-error').removeAttribute('hidden')
$('#error-message').textContent = e
console.log(e)
})
.finally(() => {
// Hide Loading Symbol
_AlertLoad.setAttribute('hidden', true)
})
})()
@@ -0,0 +1,80 @@
(async () => {
const _Collection = $('#application-collection')
const _Template = $('#device-template')
_Template.removeAttribute('hidden')
_Template.removeAttribute('id')
_Template.remove()
// Fetch Sessions
SendRequest('GET', '/api/v1/users/@me/security/sessions', null, 1)
.then(([_, body]) => {
// There's at minimum one session so we
// don't need a the 'No Connections' type message here.
body.sessions
.sort((a, b) => new Date(b.last_used) - new Date(a.last_used))
.forEach(session => {
// Metadata
const Template = _Template.cloneNode(true)
const Me = (session.fingerprint === body.fingerprint)
if (Me) $$(Template, '#me').removeAttribute('hidden')
$$(Template, '#browser').textContent = session.browser
$$(Template, '#location').textContent = session.location
$$(Template, '#lastseen').textContent = (() => {
const secondsAgo = (new Date() - new Date(session.last_used)) / 1000 | 0
if (secondsAgo < 60) {
return 'just now'
} else {
const timeUnits = [
['minute', 60],
['hour', 3600],
['day', 86400],
['week', 604800],
['month', 2592000],
];
for (const [unit, divisor] of timeUnits) {
if (secondsAgo < (divisor * 60)) {
const count = Math.floor(secondsAgo / divisor)
return `${count} ${unit}${count > 1 ? 's' : ''} ago`
}
}
}
})()
// Buttons
const Button = $$(Template, '.device-action')
Button.title = `Revoke Access for '${session.browser}' from '${session.location}'`
$$(Template, '.device-action').onclick = async () => {
await CreateModal({
behaviour: {
title: 'Revoke Session?',
tooltip: 'You\'ll have to manually log back in on this device.'
}
})
SendRequest('DELETE', `/api/v1/users/@me/security/sessions/${session.id}`, null, 2)
.then(() => {
// Redirect User to Login page if revoked self.
Template.remove()
if (Me) window.location.href = '/login'
})
.catch(e => {
$$(Template, '#error-message').textContent = e
})
}
// Append
_Collection.appendChild(Template)
})
})
.catch(e => {
// Display Error Message
$('#alert-error').removeAttribute('hidden')
$('#error-message').textContent = e
console.log(e)
})
.finally(() => {
// Hide Loading Symbol
$('#alert-load').setAttribute('hidden', true)
})
})()
@@ -0,0 +1,263 @@
(() => {
const
_Profile = $('.profile-wrapper'),
_Preview = $('.profile-container'),
_Status = $('#profile-status'),
// Option Elements
iDisplayname = $('#input-displayname'),
iSubtitle = $('#input-subtitle'),
iBio = $('#input-bio'),
iAvatarFile = $('#input-avatar-file'),
iBannerFile = $('#input-banner-file'),
iBannerColor = $('#input-banner'),
iBorderColor = $('#input-border'),
iBackgroundColor = $('#input-background'),
iSubmit = $('#button-submit'),
iCancel = $('#button-cancel'),
// Preview Elements
pBanner = $('#profile-banner'),
pAvatar = $('#profile-avatar'),
pBadges = $('#profile-badges'),
pDisplayname = $('#profile-displayname'),
pUsername = $('#profile-username'),
pSubtitle = $('#profile-subtitle'),
pDivider = $('#profile-divider'),
pBio = $('#profile-bio'),
// Buttons
bAvatar = $('#button-avatar-remove'),
bBanner = $('#button-banner-remove')
// Elements that the user interacts with in real time
const _InputElements = [
iDisplayname,
iSubtitle,
iBio,
iBannerColor,
iBorderColor,
iBackgroundColor,
// These elements have async calls
// they'll update the preview when complete
// pBanner,
// pAvatar,
]
const _RecognizedBadges = [
[1 << 0, 'crown', 'The Boss'],
[1 << 1, 'star', 'V.I.P'],
[1 << 2, 'picross', 'Picross Master'],
]
SendRequest('GET', '/api/v1/users/@me', null, 1)
.then(([_, body]) => {
let removingBanner = false
let removingAvatar = false
let unsavedChanges = false
const hexToDec = e => parseInt(e.value.slice(1), 16)
const trimDataURL = (s, x = undefined) => s.slice(s.indexOf(',') + 1, x)
const decToHex = (e, x) => '#' + (e?.toString(16).padStart('6', 0) || x)
function FormReset() {
const colorBanner = decToHex(body.accent_banner, '1d1e2e')
const colorBorder = decToHex(body.accent_border, '282a3f')
const colorBackground = decToHex(body.accent_background, '333550')
// Reset Options
iDisplayname.value = body.displayname
iSubtitle.value = body.subtitle
iBio.value = body.biography
iBannerColor.value = colorBanner
iBorderColor.value = colorBorder
iBackgroundColor.value = colorBackground
iAvatarFile.value = null
iBannerFile.value = null
// Reset Preview
pDisplayname.textContent = body.displayname
pUsername.textContent = '@' + body.username
pSubtitle.textContent = body.subtitle
pBio.textContent = body.biography
body.biography
? pDivider.removeAttribute('hidden')
: pDivider.setAttribute('hidden', true)
pAvatar.src = ImageURL('avatars', body.id, body.avatar, true, 128)
pBanner.style.backgroundImage = body.banner
? `url('${ImageURL('banners', body.id, body.banner, true, 512)}')`
: ''
_Profile.style.setProperty('--accent-banner', colorBanner)
_Profile.style.setProperty('--accent-border', colorBorder)
_Profile.style.setProperty('--accent-background', colorBackground)
iBackgroundColor.oninput()
// Disable Buttons
removingAvatar = false
removingBanner = false
FormUpdated()
}
function FormUpdated() {
unsavedChanges = (Object.entries(CollectBody()).length > 0)
if (unsavedChanges) {
iCancel.removeAttribute('disabled')
iSubmit.removeAttribute('disabled')
} else {
iCancel.setAttribute('disabled', true)
iSubmit.setAttribute('disabled', true)
}
!removingBanner && (body.banner !== null || iBannerFile.files.length > 0)
? bBanner.removeAttribute('disabled')
: bBanner.setAttribute('disabled', true)
!removingAvatar && (body.avatar !== null || iAvatarFile.files.length > 0)
? bAvatar.removeAttribute('disabled')
: bAvatar.setAttribute('disabled', true)
}
function CollectBody() {
const data = {}
// Collect Textboxes
if ((iDisplayname.value.trim() || body.displayname) !== body.displayname)
data['displayname'] = iDisplayname.value
if (iBio.value.trim() !== (body.biography || ''))
data['biography'] = iBio.value
if (iSubtitle.value.trim() !== (body.subtitle || ''))
data['subtitle'] = iSubtitle.value
// Collect Colors
const decBannerColor = hexToDec(iBannerColor)
if (decBannerColor !== (body.accent_banner || 1908270))
data['accent_banner'] = decBannerColor
const decBorderColor = hexToDec(iBorderColor)
if (decBorderColor !== (body.accent_border || 2632255))
data['accent_border'] = decBorderColor
const decBackgroundColor = hexToDec(iBackgroundColor)
if (decBackgroundColor !== (body.accent_background || 3355984))
data['accent_background'] = decBackgroundColor
// Collect Images
if (removingAvatar && body.avatar) data['avatar'] = ''
if (removingBanner && body.banner) data['banner'] = ''
if (iAvatarFile.files.length)
data['avatar'] = trimDataURL(pAvatar.src)
if (iBannerFile.files.length)
data['banner'] = trimDataURL(pBanner.style.backgroundImage, -2)
console.log(data)
return data
}
// Templating
_InputElements.forEach(e => e.addEventListener('change', FormUpdated))
_RecognizedBadges.forEach(b => {
if ((body.public_flags & b[0]) !== 0) {
const img = document.createElement('img')
img.src = `/assets/images/icons/badge-${b[1]}.svg`
img.title = b[2]
img.alt = b[2]
pBadges.appendChild(img)
}
})
// Section: About Me
iDisplayname.oninput = () => pDisplayname.textContent = (iDisplayname.value || body.displayname).trim()
iSubtitle.oninput = () => pSubtitle.textContent = iSubtitle.value.trim()
iBio.oninput = () => {
const content = (iBio.value || '')
pBio.textContent = content.trim()
content.length > 0
? pDivider.removeAttribute('hidden')
: pDivider.setAttribute('hidden', true)
}
// Section: Images
$('#button-avatar').onclick = () => iAvatarFile.click()
$('#button-banner').onclick = () => iBannerFile.click()
bAvatar.onclick = () => {
pAvatar.src = ImageURL('avatars', body.id, null)
iAvatarFile.value = null
removingAvatar = true
FormUpdated()
}
bBanner.onclick = () => {
pBanner.style.backgroundImage = null
iBannerFile.value = null
removingBanner = true
FormUpdated()
}
iAvatarFile.onchange = (ev) => {
const file = ev.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = e => {
pAvatar.src = e.target.result
removingAvatar = false
FormUpdated()
}
reader.readAsDataURL(file)
}
iBannerFile.onchange = (ev) => {
const file = ev.target.files[0]
if (!file) return
const reader = new FileReader()
reader.onload = e => {
pBanner.style.backgroundImage = `url(${e.target.result})`
removingBanner = false
FormUpdated()
}
reader.readAsDataURL(file)
}
// Section: Accents
iBannerColor.oninput = () => _Profile.style.setProperty('--accent-banner', iBannerColor.value)
iBorderColor.oninput = () => _Profile.style.setProperty('--accent-border', iBorderColor.value)
iBackgroundColor.oninput = () => {
let hexColor = iBackgroundColor.value
_Profile.style.setProperty('--accent-background', hexColor)
// Check Luminance of Background Color
hexColor = hexColor.replace('#', '')
var r = parseInt(hexColor.substr(0, 2), 16)
var g = parseInt(hexColor.substr(2, 2), 16)
var b = parseInt(hexColor.substr(4, 2), 16)
if (((0.299 * r) + (0.587 * g) + (0.114 * b)) / 255 > 0.85) {
_Preview.classList.add('profile-dark')
} else {
_Preview.classList.remove('profile-dark')
}
}
// Section: Actions
iCancel.onclick = () => FormReset()
iSubmit.onclick = () => {
if (!unsavedChanges) return
const unlock = LockInteraction()
_Status.textContent = 'Saving Changes...'
SendRequest('PATCH', '/api/v1/users/@me', CollectBody(), 1)
.then(([_, patchedBody]) => {
_Status.textContent = 'Profile Saved!'
_Status.classList.remove('text-error')
localStorage.removeItem('cache_home_me') // Used by page-home.js
body = patchedBody
unlock()
FormReset()
})
.catch(e => {
_Status.textContent = e
_Status.classList.add('text-error')
unlock()
})
// we dont use finally block here because race condition :p
}
// Display Profile
_Profile.removeAttribute('hidden')
FormReset()
})
.catch(e => {
// Display Error Message
$('#alert-error').removeAttribute('hidden')
$('#error-message').textContent = e
console.log(e)
})
.finally(() => {
// Hide Loading Symbol
$('#alert-load').setAttribute('hidden', true)
})
})()
@@ -0,0 +1,281 @@
(() => {
const
elemPreviewEmail = $('#preview-email'),
actionEmailReveal = $('#email-reveal'),
actionEmailEdit = $('#email-edit'),
actionEmailResend = $('#email-resend'),
actionPassChange = $('#password-change'),
actionMFASetup = $('#mfa-setup-start'),
actionMFASetupRemove = $('#mfa-setup-remove'),
actionMFARecoveryView = $('#mfa-recovery-view'),
actionMFARecoveryRegen = $('#mfa-recovery-regen'),
actionDangerDeleteAccount = $('#danger-deleteaccount'),
errorMFA = $('#error-mfa'),
errorEmail = $('#error-email'),
errorDanger = $('#error-danger')
SendRequest('GET', '/api/v1/users/@me', null, 1)
.then(([_, body]) => {
// Email
let emailHidden = false
let emailTemplate = elemPreviewEmail.textContent
actionEmailReveal.onclick = () => {
let newPreview
if (emailHidden) {
newPreview = body.email
actionEmailReveal.textContent = 'Hide Email Address'
} else {
const usernameLength = body.email.indexOf('@')
const censoredSegment = ''.padStart(usernameLength > 12 ? 12 : usernameLength, '*')
const hostSegment = body.email.slice(usernameLength)
newPreview = censoredSegment + hostSegment
actionEmailReveal.textContent = 'Reveal Email Address'
}
elemPreviewEmail.textContent = (emailTemplate + newPreview)
emailHidden = !emailHidden
}
actionEmailReveal.onclick()
if (!body.verified) {
actionEmailResend.removeAttribute('hidden')
actionEmailResend.onclick = () => {
const unlock = LockInteraction()
SendRequest('POST', '/api/v1/users/@me/security/email', null, 1)
.then(() => CreateModal({
behaviour: {
cancelText: '',
submitText: 'Close',
title: 'Verification Email Sent',
tooltip: 'An email to verify this email address has been sent. Please check your spam or inbox.',
}
}))
.catch(e => errorEmail.textContent = e)
.finally(unlock)
}
}
actionEmailEdit.onclick = () => {
CreateModal({
behaviour: {
title: 'Update Email',
tooltip: 'Changing your email address will mark it as unverified.',
onSubmit: ({ email }) => new Promise(ok => {
SendRequest('PATCH', '/api/v1/users/@me/security/email', { email }, 2)
.then(() => window.location.reload())
.catch(ok)
})
},
'_divider': { type: 'divider-line' },
'email': {
type: 'text',
required: true,
title: 'Email Address',
validator: TestEmailSuite,
}
})
}
// Password
actionPassChange.onclick = () => {
CreateModal({
behaviour: {
title: 'Change Password',
tooltip: 'Enter your current password and new password.',
onSubmit: ({ old_password, new_password }) => new Promise(ok => {
SendRequest(
'PATCH', '/api/v1/users/@me/security/password',
{ old_password, new_password }, 1
)
.then(() => ok())
.catch(ok)
})
},
'_link': {
type: 'link',
title: 'Forgot Password?',
href: '/password-reset',
},
'_divider': { type: 'divider-line' },
'old_password': {
type: 'password',
required: true,
title: 'Old Password',
},
'new_password': {
type: 'password',
required: true,
title: 'New Password',
validator: (v, { new_password_again }) => {
if (v !== new_password_again) return 'Passwords do not Match'
return TestPasswordSuite(v)
},
},
'new_password_again': {
type: 'password',
required: true,
title: 'New Password Again',
validator: (v, { new_password }) => {
if (v !== new_password) return 'Passwords do not Match'
return TestPasswordSuite(v)
},
},
})
}
// Multi-Factor
function RecoveryModal(codes) {
return CreateModal({
behaviour: {
title: 'Recovery Codes',
tooltip:
'Keep these recovery codes safe! ' +
'You\'ll lose access to your account if both recovery codes and your authenticator app are lost.',
submitText: 'Download',
cancelText: 'Close',
onSubmit: () => {
// Generate Text
const filename = `Recovery Codes for ${body.username}.txt`
const filedata =
`Recovery Codes for ${body.username} (${body.email})\n` +
`Generated on ${new Date().toLocaleString()}\n` +
`------------\n` +
codes.map(c => `> ${c}\n`).join('');
// Fake Download Element
const a = document.createElement('a')
a.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(filedata))
a.setAttribute('download', filename)
a.style.display = 'none'
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
return ' '
}
},
'_codes': {
type: 'recovery-codes',
codes: codes
}
})
}
if (body.mfa_enabled) {
actionMFASetupRemove.removeAttribute('hidden')
actionMFASetupRemove.onclick = async () => {
await CreateModal({
behaviour: {
title: 'Remove Authenticator App?',
tooltip: 'This will disable MFA on your account.',
}
})
const unlock = LockInteraction()
SendRequest('DELETE', '/api/v1/users/@me/security/mfa/setup', null, 2)
.then(() => window.location.reload())
.catch(e => errorMFA.textContent = e)
.finally(unlock)
}
actionMFARecoveryView.removeAttribute('hidden')
actionMFARecoveryView.onclick = () => {
const unlock = LockInteraction()
SendRequest('GET', '/api/v1/users/@me/security/mfa/codes', null, 2)
.then(([_, codes]) => RecoveryModal(codes))
.catch(e => errorMFA.textContent = e)
.finally(unlock)
}
actionMFARecoveryRegen.removeAttribute('hidden')
actionMFARecoveryRegen.onclick = async () => {
await CreateModal({
behaviour: {
title: 'Reset Recovery Codes?',
tooltip: 'This will void the previously generated codes.',
}
})
const unlock = LockInteraction()
SendRequest('DELETE', '/api/v1/users/@me/security/mfa/codes', null, 2)
.then(([_, codes]) => RecoveryModal(codes))
.catch(e => errorMFA.textContent = e)
.finally(unlock)
}
} else {
actionMFASetup.removeAttribute('hidden')
actionMFASetup.onclick = () => {
const unlock = LockInteraction()
SendRequest('GET', '/api/v1/users/@me/security/mfa/setup', null, 1)
.then(([_, mfa]) => {
CreateModal({
behaviour: {
title: 'Setup Authenticator App',
tooltip: 'Scan the QR code using your Authenticator App and enter the generated passcode.',
onSubmit: ({ passcode }) => new Promise(ok => {
const unlock = LockInteraction()
SendRequest('POST', '/api/v1/users/@me/security/mfa/setup', { passcode }, 1)
.then((/* 204 No Content */) => {
RecoveryModal(mfa.recovery_codes)
.finally(() => window.location.reload())
ok()
})
.catch(ok)
.finally(unlock)
})
},
'_spacer1': { type: 'divider-line' },
'_qrcode': {
type: 'qr-code',
src: mfa.image,
},
'_spacer2': { type: 'divider-line' },
'_secret': {
type: 'text',
title: 'Can\'t Scan the QR Code? Use this secret key:',
value: mfa.secret,
readonly: true,
},
passcode: {
required: true,
type: 'password',
title: 'Passcode',
autocomplete: 'one-time-code',
placeholder: '123456',
validator: v => {
if (!/[0-9]{6}/.test(v)) return 'Invalid Passcode'
return true
}
},
})
})
.catch(e => errorMFA.textContent = e)
.finally(unlock)
}
}
// Danger Zone
actionDangerDeleteAccount.onclick = async () => {
// Prompt Modal
await CreateModal({
behaviour: {
title: 'Delete Account?',
tooltip:
'This will start a countdown where afterwards your account and all of its data will be deleted. ' +
'More details will be sent to your email address.',
}
})
const unlock = LockInteraction()
SendRequest('DELETE', '/api/v1/users/@me', null, 2)
.then(() => SignOut())
.catch(e => errorDanger.textContent = e)
.finally(unlock)
}
// Display Menu
$('#security-wrapper').removeAttribute('hidden')
})
.catch(e => {
// Display Error Message
$('#alert-error').removeAttribute('hidden')
$('#error-message').textContent = e
console.log(e)
})
.finally(() => {
// Hide Loading Symbol
$('#alert-load').setAttribute('hidden', true)
})
})()
@@ -0,0 +1,67 @@
let x = {
static: true,
submitText: 'Log In',
cancelText: '',
onSubmit: ({ email, password }) => new Promise(submitResult => {
const unlock = LockInteraction()
SendRequest('POST', '/api/v1/auth/login', { email, password }, 0, false)
.then(([resp, body]) => {
if (!resp.ok) {
// Display Custom Modal
switch (body.code) {
case 2000: // ERROR_MFA_EMAIL_SENT
return CreateModal({
behaviour: {
cancelText: '',
title: 'Verify Login from a New Device',
tooltip: 'Check your email for a verification link to authorize access from this device.'
},
})
.catch(() => { /** Silence Modal Closing */ })
case 2001: // ERROR_MFA_PASSCODE_REQUIRED
return CreateModal({
'behaviour': {
title: 'Passcode Required',
tooltip: 'Please enter your Authenticator Passcode or Recovery Code to log in.',
onSubmit: ({ passcode }) => new Promise(mfaResult => {
SendRequest('POST', '/api/v1/auth/login', { email, password, passcode }, 0)
.then(([_, body]) => {
// Store Credentials
localStorage.setItem('auth_token', body.token)
localStorage.setItem('auth_expires', Date.now() + (body.expires_in * 1000))
FollowRedirect('/account/profile')
})
.catch(mfaResult)
})
},
'_spacer': {
type: 'divider-line'
},
'passcode': {
required: true,
type: 'password',
title: 'Passcode',
autocomplete: 'one-time-code',
placeholder: '123456',
validator: v => {
if (!/([0-9]{6}|[A-z0-9]{8})/.test(v))
return 'Invalid Passcode'
return true
}
}
})
.catch(() => { /** Silence Modal Closing */ })
default:
return submitResult(body.error || body)
}
} else {
// Store Credentials
localStorage.setItem('auth_token', body.token)
localStorage.setItem('auth_expires', Date.now() + (body.expires_in * 1000))
FollowRedirect('/account/profile')
}
})
.catch(submitResult)
.finally(unlock)
}),
}
@@ -0,0 +1,78 @@
(() => {
// Fetch Application and Profile
const href = window.location.href
const query = href.slice(href.indexOf('?') + 1)
SendRequest('GET', '/api/v1/oauth2/authorize', query, 1)
.then(([_, body]) => {
const
me = body.user,
app = body.application,
ScopesParent = $('#content-scopes'),
imageUser = $('#image-user'),
imageApp = $('#image-app')
// Set Avatars
imageUser.src = ImageURL('avatars', me.id, me.avatar, false, 128)
imageApp.src = ImageURL('icons', app.id, app.icon, false, 128)
imageUser.alt = `Avatar for '${me.username}'`
imageApp.alt = `Icon for Application '${app.name}'`
// Fill Template
$('#content-login').textContent = `Logged in as ${me.username}, not you?`
$('#content-aname').textContent = `Connect ${app.name}?`
$('#content-redirect').textContent += body.redirect
$('#content-created').textContent += (() => {
const t = new Date(app.created)
const m = ['January', 'Feburary', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][t.getMonth()]
const d = t.getDate()
const y = t.getFullYear()
return `${m} ${d}${d === 1 ? 'st' : d === 2 ? 'nd' : d === 3 ? 'rd' : 'th'}, ${y}`
})()
_Scopes.forEach(s => {
if ((body.scopes & s[0]) !== 0) {
const div = document.createElement('div')
div.classList.add('scope')
const dot = document.createElement('div')
dot.classList.add('dot-green')
const txt = document.createElement('p')
txt.textContent = s[1]
div.append(dot, txt)
ScopesParent.appendChild(div)
}
})
// Cancel Request, go to application with error...
$('#action-cancel').onclick = (ev) => {
if (!ev.isTrusted) return
const url = new URL(body.redirect)
url.searchParams.set('error', 'access_denied')
url.searchParams.set('error_description', 'User Declined')
window.location.assign(url.toString())
}
// Complete Request, go to application with code...
const actionError = $('#action-error')
$('#action-authorize').onclick = (ev) => {
if (!ev.isTrusted) return
const unlock = LockInteraction(true)
actionError.textContent = ''
SendRequest('POST', '/api/v1/oauth2/authorize', query, 1)
.then(([_, body]) => window.location.assign(body.location))
.catch(e => {
actionError.textContent = e.error || e
console.log(e)
})
.finally(() => unlock())
}
$('#oauth2-flow').removeAttribute('hidden')
})
.catch(e => {
$('#oauth2-fail').removeAttribute('hidden')
$('#fail-reason').textContent = e.error || e
console.log(e)
})
.finally(() => {
$('#oauth2-load').setAttribute('hidden', true)
})
})()
@@ -0,0 +1,389 @@
/* Collections */
div.collection-wrapper {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
div.collection-item {
width: 100%;
height: fit-content;
border-radius: 8px;
overflow: hidden;
background-color: var(--background-layer-4);
border: 2px solid var(--background-layer-1);
box-sizing: border-box;
padding: 16px;
gap: 8px;
}
div.text-box {
background-color: var(--background-layer-5);
box-sizing: border-box;
border-radius: 8px;
padding: 8px;
}
div.collection-message {
width: 100%;
padding: 32px;
text-align: center;
box-sizing: border-box;
}
div.collection-message img,
div.collection-message i {
fill: var(--background-layer-0);
height: 48px;
width: 48px;
}
/* Page: Devices */
div.item-device {
display: flex;
align-items: center;
}
div.device-icon {
min-height: 72px;
min-width: 72px;
padding: 8px;
border-radius: 100%;
box-sizing: border-box;
background-color: var(--background-layer-4);
border: 2px solid var(--background-layer-1);
display: flex;
align-items: center;
justify-content: center;
}
div.device-icon img {
fill: var(--background-layer-0);
height: 40px;
width: 40px;
}
div.device-info {
flex-basis: 100%;
}
div.info-alongside {
display: flex;
flex-wrap: wrap;
align-items: center;
overflow: hidden;
gap: 8px;
}
button.device-action {
fill: var(--background-layer-1);
height: 24px;
width: 24px;
background-color: transparent;
cursor: pointer;
border: none;
transition: var(--transition-time) ease-in-out fill;
}
button.device-action:hover,
button.device-action:focus-visible {
fill: var(--text-error-1);
}
/* Page: Connections */
div.application-header {
display: flex;
align-items: self-start;
justify-content: space-between;
gap: 8px;
}
img.application-icon {
border-radius: 100%;
box-sizing: border-box;
border: 4px solid var(--background-layer-1);
background-color: var(--background-layer-4);
min-height: 64px;
max-height: 64px;
min-width: 64px;
max-width: 64px;
opacity: 0;
background-size: contain;
transition: var(--transition-time) ease-in-out opacity;
}
div.application-info {
flex-basis: 100%;
overflow: hidden;
}
div.application-extended {
display: grid;
gap: 8px;
transition: var(--transition-time) ease-in-out all;
height: -8px;
}
div.scopes-wrapper {
display: grid;
}
div.scope {
display: flex;
gap: 4px;
align-items: center;
}
div.dot-green {
width: 8px;
height: 8px;
border-radius: 100%;
background-color: #42f495;
}
/* Page: Profile */
div.profile-wrapper {
display: flex;
}
div.profile-options,
div.profile-container {
flex-basis: 50%;
box-sizing: border-box;
padding: 4px;
}
div.profile-options {
display: grid;
gap: 8px;
}
div.widget-container {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
align-items: flex-start
}
div.widget-color {
display: grid;
place-items: center;
}
input[type='color']::-webkit-color-swatch-wrapper {
padding: 0;
}
input.color-preview {
cursor: pointer;
border-radius: 8px;
background-color: white;
height: 48px;
width: 96px;
border: 2px solid var(--background-layer-2);
transition: var(--transition-time) ease-in-out all;
}
div.profile-container {
min-width: 350px;
height: fit-content;
background-color: var(--accent-background);
border: 4px solid var(--accent-border);
box-sizing: border-box;
border-radius: 8px;
overflow: hidden;
margin: 16px;
padding: 0;
}
#profile-banner {
width: 100%;
height: 128px;
border-bottom: 4px solid var(--accent-border);
background-color: var(--accent-banner);
background-position: center;
background-size: cover;
}
div.profile-header {
display: flex;
justify-content: space-between;
box-sizing: border-box;
padding: 8px 8px 0 8px;
}
#profile-avatar {
width: 96px;
height: 96px;
margin-top: -60px;
border-radius: 100%;
border: 4px solid var(--accent-border);
background-color: var(--accent-banner);
object-fit: cover;
}
#profile-badges {
height: fit-content;
width: fit-content;
display: flex;
flex-wrap: wrap;
background-color: rgb(0, 0, 0, 0.4);
border-radius: 8px;
padding: 8px;
gap: 4px;
}
#profile-badges:empty {
display: none;
}
#profile-badges img {
height: 20px;
width: 20px;
}
div.profile-about {
background-color: rgba(0, 0, 0, 0.4);
margin: 8px;
padding: 8px;
border-radius: 8px;
box-sizing: border-box;
}
div.profile-about p,
div.profile-about pre {
transition: var(--transition-time) ease-in-out color;
max-height: 300px;
overflow: hidden;
text-wrap: balance;
}
div.profile-dark p,
div.profile-dark pre {
color: var(--text-layer-2);
}
#profile-displayname {
font-size: large;
}
#profile-username {
font-size: small;
opacity: 0.80;
}
#profile-divider {
background-color: var(--accent-border);
opacity: 0.5;
}
/* Page: Applications */
div.application-options {
display: grid;
gap: 8px;
}
div.options-hidden {
height: 0;
overflow: hidden;
}
div.input-redirect {
display: flex;
gap: 8px;
}
div.input-redirect button {
min-width: fit-content;
height: 100%;
}
div#container-redirects {
display: grid;
gap: 8px;
}
/* Mobile Styling */
@media (max-width: 1024px) {
div.main-content {
border-right: none;
}
}
@media (max-width: 640px) {
div.main-content {
border: none;
background-color: transparent;
}
div.main-wrapper {
display: block;
}
div.main-wrapper div.divider-line {
display: block !important;
}
a.navigation-link {
font-size: large;
}
div.main-aside {
width: 100%;
max-width: 100%;
}
div.main-content {
width: 100%;
height: fit-content;
min-height: 100vh;
max-width: 100%;
}
div.collection-item {
background-color: var(--background-layer-4);
border: 2px solid var(--background-layer-3);
}
div.device-icon {
background-color: var(--background-layer-4);
border: 2px solid var(--background-layer-1);
}
/* Page: Connections */
div.application-header {
display: block;
}
div.application-header button {
margin: 8px 0;
width: 100%;
}
div.text-box {
background-color: var(--background-layer-3);
}
/* Page: Devices */
button.device-action {
height: 40px;
width: 40px;
}
div.info-alongside {
text-wrap: balance;
}
/* Page: Profile */
div.profile-wrapper {
display: block;
}
div.profile-container {
min-width: 0px;
margin: 32px 0 0 0;
}
}
@@ -0,0 +1,73 @@
//- redo with connection to profiles database
extends _Template.pug
block pageStyles
link(rel='stylesheet' href=`/assets/styles/ui-public-profile.css?v=${cachev}`)
block pageScripts
script(src=`/assets/scripts/public-profile.js?v=${cachev}`)
block pageHeader
meta(property='og:image' content='https://suzzygames.com/assets/images/og-logo.png')
meta(property='og:title' content=`profile - ${username}`)
meta(property='og:description' content=`View ${username}'s profile on suzzy games`)
meta(name='description' content=`View ${username}'s profile on suzzy games`)
title profile - #{username}
block pageContent
noscript
style.
noscript {
display: grid;
box-sizing: border-box;
place-items: center;
align-content: center;
}
#alert-load {
display: none
}
div(class='collection-message')
img(aria-hidden='true' src='/assets/images/icons/icon-cross.svg' alt='Icon for Cross')
p This page requires JavaScript to be enabled.
div(class='collection-message' id='alert-load')
img(src='/assets/images/icons/diagram-loading.svg' alt='Icon for Spinner')
p Loading Profile
div(class='collection-message' id='alert-error' hidden)
img(src='/assets/images/icons/icon-cross.svg' alt='Icon for Cross')
p(id='alert-error-message') An Error has Occurred
div(class='main-wrapper')
//- Profile
div(class='main-aside' hidden)
div(class='profile-container')
div(id='profile-banner')
div(class='profile-header')
img(id='profile-avatar' alt='My Avatar')
div(id='profile-badges')
div(class='profile-about text-nopad')
div(class='info-alongside')
p(id='profile-displayname')
p(id='profile-username')
p(id='profile-subtitle' class='text-tip')
div(class='divider-line' id='profile-divider' hidden)
pre(id='profile-bio' class='text-tip')
//- Categories
div(class='divider-spacer')
p(class='text-header') Categories
button(class='category category-active')
img(alt='Icon for Blog Post' src='/assets/blog/category-any.png')
p(class='category-name') None
p(class='category-count text-muted') (x)
//- button(class='category' id='category-posts')
//- img(alt='Icon for Blog Post' src='/assets/blog/category-news.png')
//- p(class='category-name') Blog Posts
//- p(class='category-count text-muted') (1)
//- button(class='category' id='category-friends')
//- img(alt='Icon for Blog Post' src='/assets/blog/category-friends.png')
//- p(class='category-name') Friends
//- p(class='category-count text-muted') (0)
div(class='divider-dashed' hidden)
//- Content
div(class='main-content' role='main' hidden)
div(class='collection-message')
img(src='/assets/images/icons/icon-filter.svg' alt='Icon for Filter')
p(class='text-muted') Please select a category
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 300 256" overflow="visible" xml:space="preserve">
<path fill="#181818" d="M295.5,195.1l-1.3-1.3c-2.4-2.3-5.3-3.7-8.4-4.1c7.9-9.8,11.9-22.2,11.9-36.5s-3.8-26.3-11.1-35.4
c-5.7-7-12.8-12.6-20.9-16.3h12.4c3.9,0,7.7-1.6,10.5-4.4c2.8-2.8,4.3-6.7,4.3-10.6c0-18.9-7.4-34.8-21.3-45.8
c-13-10.3-29.4-15.5-48.9-15.5c-11.7-0.1-23.2,2.1-34.1,6.7c0,0-0.1,0-0.1,0c-10.9,4.7-19.8,11.5-26.3,20.1
c-4.4,5.8-7.6,12.6-9.2,19.8c-1.1-19.4-8.4-36.7-21.8-50C110.4,1.1,78-5.7,50.6,5C20.8,16.7,2.4,45.8,0.1,84.7c0,0.2,0,0.4,0,0.6
c-0.8,33.6,10.4,66.1,32.3,94c19.5,24.9,46.8,45.4,78.7,59.1c26.8,11.5,55.7,17.6,83,17.6c5.1,0,10.1-0.2,15-0.6
c35.1-3,65.1-16.4,86.7-38.9c2.8-2.9,4.3-6.7,4.2-10.7C300,201.8,298.3,197.9,295.5,195.1L295.5,195.1z"/>
<path fill="#DCDCDC" d="M284,204.7c-46.5,30.5-110,24.6-143-28.2c-9.3-21.4-13.9-40.3-8.5-67.4C169.6,4.3,21.5-26,14.8,85.7
c-2.9,129.6,190.5,203,270.4,120.3L284,204.7L284,204.7z M36.3,69c-0.8-1-1.2-2.3-1.1-3.6c0-1.3,0.7-2.6,1.7-3.5
c1-0.8,2.3-1.2,3.6-1l21.4,1.9c1.3,0.1,2.5,0.7,3.3,1.7c0.8,1,1.2,2.4,1,3.7c-0.1,1.3-0.7,2.5-1.6,3.4c-1,0.8-2.3,1.2-3.6,1
l-21.4-1.9C38.4,70.6,37.2,70,36.3,69z M91.1,109.3c2.8,2.8,2.9,7.3,0.2,10.2c-2.7,2.9-7.2,3-10,0.2c-0.1-0.1-0.2-0.2-0.3-0.3
c-0.7-0.7-1.2-1.4-1.5-2.3c-0.2,0.1-0.4,0.2-0.6,0.3c-8.4,3.6-18-0.3-21.6-8.9c-3.6-8.5,0.3-18.4,8.7-22s18,0.3,21.6,8.9
c1.6,3.8,1.7,8.2,0.4,12.1C89.2,107.9,90.2,108.5,91.1,109.3L91.1,109.3L91.1,109.3z M87.5,74c-1.7-2.2-1.4-5.3,0.6-7.1
c1-0.8,2.3-1.2,3.6-1l21.4,1.9c1.3,0.1,2.5,0.7,3.3,1.7c0.8,1,1.2,2.4,1,3.7c-0.1,2.6-2.2,4.6-4.8,4.5c-0.1,0-0.3,0-0.4,0l-21.4-1.9
C89.6,75.6,88.4,75,87.5,74L87.5,74L87.5,74z"/>
<path fill="#DCDCDC" d="M194,193.9c-8.6-3.5-16.1-9.3-21.7-16.8c-5.5-7.4-8.2-16.2-8.2-26.2h28.5c0,6.6,3,11.8,8.9,15.8
s13.5,5.9,22.6,5.9c9.3,0,16.5-1.9,21.5-5.6s7.6-8.6,7.6-14.8c0-6.8-2.8-11.6-8.5-14.5c-5.7-2.9-14.5-5.7-26.4-8.3
c-10.9-2.5-19.8-5-26.7-7.6c-6.9-2.6-13.1-7.1-17.7-13c-5-6.1-7.4-14.3-7.4-24.9c-0.1-8.2,2.5-16.1,7.4-22.6
c4.9-6.6,11.7-11.7,20.3-15.4c9-3.8,18.6-5.7,28.4-5.6c16.2,0,29.5,4.1,39.9,12.3c10.4,8.2,15.6,19.6,15.6,34h-28.5
c0-5.8-2.7-10.4-8-14.1c-5.4-3.6-11.9-5.4-19.6-5.4c-7.5,0-13.6,1.8-18.5,5.3s-7.3,8-7.3,13.6s2.6,9.9,7.9,12.5
c5.3,2.6,13.6,5.2,24.9,7.9c9.4,2,18.6,4.6,27.6,7.7c7.3,2.7,13.7,7.3,18.7,13.4c5.2,6.3,7.8,14.9,7.8,25.8c0,15-5.5,26.5-16.5,34.4
s-25.3,11.9-42.9,11.9C213.4,199.8,203.4,197.8,194,193.9L194,193.9z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

@@ -0,0 +1,59 @@
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('file://fonts/pxiEyp8kv8JHgFVrJJbecmNE.woff2') format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('file://fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2') format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('file://fonts/pxiEyp8kv8JHgFVrJJfecg.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* devanagari */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('file://fonts/pxiByp8kv8JHgFVrLEj6Z11lFc-K.woff2') format('woff2');
unicode-range: U+0900-097F, U+1CD0-1CF9, U+200C-200D, U+20A8, U+20B9, U+20F0, U+25CC, U+A830-A839, U+A8E0-A8FF, U+11B00-11B09;
}
/* latin-ext */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('file://fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2') format('woff2');
unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url('file://fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 44" fill="#f7931e"><path d="M40 36H0v4a4 4 0 0 0 4 4h32a4 4 0 0 0 4-4v-4zM30 16.25L20 .5 10.16 16 0 0v32h20.32H40V0L30 16.25z"/></svg>

After

Width:  |  Height:  |  Size: 190 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 176 176"><path d="M160,80h-64V16c0-8.84-7.16-16-16-16H16C7.16,0,0,7.16,0,16v64c0,8.84,7.16,16,16,16h64v64c0,8.84,7.16,16,16,16h64c8.84,0,16-7.16,16-16v-64c0-8.84-7.16-16-16-16ZM16,80V16h64v64H16ZM160,160h-64v-64h64v64Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M51.3,136.49l18.69-18.69c3.12-3.12,3.12-8.19,0-11.31-3.12-3.12-8.19-3.12-11.31,0l-18.69,18.69-18.33-18.33c-3.12-3.12-8.19-3.12-11.31,0-3.12,3.12-3.12,8.19,0,11.31l18.33,18.33-18,18c-3.12,3.12-3.12,8.19,0,11.31,1.56,1.56,3.61,2.34,5.66,2.34s4.09-.78,5.66-2.34l18-18,18.36,18.36c1.56,1.56,3.61,2.34,5.66,2.34s4.09-.78,5.66-2.34c3.12-3.12,3.12-8.19,0-11.31l-18.36-18.36Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M147.47,40.3l18.69-18.69c3.12-3.12,3.12-8.19,0-11.31-3.12-3.12-8.19-3.12-11.31,0l-18.69,18.69-18.33-18.33c-3.12-3.12-8.19-3.12-11.31,0-3.12,3.12-3.12,8.19,0,11.31l18.33,18.33-18,18c-3.12,3.12-3.12,8.19,0,11.31,1.56,1.56,3.61,2.34,5.66,2.34s4.09-.78,5.66-2.34l18-18,18.36,18.36c1.56,1.56,3.61,2.34,5.66,2.34s4.09-.78,5.66-2.34c3.12-3.12,3.12-8.19,0-11.31l-18.36-18.36Z" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 99.11 94.5" fill="#f7931e"><path d="M99.01,35.85c-.24-.72-.86-1.25-1.62-1.36l-31.83-4.57L51.29,1.11c-.34-.68-1.03-1.11-1.79-1.11h0c-.76,0-1.46.43-1.79,1.12l-14.18,28.85L1.71,34.65c-.75.11-1.38.64-1.61,1.36-.23.72-.04,1.52.51,2.05l23.06,22.41-5.38,31.7c-.13.75.18,1.51.8,1.96.35.25.76.38,1.17.38.32,0,.64-.08.93-.23l28.43-15.01,28.48,14.92c.67.35,1.49.29,2.11-.16.62-.45.92-1.21.79-1.96l-5.49-31.68,22.99-22.48c.54-.53.74-1.33.5-2.05Z"/></svg>

After

Width:  |  Height:  |  Size: 495 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 219 32.12"><style>path {fill:#a1a8ff;}</style><defs><linearGradient id="b" gradientUnits="userSpaceOnUse"><stop offset=".75" stop-color="#a1a8ff" /><stop offset="1" stop-color="#a1a8ff" stop-opacity="0" /></linearGradient><linearGradient id="c" gradientTransform="translate(78 32) rotate(-180)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a1a8ff" stop-opacity="0" /><stop offset=".25" stop-color="#a1a8ff" /></linearGradient></defs><path d="M91.99,32.12c3.91,0,7.15-2.8,7.85-6.5h10.05c1.47,0,2.67-1.34,2.67-3,0-1.66-1.19-3-2.67-3h-9.9v-7.5h9.9c1.47,0,2.67-1.34,2.67-3s-1.19-3-2.67-3h-10.17c-.89-3.45-4.01-6-7.74-6-7.26,0-13.29,5.21-14.59,12.09-.27-.06-.27,7.88,0,7.82,1.3,6.88,7.33,12.09,14.59,12.09Z" /><path d="M141.44,12.09c-1.3-6.88-7.33-12.09-14.59-12.09-3.91,0-7.15,2.8-7.85,6.5l.12,19.5c.89,3.45,4.01,6,7.74,6,7.26,0,13.29-5.21,14.59-12.09.27.06.27-7.88,0-7.82Z"/><rect x="141" y="12" width="78" height="8" style="fill:url(#b);" /><rect y="12" width="78" height="8" transform="translate(78 32) rotate(180)" style="fill:url(#c);" /></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60">
<!-- why is it called a throbber? -->
<style>
rect {fill: #333550; animation: a 800ms infinite}
rect:nth-child(1) {animation-delay: 0ms}
rect:nth-child(2) {animation-delay: 100ms}
rect:nth-child(3) {animation-delay: 200ms}
rect:nth-child(4) {animation-delay: 300ms}
rect:nth-child(5) {animation-delay: 400ms}
rect:nth-child(6) {animation-delay: 500ms}
rect:nth-child(7) {animation-delay: 600ms}
rect:nth-child(8) {animation-delay: 700ms}
@keyframes a {
0%, 100% {fill: #333550}
50% {fill: #a1a8ff}
}
</style>
<g>
<rect x="1" y="1" width="18" height="18"/>
<rect x="21" y="1" width="18" height="18"/>
<rect x="41" y="1" width="18" height="18"/>
<rect x="41" y="21" width="18" height="18"/>
<rect x="41" y="41" width="18" height="18"/>
<rect x="21" y="41" width="18" height="18"/>
<rect x="1" y="41" width="18" height="18"/>
<rect x="1" y="21" width="18" height="18"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 85"><circle cx="24" cy="28" r="8" style="fill:#a1a8ff; stroke-width:0px;"/><circle cx="56" cy="28" r="8" style="fill:#a1a8ff; stroke-width:0px;"/><path d="M54,0h-28C11.66,0,0,11.66,0,26v47c0,6.62,5.38,12,12,12h56c6.62,0,12-5.38,12-12V26c0-14.34-11.66-26-26-26ZM72,73c0,2.21-1.79,4-4,4H12c-2.21,0-4-1.79-4-4v-17h64v17ZM72,48H8v-22c0-9.94,8.06-18,18-18h28c9.94,0,18,8.06,18,18v22Z" style="fill:#a1a8ff; stroke-width:0px;"/><path d="M24,28s-.01,0-.01,0h.01Z" style="fill:#000; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 601 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 85"><path d="M56,0H24C10.75,0,0,10.75,0,24v32h80V24c0-13.25-10.75-24-24-24ZM24,36c-4.42,0-8-3.58-8-8s3.58-8,8-8,8,3.58,8,8-3.58,8-8,8ZM56,36c-4.42,0-8-3.58-8-8s3.58-8,8-8,8,3.58,8,8-3.58,8-8,8Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M24,28s-.01,0-.01,0h.01Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M0,61h80v8c0,8.83-7.17,16-16,16H16c-8.83,0-16-7.17-16-16v-8h0Z" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 533 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 77.12"><path d="M92 0H8a8 8 0 0 0-8 8v40.25a8 8 0 0 0 8 8h37v10.88H34c-2.76 0-5 2.24-5 5s2.24 5 5 5h32c2.76 0 5-2.24 5-5s-2.24-5-5-5H55V56.25h37a8 8 0 0 0 8-8V8a8 8 0 0 0-8-8zm0 44.12a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4v-32a4 4 0 0 1 4-4h76a4 4 0 0 1 4 4v32z" fill="#f0f0f0"/></svg>

After

Width:  |  Height:  |  Size: 334 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.97 20.97"><path d="M13.31 10.49l7.07-7.07c.78-.78.78-2.05 0-2.83s-2.05-.78-2.83 0l-7.07 7.07L3.41.59C2.63-.2 1.37-.2.59.59c-.79.78-.79 2.04 0 2.82l7.07 7.07-7.07 7.08c-.78.78-.78 2.05 0 2.83.39.39.9.59 1.41.59s1.02-.2 1.41-.59l7.07-7.07 7.07 7.07c.39.39.9.59 1.41.59s1.02-.2 1.41-.59c.78-.78.78-2.05 0-2.83l-7.07-7.07z" fill="#a1a8ff"/></svg>

After

Width:  |  Height:  |  Size: 398 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.97 20.97"><path d="M13.31 10.49l7.07-7.07c.78-.78.78-2.05 0-2.83s-2.05-.78-2.83 0l-7.07 7.07L3.41.59C2.63-.2 1.37-.2.59.59c-.79.78-.79 2.04 0 2.82l7.07 7.07-7.07 7.08c-.78.78-.78 2.05 0 2.83.39.39.9.59 1.41.59s1.02-.2 1.41-.59l7.07-7.07 7.07 7.07c.39.39.9.59 1.41.59s1.02-.2 1.41-.59c.78-.78.78-2.05 0-2.83l-7.07-7.07z" fill="#f0f0f0"/></svg>

After

Width:  |  Height:  |  Size: 398 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 77.12"><path d="M92 0H8a8 8 0 0 0-8 8v40.25a8 8 0 0 0 8 8h37v10.88H34c-2.76 0-5 2.24-5 5s2.24 5 5 5h32c2.76 0 5-2.24 5-5s-2.24-5-5-5H55V56.25h37a8 8 0 0 0 8-8V8a8 8 0 0 0-8-8zm0 44.12a4 4 0 0 1-4 4H12a4 4 0 0 1-4-4v-32a4 4 0 0 1 4-4h76a4 4 0 0 1 4 4v32z" fill="#a1a8ff"/></svg>

After

Width:  |  Height:  |  Size: 334 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 70"><rect y="54" width="64" height="16" rx="4" ry="4" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M46.77,25h-7.77V4c0-2.21-1.79-4-4-4h-8c-2.21,0-4,1.79-4,4v21h-7.77c-3.64,0-5.39,4.47-2.71,6.94l15.77,14.56c1.53,1.41,3.89,1.41,5.43,0l15.77-14.56c2.68-2.47.93-6.94-2.71-6.94Z" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 428 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.5 48" fill="#f0f0f0"><path d="M53.5 20H38v-9.5c-1.02 0-2.05.25-2.83.75L18.2 22.17c-1.56 1.01-1.56 2.64 0 3.64l16.97 10.92c.78.5 1.8.75 2.83.75v-9.5h15.5a4 4 0 1 0 0-8zM8 47.5C3.86 47.5.5 44.14.5 40V8C.5 3.86 3.86.5 8 .5h23.5v7H16A8.51 8.51 0 0 0 7.5 16v16a8.51 8.51 0 0 0 8.5 8.5h15.5v7H8z"/><path d="M31 1v6H16c-4.96 0-9 4.04-9 9v16c0 4.96 4.04 9 9 9h15v6H8a7.01 7.01 0 0 1-7-7V8a7.01 7.01 0 0 1 7-7h23m1-1H8a8 8 0 0 0-8 8v32a8 8 0 0 0 8 8h24v-8H16a8 8 0 0 1-8-8V16a8 8 0 0 1 8-8h16V0h0z"/></svg>

After

Width:  |  Height:  |  Size: 553 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 92 72"><path d="M92,6c0-3.31-2.68-6-6-6H6C3.57,0,1.39,1.46.46,3.7-.47,5.95.04,8.53,1.76,10.24l29.74,29.74v20.01c0,6.62,4.49,12,10,12h10c5.51,0,10-5.38,10-12v-21.01l28.67-28.67c1.13-1.09,1.83-2.62,1.83-4.31Z" style="fill:#a1a8ff; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 353 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 49"><path d="M13,15V5h14v10h6V3c0-1.66-1.34-3-3-3H10c-1.66,0-3,1.34-3,3v12h6Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M36,17H4c-2.21,0-4,1.79-4,4v24c0,2.21,1.79,4,4,4h32c2.21,0,4-1.79,4-4v-24c0-2.21-1.79-4-4-4ZM23,35.19v3.81c0,1.66-1.34,3-3,3s-3-1.34-3-3v-3.81c-1.79-1.04-3-2.97-3-5.19,0-3.31,2.69-6,6-6s6,2.69,6,6c0,2.22-1.21,4.15-3,5.19Z" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 500 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 60"><path d="M32,0H8C3.58,0,0,3.58,0,8v44c0,4.42,3.58,8,8,8h24c4.42,0,8-3.58,8-8V8c0-4.42-3.58-8-8-8ZM20,57c-2.76,0-5-2.24-5-5s2.24-5,5-5,5,2.24,5,5-2.24,5-5,5ZM32,44H8V8h24v36Z" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 327 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect x="44" width="20" height="64" rx="4" ry="4" style="fill:#f0f0f0; stroke-width:0px;"/><rect width="20" height="64" rx="4" ry="4" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 286 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 71 80.76"><path d="M69,36.91L6,.54C3.33-1,0,.93,0,4.01v72.75c0,3.08,3.33,5,6,3.46l63-36.37c2.67-1.54,2.67-5.39,0-6.93Z" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 265 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 71.04 71.4" fill="#a1a8ff"><path d="M34.72 59.32c2.76-2.76 3.08-7.04.96-10.15l7.11-7.11c1.04-1.04.94-2.84-.24-4.01s-2.97-1.28-4.01-.24l-7 7-5.3-5.3 7-7c1.04-1.04.94-2.84-.24-4.01s-2.97-1.28-4.01-.24l-7.19 7.19c-3.07-1.81-7.08-1.41-9.71 1.23-5.13 5.13-5.72 13.08-1.77 18.87-.23.15-.45.32-.66.53l-8.43 8.43C0 65.74-.4 67.64.41 69.17c1.34 2.56 4.57 2.91 6.42 1.05l8.49-8.49c.2-.2.38-.43.53-.66 5.78 3.95 13.73 3.37 18.87-1.77zM69.87 6.83c1.56-1.56 1.56-4.09 0-5.66-1.56-1.56-4.09-1.56-5.66 0l-8.49 8.49c-.2.2-.38.43-.53.66-5.78-3.95-13.73-3.37-18.87 1.77-2.76 2.76-3.08 7.04-.96 10.15l13.87 13.71c3.07 1.81 7.08 1.41 9.71-1.23 5.13-5.13 5.72-13.08 1.77-18.87.23-.15.45-.32.66-.53l8.49-8.49z"/></svg>

After

Width:  |  Height:  |  Size: 749 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 52"><path d="M32 20.57a7.99 7.99 0 0 0-6.5-7.85V2.67c0-1.47-1.34-2.67-3-2.67s-3 1.19-3 2.67v9.9H12v-9.9C12 1.2 10.66 0 9 0S6 1.19 6 2.67v10.17c-3.45.89-6 4.01-6 7.74 0 7.26 5.21 13.29 12.09 14.59a3.86 3.86 0 0 0-.09.84v12a4 4 0 1 0 8 0v-12a3.86 3.86 0 0 0-.09-.84C26.79 33.87 32 27.84 32 20.58z" fill="#f0f0f0"/></svg>

After

Width:  |  Height:  |  Size: 374 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 99.11 94.5" fill="#f0f0f0"><path d="M99.01,35.85c-.24-.72-.86-1.25-1.62-1.36l-31.83-4.57L51.29,1.11c-.34-.68-1.03-1.11-1.79-1.11h0c-.76,0-1.46.43-1.79,1.12l-14.18,28.85L1.71,34.65c-.75.11-1.38.64-1.61,1.36-.23.72-.04,1.52.51,2.05l23.06,22.41-5.38,31.7c-.13.75.18,1.51.8,1.96.35.25.76.38,1.17.38.32,0,.64-.08.93-.23l28.43-15.01,28.48,14.92c.67.35,1.49.29,2.11-.16.62-.45.92-1.21.79-1.96l-5.49-31.68,22.99-22.48c.54-.53.74-1.33.5-2.05Z"/></svg>

After

Width:  |  Height:  |  Size: 495 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 54"><path d="M49,54H1c-.55,0-1-.45-1-1,0-12.12,5.01-23.24,13.08-29.02.42-.3,1-.23,1.34.16,2.67,3.09,6.53,4.86,10.58,4.86s7.91-1.77,10.58-4.86c.34-.39.92-.46,1.34-.16,8.07,5.78,13.08,16.9,13.08,29.02,0,.55-.45,1-1,1Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M25,26c-7.17,0-13-5.83-13-13S17.83,0,25,0s13,5.83,13,13-5.83,13-13,13Z" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 487 B

@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 112.28 92"><path d="M66.78,33.24v25c8.84,0,16-5.6,16-12.5s-7.16-12.5-16-12.5h0Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M57.99,92c3.31,0,6-2.68,6-6V6c0-2.43-1.46-4.61-3.7-5.54-2.25-.93-4.83-.42-6.54,1.3l-29.74,29.74h-12.01c-6.62,0-12,4.49-12,10v10c0,5.51,5.38,10,12,10h13.01l28.67,28.67c1.09,1.13,2.62,1.83,4.31,1.83Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M70.78,71.97c-2.21,0-4-1.69-4-3.76s1.79-3.76,4-3.76c11.58,0,21-8.38,21-18.69s-9.42-18.69-21-18.69c-2.21,0-4-1.69-4-3.76s1.79-3.76,4-3.76c15.99,0,29,11.76,29,26.22s-13.01,26.22-29,26.22Z" style="fill:#f0f0f0; stroke-width:0px;"/><path d="M70.78,86.24c-2.21,0-4-1.69-4-3.76s1.79-3.76,4-3.76c18.47,0,33.5-14.57,33.5-32.47s-15.03-32.47-33.5-32.47c-2.21,0-4-1.69-4-3.76s1.79-3.76,4-3.76c22.88,0,41.5,17.94,41.5,40s-18.62,40-41.5,40Z" style="fill:#f0f0f0; stroke-width:0px;"/></svg>

After

Width:  |  Height:  |  Size: 954 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Some files were not shown because too many files have changed in this diff Show More