Update prettier config + Run pnpm format

This commit is contained in:
venashial
2022-05-26 19:17:15 -07:00
parent 89571d57bd
commit 8d4da009af
62 changed files with 8612 additions and 8618 deletions

View File

@@ -1,8 +1,8 @@
import type { SvelteComponentDev } from 'svelte/internal';
import type { SvelteComponentDev } from 'svelte/internal'
export interface Option {
label: string;
/** The element that will be in the `value` array while the option is checked */
value: string | number;
icon?: SvelteComponentDev | string;
label: string
/** The element that will be in the `value` array while the option is checked */
value: string | number
icon?: SvelteComponentDev | string
}

View File

@@ -1,33 +1,33 @@
const config = {
plugins: [
require('postcss-import-ext-glob'),
require('postcss-import'),
require('postcss-strip-inline-comments'),
require('postcss-extend-rule'),
require('postcss-nested'),
require('postcss-preset-env')({
features: {
'custom-media-queries': {
importFrom: [
{
customMedia: {
'--sm': '(min-width: 544px)',
'--md': '(min-width: 768px)',
'--lg': '(min-width: 1012px)',
'--xl': '(min-width: 1280px)',
},
},
],
},
},
}),
require('postcss-pxtorem'),
require('autoprefixer'),
process.env.NODE_ENV === 'development' &&
require('cssnano')({
preset: 'default',
}),
],
};
plugins: [
require('postcss-import-ext-glob'),
require('postcss-import'),
require('postcss-strip-inline-comments'),
require('postcss-extend-rule'),
require('postcss-nested'),
require('postcss-preset-env')({
features: {
'custom-media-queries': {
importFrom: [
{
customMedia: {
'--sm': '(min-width: 544px)',
'--md': '(min-width: 768px)',
'--lg': '(min-width: 1012px)',
'--xl': '(min-width: 1280px)',
},
},
],
},
},
}),
require('postcss-pxtorem'),
require('autoprefixer'),
process.env.NODE_ENV === 'development' &&
require('cssnano')({
preset: 'default',
}),
],
}
module.exports = config;
module.exports = config

View File

@@ -1,17 +1,17 @@
import sveltePreprocess from 'svelte-preprocess';
import Icons from 'unplugin-icons/vite';
import svelteSvg from '@poppanator/sveltekit-svg';
import sveltePreprocess from 'svelte-preprocess'
import Icons from 'unplugin-icons/vite'
import svelteSvg from '@poppanator/sveltekit-svg'
export const preprocess = sveltePreprocess({
postcss: true,
preserve: ['ld+json'],
});
postcss: true,
preserve: ['ld+json'],
})
export const plugins = [
svelteSvg(),
Icons({
compiler: 'svelte',
defaultClass: 'icon',
scale: 1,
}),
];
svelteSvg(),
Icons({
compiler: 'svelte',
defaultClass: 'icon',
scale: 1,
}),
]

View File

@@ -1,25 +1,25 @@
/* COMPONENTS */
export { default as Avatar } from './components/Avatar.svelte';
export { default as Avatar } from './components/Avatar.svelte'
export { default as Badge } from './components/Badge.svelte';
export { default as Badge } from './components/Badge.svelte'
export { default as Button } from './components/Button.svelte';
export { default as Button } from './components/Button.svelte'
export { default as Checkbox } from './components/Checkbox.svelte';
export { default as CheckboxList } from './components/CheckboxList.svelte';
export { default as CheckboxVirtualList } from './components/CheckboxVirtualList.svelte';
export { default as Checkbox } from './components/Checkbox.svelte'
export { default as CheckboxList } from './components/CheckboxList.svelte'
export { default as CheckboxVirtualList } from './components/CheckboxVirtualList.svelte'
export { default as Chips } from './components/Chips.svelte';
export { default as Chips } from './components/Chips.svelte'
export { default as FormField } from './components/FormField.svelte';
export { default as FormField } from './components/FormField.svelte'
export { default as NavRow } from './components/NavRow.svelte';
export { default as NavRow } from './components/NavRow.svelte'
export { default as Pagination } from './components/Pagination.svelte';
export { default as Pagination } from './components/Pagination.svelte'
export { default as Select } from './components/Select.svelte';
export { default as Select } from './components/Select.svelte'
export { default as Slider } from './components/Slider.svelte';
export { default as Slider } from './components/Slider.svelte'
export { default as TextInput } from './components/TextInput.svelte';
export { default as TextInput } from './components/TextInput.svelte'

View File

@@ -1,10 +1,10 @@
export default function Generator(options: PluginOptions): {
name: string;
buildStart(): Promise<void>;
};
export interface PluginOptions {
projectColors: boolean;
landingPage: boolean;
gameVersions: boolean;
tags: boolean;
name: string
buildStart(): Promise<void>
}
export interface PluginOptions {
projectColors: boolean
landingPage: boolean
gameVersions: boolean
tags: boolean
}

View File

@@ -1,58 +1,58 @@
import { promises as fs } from 'fs';
import { landingPage } from './outputs/landingPage.js';
import { projectColors } from './outputs/projectColors.js';
import { gameVersions } from './outputs/gameVersions.js';
import { tags } from './outputs/tags.js';
import { promises as fs } from 'fs'
import { landingPage } from './outputs/landingPage.js'
import { projectColors } from './outputs/projectColors.js'
import { gameVersions } from './outputs/gameVersions.js'
import { tags } from './outputs/tags.js'
const API_URL =
process.env.VITE_API_URL && process.env.VITE_API_URL === 'https://staging-api.modrinth.com/v2/'
? 'https://staging-api.modrinth.com/v2/'
: 'https://api.modrinth.com/v2/';
process.env.VITE_API_URL && process.env.VITE_API_URL === 'https://staging-api.modrinth.com/v2/'
? 'https://staging-api.modrinth.com/v2/'
: 'https://api.modrinth.com/v2/'
// Time to live: 7 days
const TTL = 7 * 24 * 60 * 60 * 1000;
const TTL = 7 * 24 * 60 * 60 * 1000
export default function Generator(options) {
return {
name: 'rollup-plugin-omorphia-generator',
async buildStart() {
let state = {};
try {
state = JSON.parse(await fs.readFile('./generated/state.json', 'utf8'));
} catch {
// File doesn't exist, create folder
await fs.mkdir('./generated', { recursive: true });
await fs.writeFile(
'./generated/state.json',
JSON.stringify(
{
options,
},
null,
2
)
);
}
return {
name: 'rollup-plugin-omorphia-generator',
async buildStart() {
let state = {}
try {
state = JSON.parse(await fs.readFile('./generated/state.json', 'utf8'))
} catch {
// File doesn't exist, create folder
await fs.mkdir('./generated', { recursive: true })
await fs.writeFile(
'./generated/state.json',
JSON.stringify(
{
options,
},
null,
2
)
)
}
// Don't generate if the last generation was less than TTL and the options are the same
if (
state?.lastGenerated &&
new Date(state.lastGenerated).getTime() + TTL > new Date().getTime() &&
JSON.stringify(state.options) === JSON.stringify(options)
) {
return;
}
// Don't generate if the last generation was less than TTL and the options are the same
if (
state?.lastGenerated &&
new Date(state.lastGenerated).getTime() + TTL > new Date().getTime() &&
JSON.stringify(state.options) === JSON.stringify(options)
) {
return
}
if (options.tags) await tags(API_URL);
if (options.landingPage) await landingPage(API_URL);
if (options.gameVersions) await gameVersions(API_URL);
if (options.projectColors) await projectColors(API_URL);
if (options.tags) await tags(API_URL)
if (options.landingPage) await landingPage(API_URL)
if (options.gameVersions) await gameVersions(API_URL)
if (options.projectColors) await projectColors(API_URL)
// Write new state
state.lastGenerated = new Date().toISOString();
state.options = options;
// Write new state
state.lastGenerated = new Date().toISOString()
state.options = options
await fs.writeFile('./generated/state.json', JSON.stringify(state, null, 2));
},
};
await fs.writeFile('./generated/state.json', JSON.stringify(state, null, 2))
},
}
}

View File

@@ -1 +1 @@
export declare function gameVersions(API_URL: string): Promise<void>;
export declare function gameVersions(API_URL: string): Promise<void>

View File

@@ -1,22 +1,22 @@
import { fetch } from 'undici';
import { promises as fs } from 'fs';
import cliProgress from 'cli-progress';
import { fetch } from 'undici'
import { promises as fs } from 'fs'
import cliProgress from 'cli-progress'
export async function gameVersions(API_URL) {
const progressBar = new cliProgress.SingleBar({
format: 'Generating game versions | {bar} | {percentage}%',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
});
progressBar.start(2, 0);
const progressBar = new cliProgress.SingleBar({
format: 'Generating game versions | {bar} | {percentage}%',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
})
progressBar.start(2, 0)
const gameVersions = await (await fetch(API_URL + 'tag/game_version')).json();
progressBar.increment();
const gameVersions = await (await fetch(API_URL + 'tag/game_version')).json()
progressBar.increment()
// Write JSON file
await fs.writeFile('./generated/gameVersions.json', JSON.stringify(gameVersions));
progressBar.increment();
// Write JSON file
await fs.writeFile('./generated/gameVersions.json', JSON.stringify(gameVersions))
progressBar.increment()
progressBar.stop();
progressBar.stop()
}

View File

@@ -1 +1 @@
export declare function landingPage(API_URL: string): Promise<void>;
export declare function landingPage(API_URL: string): Promise<void>

View File

@@ -1,40 +1,40 @@
import { fetch } from 'undici';
import { promises as fs } from 'fs';
import cliProgress from 'cli-progress';
import { fetch } from 'undici'
import { promises as fs } from 'fs'
import cliProgress from 'cli-progress'
export async function landingPage(API_URL) {
const progressBar = new cliProgress.SingleBar({
format: 'Generating landing page | {bar} | {percentage}% || {value}/{total} mods',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
});
progressBar.start(100, 0);
const progressBar = new cliProgress.SingleBar({
format: 'Generating landing page | {bar} | {percentage}% || {value}/{total} mods',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
})
progressBar.start(100, 0)
// Fetch top 100 mods
const response = await (
await fetch(API_URL + 'search?limit=100&facets=[["project_type:mod"]]')
).json();
// Fetch top 100 mods
const response = await (
await fetch(API_URL + 'search?limit=100&facets=[["project_type:mod"]]')
).json()
// Simplified array with the format: ['id', 'slug', 'icon_extension']
const compressed = response.hits
.filter((project) => project.icon_url)
.map((project) => {
progressBar.increment();
return [
project.project_id,
project.slug || '',
project.icon_url.match(/\.[0-9a-z]+$/i)[0].substring(1),
];
});
// Simplified array with the format: ['id', 'slug', 'icon_extension']
const compressed = response.hits
.filter((project) => project.icon_url)
.map((project) => {
progressBar.increment()
return [
project.project_id,
project.slug || '',
project.icon_url.match(/\.[0-9a-z]+$/i)[0].substring(1),
]
})
// Write JSON file
await fs.writeFile(
'./generated/landingPage.json',
JSON.stringify({
mods: compressed,
random: Math.random(),
})
);
progressBar.stop();
// Write JSON file
await fs.writeFile(
'./generated/landingPage.json',
JSON.stringify({
mods: compressed,
random: Math.random(),
})
)
progressBar.stop()
}

View File

@@ -1 +1 @@
export declare function projectColors(API_URL: string): Promise<void>;
export declare function projectColors(API_URL: string): Promise<void>

View File

@@ -1,86 +1,86 @@
import { fetch } from 'undici';
import { promises as fs, createWriteStream } from 'fs';
import cliProgress from 'cli-progress';
import Jimp from 'jimp';
import { getAverageColor } from 'fast-average-color-node';
import { fetch } from 'undici'
import { promises as fs, createWriteStream } from 'fs'
import cliProgress from 'cli-progress'
import Jimp from 'jimp'
import { getAverageColor } from 'fast-average-color-node'
// Note: This function has issues and will occasionally fail with some project icons. It averages at a 99.4% success rate. Most issues are from ECONNRESET errors & Jimp not being able to handle webp & svg images.
export async function projectColors(API_URL) {
const progressBar = new cliProgress.SingleBar({
format: 'Generating project colors | {bar} | {percentage}% || {value}/{total} projects',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
});
// Get total number of projects
const projectCount = (await (await fetch(API_URL + 'search?limit=0')).json()).total_hits;
progressBar.start(projectCount, 0);
const writeStream = createWriteStream('./generated/projects.json');
writeStream.write('{');
// Used to form the JSON string (so that the first doesn't have a comma prefix)
let first = true;
let completed = 0;
// Number of pages through search to fetch
const requestCount = Math.ceil(projectCount / 100);
await Promise.allSettled(
Array.from({ length: requestCount }, async (_, index) => {
const response = await fetch(API_URL + `search?limit=100&offset=${index * 100}`);
if (!response.ok) {
throw new Error(`Failed to fetch projects: ${response.statusText}`);
}
// Get project hits & use map to get rid of extra data
const hits = (await response.json()).hits.map((project) => ({
project_id: project.project_id,
slug: project.slug,
title: project.title,
icon_url: project.icon_url,
}));
// Try parsing the icon of each project
await Promise.allSettled(
hits.map(async (project) => {
if (
project.icon_url &&
// Jimp doesn't support webp or svg
!project.icon_url.endsWith('.webp') &&
!project.icon_url.endsWith('.svg')
) {
try {
const image = await Jimp.read(
project.icon_url.replace('cdn', 'cdn-raw') // Skip redirect to raw CDN
);
// Resize image before getting average color (faster)
image.resize(256, 256);
// Get bottom edge of image
const edge = image.clone().crop(0, 255, 256, 1);
const buffer = await edge.getBufferAsync(Jimp.AUTO);
let color = (await getAverageColor(buffer)).hexa;
// If the edge is transparent, use the average color of the entire image
if (color === '#00000000') {
const buffer = await image.getBufferAsync(Jimp.AUTO);
color = (await getAverageColor(buffer)).hexa;
}
// Remove color transparency
color = color.replace(/.{2}$/, '');
// Only use comma prefix if not first
let prefix = ',';
if (first) {
prefix = '';
first = false;
}
writeStream.write(`${prefix}"${project.project_id}":"${color}"`);
completed++;
} catch (error) {
// Ignore errors
// console.log(error);
}
}
progressBar.increment();
})
);
})
);
writeStream.write('}');
writeStream.end();
progressBar.stop();
console.log(`Failed to parse ${projectCount - completed} project icons.`);
const progressBar = new cliProgress.SingleBar({
format: 'Generating project colors | {bar} | {percentage}% || {value}/{total} projects',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
})
// Get total number of projects
const projectCount = (await (await fetch(API_URL + 'search?limit=0')).json()).total_hits
progressBar.start(projectCount, 0)
const writeStream = createWriteStream('./generated/projects.json')
writeStream.write('{')
// Used to form the JSON string (so that the first doesn't have a comma prefix)
let first = true
let completed = 0
// Number of pages through search to fetch
const requestCount = Math.ceil(projectCount / 100)
await Promise.allSettled(
Array.from({ length: requestCount }, async (_, index) => {
const response = await fetch(API_URL + `search?limit=100&offset=${index * 100}`)
if (!response.ok) {
throw new Error(`Failed to fetch projects: ${response.statusText}`)
}
// Get project hits & use map to get rid of extra data
const hits = (await response.json()).hits.map((project) => ({
project_id: project.project_id,
slug: project.slug,
title: project.title,
icon_url: project.icon_url,
}))
// Try parsing the icon of each project
await Promise.allSettled(
hits.map(async (project) => {
if (
project.icon_url &&
// Jimp doesn't support webp or svg
!project.icon_url.endsWith('.webp') &&
!project.icon_url.endsWith('.svg')
) {
try {
const image = await Jimp.read(
project.icon_url.replace('cdn', 'cdn-raw') // Skip redirect to raw CDN
)
// Resize image before getting average color (faster)
image.resize(256, 256)
// Get bottom edge of image
const edge = image.clone().crop(0, 255, 256, 1)
const buffer = await edge.getBufferAsync(Jimp.AUTO)
let color = (await getAverageColor(buffer)).hexa
// If the edge is transparent, use the average color of the entire image
if (color === '#00000000') {
const buffer = await image.getBufferAsync(Jimp.AUTO)
color = (await getAverageColor(buffer)).hexa
}
// Remove color transparency
color = color.replace(/.{2}$/, '')
// Only use comma prefix if not first
let prefix = ','
if (first) {
prefix = ''
first = false
}
writeStream.write(`${prefix}"${project.project_id}":"${color}"`)
completed++
} catch (error) {
// Ignore errors
// console.log(error);
}
}
progressBar.increment()
})
)
})
)
writeStream.write('}')
writeStream.end()
progressBar.stop()
console.log(`Failed to parse ${projectCount - completed} project icons.`)
}

View File

@@ -1 +1 @@
export declare function tags(API_URL: string): Promise<void>;
export declare function tags(API_URL: string): Promise<void>

View File

@@ -1,50 +1,50 @@
import { fetch } from 'undici';
import { promises as fs } from 'fs';
import cliProgress from 'cli-progress';
import { fetch } from 'undici'
import { promises as fs } from 'fs'
import cliProgress from 'cli-progress'
export async function tags(API_URL) {
const progressBar = new cliProgress.SingleBar({
format: 'Generating tags | {bar} | {percentage}%',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
});
progressBar.start(7, 0);
const progressBar = new cliProgress.SingleBar({
format: 'Generating tags | {bar} | {percentage}%',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591',
hideCursor: true,
})
progressBar.start(7, 0)
// eslint-disable-next-line prefer-const
let [categories, loaders, licenses, donationPlatforms] = await Promise.all([
await (await fetch(API_URL + 'tag/category')).json(),
await (await fetch(API_URL + 'tag/loader')).json(),
await (await fetch(API_URL + 'tag/license')).json(),
await (await fetch(API_URL + 'tag/donation_platform')).json(),
]);
progressBar.update(4);
// eslint-disable-next-line prefer-const
let [categories, loaders, licenses, donationPlatforms] = await Promise.all([
await (await fetch(API_URL + 'tag/category')).json(),
await (await fetch(API_URL + 'tag/loader')).json(),
await (await fetch(API_URL + 'tag/license')).json(),
await (await fetch(API_URL + 'tag/donation_platform')).json(),
])
progressBar.update(4)
const tagIconReducer = (a, v) => ({
...a,
[v.name]: v.icon.replace('<svg', '<svg class="icon"'),
});
const tagIconReducer = (a, v) => ({
...a,
[v.name]: v.icon.replace('<svg', '<svg class="icon"'),
})
// Create single object with icons
const tagIcons = {
...categories.reduce(tagIconReducer, {}),
...loaders.reduce(tagIconReducer, {}),
};
progressBar.increment();
// Create single object with icons
const tagIcons = {
...categories.reduce(tagIconReducer, {}),
...loaders.reduce(tagIconReducer, {}),
}
progressBar.increment()
// Delete icons from original arrays
categories = categories.map(({ icon, ...rest }) => rest);
loaders = loaders.map(({ icon, ...rest }) => rest);
progressBar.increment();
// Delete icons from original arrays
categories = categories.map(({ icon, ...rest }) => rest)
loaders = loaders.map(({ icon, ...rest }) => rest)
progressBar.increment()
// Set project types
const projectTypes = ['mod', 'modpack'];
// Set project types
const projectTypes = ['mod', 'modpack']
// Write JSON file
await fs.writeFile(
'./generated/tags.json',
JSON.stringify({ categories, loaders, projectTypes, licenses, donationPlatforms, tagIcons })
);
progressBar.increment();
// Write JSON file
await fs.writeFile(
'./generated/tags.json',
JSON.stringify({ categories, loaders, projectTypes, licenses, donationPlatforms, tagIcons })
)
progressBar.increment()
progressBar.stop();
progressBar.stop()
}

View File

@@ -1,20 +1,20 @@
.actions {
display: flex;
flex-direction: column;
align-items: flex-end;
grid-gap: 0.5rem;
flex-wrap: wrap;
margin-left: auto;
min-width: fit-content;
display: flex;
flex-direction: column;
align-items: flex-end;
grid-gap: 0.5rem;
flex-wrap: wrap;
margin-left: auto;
min-width: fit-content;
> *:last-child {
margin-top: auto;
color: var(--color-text-light);
}
> *:last-child {
margin-top: auto;
color: var(--color-text-light);
}
@media (width <= 1000px) {
flex-direction: row;
align-items: flex-start;
margin-left: unset;
}
@media (width <= 1000px) {
flex-direction: row;
align-items: flex-start;
margin-left: unset;
}
}

View File

@@ -1,23 +1,23 @@
.base {
background-color: var(--color-bg);
color: var(--color-text);
font-family: var(--font-standard);
font-size: var(--font-size);
font-weight: var(--font-weight-regular);
background-color: var(--color-bg);
color: var(--color-text);
font-family: var(--font-standard);
font-size: var(--font-size);
font-weight: var(--font-weight-regular);
scrollbar-color: var(--color-scrollbar) var(--color-bg);
scrollbar-color: var(--color-scrollbar) var(--color-bg);
&::-webkit-scrollbar {
width: 14px;
}
&::-webkit-scrollbar {
width: 14px;
}
&::-webkit-scrollbar-track {
background-color: var(--color-bg);
}
&::-webkit-scrollbar-track {
background-color: var(--color-bg);
}
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar);
border-radius: 999px;
border: 3px solid var(--color-bg);
}
&::-webkit-scrollbar-thumb {
background-color: var(--color-scrollbar);
border-radius: 999px;
border: 3px solid var(--color-bg);
}
}

View File

@@ -1,5 +1,5 @@
.button-group {
display: flex;
grid-gap: 0.5rem;
flex-wrap: wrap;
display: flex;
grid-gap: 0.5rem;
flex-wrap: wrap;
}

View File

@@ -1,101 +1,101 @@
.card {
--padding: 1rem;
--padding: 1rem;
display: flex;
flex-direction: column;
position: relative;
display: flex;
flex-direction: column;
position: relative;
background-color: var(--color-raised-bg);
border-radius: var(--rounded-lg);
overflow: hidden;
box-shadow: var(--shadow-raised), var(--shadow-inset);
background-color: var(--color-raised-bg);
border-radius: var(--rounded-lg);
overflow: hidden;
box-shadow: var(--shadow-raised), var(--shadow-inset);
padding: var(--padding);
grid-gap: 1rem;
max-width: 100%;
padding: var(--padding);
grid-gap: 1rem;
max-width: 100%;
.profile-picture {
z-index: 1;
}
.profile-picture {
z-index: 1;
}
&--gap {
&-compressed {
grid-gap: 0.6rem;
}
&--gap {
&-compressed {
grid-gap: 0.6rem;
}
&-none {
grid-gap: 0;
}
}
&-none {
grid-gap: 0;
}
}
&--pad {
&-top {
padding-top: 2.5rem;
}
}
&--pad {
&-top {
padding-top: 2.5rem;
}
}
&--overflow-visible {
overflow: visible;
}
&--overflow-visible {
overflow: visible;
}
&__banner {
--inverse-padding: calc(var(--padding) * -1);
margin: var(--inverse-padding) var(--inverse-padding) 0 var(--inverse-padding);
z-index: 0;
background-color: var(--color-divider);
&__banner {
--inverse-padding: calc(var(--padding) * -1);
margin: var(--inverse-padding) var(--inverse-padding) 0 var(--inverse-padding);
z-index: 0;
background-color: var(--color-divider);
&:-moz-loading {
visibility: hidden;
}
&:-moz-loading {
visibility: hidden;
}
&--short {
height: 6.5rem;
object-fit: cover;
object-position: center;
}
&--short {
height: 6.5rem;
object-fit: cover;
object-position: center;
}
&--dark {
filter: brightness(0.7);
}
}
&--dark {
filter: brightness(0.7);
}
}
&__overlay {
position: absolute;
top: 1rem;
right: 1rem;
&__overlay {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
flex-direction: column;
align-items: flex-end;
grid-gap: 0.5rem;
display: flex;
flex-direction: column;
align-items: flex-end;
grid-gap: 0.5rem;
z-index: 1;
z-index: 1;
&--row {
flex-direction: row;
}
}
&--row {
flex-direction: row;
}
}
&--row {
flex-direction: row;
align-items: center;
}
&--row {
flex-direction: row;
align-items: center;
}
&--strip {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
&--strip {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
&--pad-x {
--padding: 1rem 1.3rem;
}
&--pad-x {
--padding: 1rem 1.3rem;
}
&.markdown {
--padding: 1.5rem;
}
&.markdown {
--padding: 1.5rem;
}
p {
line-height: 130%;
}
p {
line-height: 130%;
}
}

View File

@@ -1,6 +1,6 @@
.divider {
margin: 0.25rem 0;
border: none;
border-top: 1px solid var(--color-divider);
width: 100%;
margin: 0.25rem 0;
border: none;
border-top: 1px solid var(--color-divider);
width: 100%;
}

View File

@@ -1,16 +1,16 @@
.illustration {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
grid-gap: 2rem;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
grid-gap: 2rem;
&__image {
max-width: 300px;
}
&__image {
max-width: 300px;
}
&__description {
font-size: 1.2rem;
color: var(--color-text-light);
}
&__description {
font-size: 1.2rem;
color: var(--color-text-light);
}
}

View File

@@ -1,11 +1,11 @@
.info-table {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 0.25rem 2rem;
width: fit-content;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 0.25rem 2rem;
width: fit-content;
&__label {
color: var(--color-text-lightest);
font-weight: var(--font-weight-medium);
}
&__label {
color: var(--color-text-lightest);
font-weight: var(--font-weight-medium);
}
}

View File

@@ -1,25 +1,25 @@
.link {
color: var(--color-link);
display: flex;
align-items: center;
gap: 4px;
line-height: 100%;
color: var(--color-link);
display: flex;
align-items: center;
gap: 4px;
line-height: 100%;
&:hover {
text-decoration: underline;
}
&:hover {
text-decoration: underline;
}
}
.link-group {
display: grid;
grid-template-columns: repeat(3, auto);
grid-gap: 0.75rem;
display: grid;
grid-template-columns: repeat(3, auto);
grid-gap: 0.75rem;
.link {
color: var(--color-text);
.link {
color: var(--color-text);
&:hover {
color: var(--color-link);
}
}
&:hover {
color: var(--color-link);
}
}
}

View File

@@ -1,137 +1,137 @@
.markdown {
display: flex;
flex-direction: column;
grid-gap: 1rem;
display: flex;
flex-direction: column;
grid-gap: 1rem;
blockquote,
details,
dl,
ol,
p,
code,
pre,
table,
ul {
margin: 0;
}
blockquote,
details,
dl,
ol,
p,
code,
pre,
table,
ul {
margin: 0;
}
p {
line-height: 1.5;
word-wrap: break-word;
overflow-wrap: anywhere;
}
p {
line-height: 1.5;
word-wrap: break-word;
overflow-wrap: anywhere;
}
h1,
h2 {
padding-bottom: 0.2em;
border-bottom: 1px solid var(--color-divider);
}
h1,
h2 {
padding-bottom: 0.2em;
border-bottom: 1px solid var(--color-divider);
}
blockquote {
padding: 0 1rem;
color: var(--color-text);
border-left: 0.25rem solid var(--color-divider);
}
blockquote {
padding: 0 1rem;
color: var(--color-text);
border-left: 0.25rem solid var(--color-divider);
}
a {
color: var(--color-link);
a {
color: var(--color-link);
&:hover {
text-decoration: underline;
}
}
&:hover {
text-decoration: underline;
}
}
img,
iframe {
max-width: 100%;
height: auto;
border-radius: var(--rounded-sm);
}
img,
iframe {
max-width: 100%;
height: auto;
border-radius: var(--rounded-sm);
}
iframe {
width: 35rem;
aspect-ratio: 16/9;
}
iframe {
width: 35rem;
aspect-ratio: 16/9;
}
code {
padding: 0.2rem 0.4rem;
font-size: 80%;
border-radius: var(--rounded-sm);
background-color: var(--color-code-bg);
color: var(--color-code-text);
}
code {
padding: 0.2rem 0.4rem;
font-size: 80%;
border-radius: var(--rounded-sm);
background-color: var(--color-code-bg);
color: var(--color-code-text);
}
pre {
padding: 1rem;
border-radius: var(--rounded-sm);
overflow-x: auto;
pre {
padding: 1rem;
border-radius: var(--rounded-sm);
overflow-x: auto;
code {
font-size: 80%;
padding: 0;
border-radius: 0;
background-color: unset;
}
}
code {
font-size: 80%;
padding: 0;
border-radius: 0;
background-color: unset;
}
}
hr {
margin: 0;
color: var(--color-divider);
}
hr {
margin: 0;
color: var(--color-divider);
}
table {
display: block;
width: max-content;
max-width: 100%;
overflow: auto;
border-collapse: collapse;
line-height: 1.5;
table {
display: block;
width: max-content;
max-width: 100%;
overflow: auto;
border-collapse: collapse;
line-height: 1.5;
th {
font-weight: 600;
}
th {
font-weight: 600;
}
td,
th {
padding: 0.4rem 0.85rem;
border: 0.1rem solid var(--color-table-border);
}
td,
th {
padding: 0.4rem 0.85rem;
border: 0.1rem solid var(--color-table-border);
}
tr:nth-child(2n) {
background-color: var(--color-table-alternate-row);
}
}
tr:nth-child(2n) {
background-color: var(--color-table-alternate-row);
}
}
details {
border: 0.15rem solid var(--color-button-bg);
border-radius: var(--rounded-sm);
padding: 0.5rem 0.5rem 0;
overflow: hidden;
details {
border: 0.15rem solid var(--color-button-bg);
border-radius: var(--rounded-sm);
padding: 0.5rem 0.5rem 0;
overflow: hidden;
summary {
font-weight: bold;
margin: -0.5rem -0.5rem 0;
padding: 0.5rem 0.8rem;
cursor: pointer;
background-color: var(--color-button-bg);
summary {
font-weight: bold;
margin: -0.5rem -0.5rem 0;
padding: 0.5rem 0.8rem;
cursor: pointer;
background-color: var(--color-button-bg);
&:hover {
background-color: var(--color-button-bg-hover);
}
}
&:hover {
background-color: var(--color-button-bg-hover);
}
}
&[open] {
padding: 0.5rem;
&[open] {
padding: 0.5rem;
summary {
margin-bottom: 0.5rem;
}
}
}
summary {
margin-bottom: 0.5rem;
}
}
}
li:has(> input) {
list-style: none;
margin: 0;
padding: 0;
}
li:has(> input) {
list-style: none;
margin: 0;
padding: 0;
}
}

View File

@@ -1,13 +1,13 @@
.member {
display: flex;
grid-gap: 0.75rem;
display: flex;
grid-gap: 0.75rem;
&__info {
display: flex;
flex-direction: column;
&__info {
display: flex;
flex-direction: column;
&__link {
font-weight: var(--font-weight-medium);
}
}
&__link {
font-weight: var(--font-weight-medium);
}
}
}

View File

@@ -1,23 +1,23 @@
.stat {
display: flex;
align-items: baseline;
grid-gap: 0.4rem;
display: flex;
align-items: baseline;
grid-gap: 0.4rem;
&--light {
color: var(--color-text-lightest);
}
&--light {
color: var(--color-text-lightest);
}
.icon {
align-self: center;
}
.icon {
align-self: center;
}
strong {
font-size: 20px;
}
strong {
font-size: 20px;
}
}
.stat-group {
display: flex;
grid-gap: 0.5rem;
flex-wrap: wrap;
display: flex;
grid-gap: 0.5rem;
flex-wrap: wrap;
}

View File

@@ -1,18 +1,18 @@
.tag {
display: flex;
align-items: center;
grid-gap: 0.25rem;
color: var(--color-text-lightest);
display: flex;
align-items: center;
grid-gap: 0.25rem;
color: var(--color-text-lightest);
svg {
width: 1rem;
height: auto;
}
svg {
width: 1rem;
height: auto;
}
}
.tag-group {
display: inline-flex;
flex-wrap: wrap;
margin-top: auto;
grid-gap: 0.25rem 0.6rem;
display: inline-flex;
flex-wrap: wrap;
margin-top: auto;
grid-gap: 0.25rem 0.6rem;
}

View File

@@ -1,14 +1,14 @@
.title-primary {
font-size: 24px;
font-weight: var(--font-weight-bold);
font-size: 24px;
font-weight: var(--font-weight-bold);
}
.title-secondary {
font-size: 20px;
font-weight: var(--font-weight-bold);
font-size: 20px;
font-weight: var(--font-weight-bold);
}
.title-tertiary {
font-size: 16px;
font-weight: var(--font-weight-bold);
font-size: 16px;
font-weight: var(--font-weight-bold);
}

View File

@@ -5,38 +5,38 @@
/* Overrides */
button {
margin: 0;
padding: 0;
font-size: inherit;
box-shadow: none;
border: none;
cursor: pointer;
margin: 0;
padding: 0;
font-size: inherit;
box-shadow: none;
border: none;
cursor: pointer;
}
a {
color: inherit;
text-decoration: none;
color: inherit;
text-decoration: none;
}
*:focus {
outline: none;
outline: none;
}
button:focus-visible,
a:focus-visible,
[tabindex='0']:focus-visible {
outline: 0.2rem solid var(--color-brand);
outline: 0.2rem solid var(--color-brand);
}
html,
body,
#svelte {
height: 100%;
height: 100%;
}
html {
overflow-y: hidden;
overflow-x: hidden;
overflow-y: hidden;
overflow-x: hidden;
}
h1,
@@ -46,17 +46,17 @@ h4,
h5,
h6,
p {
line-height: 100%;
margin: 0;
line-height: 100%;
margin: 0;
}
ul {
padding: 0 0 0 1.5rem;
padding: 0 0 0 1.5rem;
}
.icon {
height: auto;
width: 16px;
min-width: 16px;
aspect-ratio: 1 / 1;
height: auto;
width: 16px;
min-width: 16px;
aspect-ratio: 1 / 1;
}

View File

@@ -1,17 +1,17 @@
:root {
--rounded: 1rem;
--rounded-top: 1rem 1rem 0 0;
--rounded-bottom: 0 0 1rem 1rem;
--rounded-sm: 0.6rem;
--rounded-max: 999999999px;
--rounded: 1rem;
--rounded-top: 1rem 1rem 0 0;
--rounded-bottom: 0 0 1rem 1rem;
--rounded-sm: 0.6rem;
--rounded-max: 999999999px;
--font-standard: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--font-standard: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--font-size-nm: 1rem; /* 16px */
--font-size-xl: 1.5rem; /* 24px */
--font-size-nm: 1rem; /* 16px */
--font-size-xl: 1.5rem; /* 24px */
--font-weight-regular: 400;
--font-weight-medium: 600;
--font-weight-bold: 700;
--font-weight-regular: 400;
--font-weight-medium: 600;
--font-weight-bold: 700;
}

View File

@@ -1,63 +1,63 @@
.theme-dark {
/* Brand colors */
--color-brand: hsl(145, 78%, 48%);
--color-brand-light: hsl(155, 54%, 35%);
--color-brand-dark: hsl(155, 58%, 25%);
--color-brand-contrast: hsl(0, 0%, 0%);
/* Brand colors */
--color-brand: hsl(145, 78%, 48%);
--color-brand-light: hsl(155, 54%, 35%);
--color-brand-dark: hsl(155, 58%, 25%);
--color-brand-contrast: hsl(0, 0%, 0%);
--color-secondary: hsl(231, 5%, 80%);
--color-tertiary: hsl(231, 3%, 45%);
--color-secondary: hsl(231, 5%, 80%);
--color-tertiary: hsl(231, 3%, 45%);
/* Shadows */
--shadow-inset-lg: inset 0px -2px 2px hsla(221, 39%, 11%, 0.1);
--shadow-inset: inset 0px -2px 2px hsla(221, 39%, 11%, 0.05);
--shadow-inset-sm: inset 0px -1px 1px hsla(221, 39%, 11%, 0.25);
/* Shadows */
--shadow-inset-lg: inset 0px -2px 2px hsla(221, 39%, 11%, 0.1);
--shadow-inset: inset 0px -2px 2px hsla(221, 39%, 11%, 0.05);
--shadow-inset-sm: inset 0px -1px 1px hsla(221, 39%, 11%, 0.25);
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
--shadow-raised: 0px -2px 4px hsla(221, 39%, 11%, 0.1);
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
--shadow-mobile-bar: hsla(0, 0%, 0%, 0.3) 0 0 20px 2px;
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
--shadow-raised: 0px -2px 4px hsla(221, 39%, 11%, 0.1);
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
--shadow-mobile-bar: hsla(0, 0%, 0%, 0.3) 0 0 20px 2px;
/* Text colors */
--color-text: hsl(221, 39%, 90%);
--color-text-light: hsl(215, 14%, 74%);
--color-text-lightest: hsl(220, 9%, 70%);
--color-heading: hsl(222, 16%, 80%);
--color-link: hsl(215, 100%, 75%);
/* Text colors */
--color-text: hsl(221, 39%, 90%);
--color-text-light: hsl(215, 14%, 74%);
--color-text-lightest: hsl(220, 9%, 70%);
--color-heading: hsl(222, 16%, 80%);
--color-link: hsl(215, 100%, 75%);
/* Container colors */
--color-bg: hsl(220, 13%, 15%);
--color-bg-contrast: hsl(0, 0%, 100%);
--color-raised-bg: hsl(220, 13%, 25%);
--color-divider: hsl(220, 13%, 50%);
--color-button-bg: hsl(222, 13%, 35%);
/* Container colors */
--color-bg: hsl(220, 13%, 15%);
--color-bg-contrast: hsl(0, 0%, 100%);
--color-raised-bg: hsl(220, 13%, 25%);
--color-divider: hsl(220, 13%, 50%);
--color-button-bg: hsl(222, 13%, 35%);
/* Label colors */
--color-badge-gray-text: hsl(0, 2%, 69%);
--color-badge-gray-dot: hsl(0, 6%, 77%);
--color-badge-red-text: hsl(343, 63%, 67%);
--color-badge-red-dot: hsl(342, 70%, 53%);
--color-badge-green-text: hsl(156, 53%, 50%);
--color-badge-green-dot: hsl(140, 64%, 40%);
--color-badge-yellow-text: hsl(40, 57%, 60%);
--color-badge-yellow-dot: hsl(40, 92%, 62%);
/* Label colors */
--color-badge-gray-text: hsl(0, 2%, 69%);
--color-badge-gray-dot: hsl(0, 6%, 77%);
--color-badge-red-text: hsl(343, 63%, 67%);
--color-badge-red-dot: hsl(342, 70%, 53%);
--color-badge-green-text: hsl(156, 53%, 50%);
--color-badge-green-dot: hsl(140, 64%, 40%);
--color-badge-yellow-text: hsl(40, 57%, 60%);
--color-badge-yellow-dot: hsl(40, 92%, 62%);
/* Markdown colors */
--color-table-border: hsl(214, 12%, 35%);
--color-table-alternate-row: hsl(216, 12%, 17%);
--color-code-bg: hsl(217, 12%, 29%);
/* Markdown colors */
--color-table-border: hsl(214, 12%, 35%);
--color-table-alternate-row: hsl(216, 12%, 17%);
--color-code-bg: hsl(217, 12%, 29%);
/* Ad colors */
--color-ad-bg: hsl(200, 70%, 25%);
--color-ad-link: hsl(200, 70%, 50%);
/* Ad colors */
--color-ad-bg: hsl(200, 70%, 25%);
--color-ad-link: hsl(200, 70%, 50%);
/* Popup colors */
--color-popup-danger-bg: hsl(355, 70%, 20%);
--color-popup-danger-text: hsl(342, 70%, 75%);
/* Popup colors */
--color-popup-danger-bg: hsl(355, 70%, 20%);
--color-popup-danger-text: hsl(342, 70%, 75%);
--color-input-light: hsl(220, 13%, 20%);
--color-input-light: hsl(220, 13%, 20%);
/* Scrollbar color */
--color-scrollbar-thumb: hsl(220, 13%, 40%);
/* Scrollbar color */
--color-scrollbar-thumb: hsl(220, 13%, 40%);
}

View File

@@ -1,62 +1,62 @@
.theme-light {
/* Brand colors */
--color-brand: hsl(155, 58%, 44%);
--color-brand-light: hsl(135, 50%, 78%);
--color-brand-dark: hsl(155, 58%, 38%);
--color-brand-contrast: hsl(0, 0%, 100%);
/* Brand colors */
--color-brand: hsl(155, 58%, 44%);
--color-brand-light: hsl(135, 50%, 78%);
--color-brand-dark: hsl(155, 58%, 38%);
--color-brand-contrast: hsl(0, 0%, 100%);
--color-secondary: hsl(231, 5%, 45%);
--color-tertiary: hsl(231, 3%, 75%);
--color-secondary: hsl(231, 5%, 45%);
--color-tertiary: hsl(231, 3%, 75%);
/* Shadows */
--shadow-inset-lg: inset 0px -2px 2px hsla(221, 39%, 11%, 0.1);
--shadow-inset: inset 0px -2px 2px hsla(221, 39%, 11%, 0.05);
--shadow-inset-sm: inset 0px -1px 2px hsla(221, 39%, 11%, 0.15);
/* Shadows */
--shadow-inset-lg: inset 0px -2px 2px hsla(221, 39%, 11%, 0.1);
--shadow-inset: inset 0px -2px 2px hsla(221, 39%, 11%, 0.05);
--shadow-inset-sm: inset 0px -1px 2px hsla(221, 39%, 11%, 0.15);
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
--shadow-raised: 0px 2px 4px hsla(221, 39%, 11%, 0.1);
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, hsla(0, 0%, 0%, 0.1) 0px 2px 4px -1px;
--shadow-mobile-bar: hsla(0, 0%, 0%, 0.3) 0 0 20px 2px;
--shadow-raised-lg: 0px 2px 4px hsla(221, 39%, 11%, 0.2);
--shadow-raised: 0px 2px 4px hsla(221, 39%, 11%, 0.1);
--shadow-floating: hsla(0, 0%, 0%, 0) 0px 0px 0px 0px, hsla(0, 0%, 0%, 0) 0px 0px 0px 0px,
hsla(0, 0%, 0%, 0.1) 0px 4px 6px -1px, hsla(0, 0%, 0%, 0.1) 0px 2px 4px -1px;
--shadow-mobile-bar: hsla(0, 0%, 0%, 0.3) 0 0 20px 2px;
/* Text colors */
--color-text: hsl(221, 39%, 11%);
--color-text-light: hsl(215, 14%, 34%);
--color-text-lightest: hsl(220, 9%, 46%);
--color-heading: hsl(222, 16%, 20%);
--color-link: hsl(221, 55%, 50%);
/* Text colors */
--color-text: hsl(221, 39%, 11%);
--color-text-light: hsl(215, 14%, 34%);
--color-text-lightest: hsl(220, 9%, 46%);
--color-heading: hsl(222, 16%, 20%);
--color-link: hsl(221, 55%, 50%);
/* Container colors */
--color-bg: hsl(220, 13%, 91%);
--color-bg-contrast: hsl(0, 0%, 0%);
--color-raised-bg: hsl(0, 0%, 100%);
--color-divider: hsl(220, 13%, 91%);
--color-button-bg: hsl(220, 13%, 91%);
--color-input-text-light: hsl(0, 0%, 94%);
/* Container colors */
--color-bg: hsl(220, 13%, 91%);
--color-bg-contrast: hsl(0, 0%, 0%);
--color-raised-bg: hsl(0, 0%, 100%);
--color-divider: hsl(220, 13%, 91%);
--color-button-bg: hsl(220, 13%, 91%);
--color-input-text-light: hsl(0, 0%, 94%);
/* Label colors */
--color-badge-gray-text: hsl(0, 2%, 39%);
--color-badge-gray-dot: hsl(0, 6%, 77%);
--color-badge-red-text: hsl(343, 63%, 27%);
--color-badge-red-dot: hsl(342, 70%, 53%);
--color-badge-green-text: hsl(156, 53%, 20%);
--color-badge-green-dot: hsl(140, 64%, 40%);
--color-badge-yellow-text: hsl(40, 57%, 29%);
--color-badge-yellow-dot: hsl(40, 92%, 62%);
/* Label colors */
--color-badge-gray-text: hsl(0, 2%, 39%);
--color-badge-gray-dot: hsl(0, 6%, 77%);
--color-badge-red-text: hsl(343, 63%, 27%);
--color-badge-red-dot: hsl(342, 70%, 53%);
--color-badge-green-text: hsl(156, 53%, 20%);
--color-badge-green-dot: hsl(140, 64%, 40%);
--color-badge-yellow-text: hsl(40, 57%, 29%);
--color-badge-yellow-dot: hsl(40, 92%, 62%);
/* Markdown colors */
--color-table-border: hsl(210, 10%, 89%);
--color-table-alternate-row: hsl(210, 29%, 97%);
--color-code-bg: hsl(210, 29%, 96%);
/* Markdown colors */
--color-table-border: hsl(210, 10%, 89%);
--color-table-alternate-row: hsl(210, 29%, 97%);
--color-code-bg: hsl(210, 29%, 96%);
/* Ad colors */
--color-ad-bg: hsl(200, 70%, 82%);
--color-ad-link: hsl(200, 80%, 40%);
/* Ad colors */
--color-ad-bg: hsl(200, 70%, 82%);
--color-ad-link: hsl(200, 80%, 40%);
/* Popup colors */
--color-popup-danger-bg: hsl(355, 70%, 88%);
--color-popup-danger-text: hsl(342, 70%, 35%);
/* Popup colors */
--color-popup-danger-bg: hsl(355, 70%, 88%);
--color-popup-danger-text: hsl(342, 70%, 35%);
/* Scrollbar color */
--color-scrollbar-thumb: hsl(220, 13%, 70%);
/* Scrollbar color */
--color-scrollbar-thumb: hsl(220, 13%, 70%);
}

View File

@@ -1,14 +1,14 @@
.theme-oled {
@extend .dark-theme;
@extend .dark-theme;
/* Container colors */
--color-bg: hsl(220, 13%, 0%);
--color-raised-bg: hsl(220, 13%, 10%);
--color-raised-bg-hover: hsl(220, 13%, 20%);
--color-divider: hsl(220, 13%, 35%);
--color-button-bg: hsl(220, 13%, 20%);
/* Container colors */
--color-bg: hsl(220, 13%, 0%);
--color-raised-bg: hsl(220, 13%, 10%);
--color-raised-bg-hover: hsl(220, 13%, 20%);
--color-divider: hsl(220, 13%, 35%);
--color-button-bg: hsl(220, 13%, 20%);
/* Ad colors */
--color-ad-bg: hsl(200, 70%, 15%);
--color-ad-link: hsl(200, 70%, 45%);
/* Ad colors */
--color-ad-bg: hsl(200, 70%, 15%);
--color-ad-link: hsl(200, 70%, 45%);
}

View File

@@ -1,16 +1,16 @@
:root {
/* Borders */
--border-width: 1px;
--border-style: solid;
--border: var(--border-width) var(--border-style);
/* Borders */
--border-width: 1px;
--border-style: solid;
--border: var(--border-width) var(--border-style);
/* Rounded radii */
--rounded-sm: 8px;
--rounded: 10px;
--rounded-lg: 14px;
--rounded-max: 100px;
--rounded-top: var(--rounded) var(--rounded) 0 0;
--rounded-bottom: 0 0 var(--rounded) var(--rounded);
--rounded-sm-top: var(--rounded-sm) var(--rounded-sm) 0 0;
--rounded-sm-bottom: 0 0 var(--rounded-sm) var(--rounded-sm);
/* Rounded radii */
--rounded-sm: 8px;
--rounded: 10px;
--rounded-lg: 14px;
--rounded-max: 100px;
--rounded-top: var(--rounded) var(--rounded) 0 0;
--rounded-bottom: 0 0 var(--rounded) var(--rounded);
--rounded-sm-top: var(--rounded-sm) var(--rounded-sm) 0 0;
--rounded-sm-bottom: 0 0 var(--rounded-sm) var(--rounded-sm);
}

View File

@@ -1,6 +1,6 @@
:root {
/* these are values for the display CSS property */
/*
/* these are values for the display CSS property */
/*
--display-values: (
block,
flex,
@@ -21,7 +21,7 @@
);
*/
/*
/*
These are our margin and padding utility spacers. The default step size we
use is 8px. This gives us a key of:
0 => 0px
@@ -31,18 +31,18 @@
4 => 24px
5 => 32px
6 => 40px */
--spacer: 8px;
--spacer: 8px;
/* Our spacing scale */
--spacer-0: 0; /* 0 */
--spacer-1: calc(var(--spacer) * 0.5); /* 4px */
--spacer-2: --spacer; /* 8px */
--spacer-3: calc(var(--spacer) * 2); /* 16px */
--spacer-4: calc(var(--spacer) * 3); /* 24px */
--spacer-5: calc(var(--spacer) * 4); /* 32px */
--spacer-6: calc(var(--spacer) * 5); /* 40px */
/* Our spacing scale */
--spacer-0: 0; /* 0 */
--spacer-1: calc(var(--spacer) * 0.5); /* 4px */
--spacer-2: --spacer; /* 8px */
--spacer-3: calc(var(--spacer) * 2); /* 16px */
--spacer-4: calc(var(--spacer) * 3); /* 24px */
--spacer-5: calc(var(--spacer) * 4); /* 32px */
--spacer-6: calc(var(--spacer) * 5); /* 40px */
/*
/*
/* The list of spacer values
--spacers: (
--spacer-0,

View File

@@ -1,39 +1,39 @@
:root {
/* Heading sizes - mobile */
/* h4-h6 remain the same size on both mobile & desktop */
--h00-size-mobile: 40px;
--h0-size-mobile: 32px;
--h1-size-mobile: 26px;
--h2-size-mobile: 22px;
--h3-size-mobile: 18px;
/* Heading sizes - mobile */
/* h4-h6 remain the same size on both mobile & desktop */
--h00-size-mobile: 40px;
--h0-size-mobile: 32px;
--h1-size-mobile: 26px;
--h2-size-mobile: 22px;
--h3-size-mobile: 18px;
/* Heading sizes - desktop */
--h00-size: 48px;
--h0-size: 40px;
--h1-size: 32px;
--h2-size: 24px;
--h3-size: 20px;
--h4-size: 16px;
--h5-size: 14px;
--h6-size: 12px;
/* Heading sizes - desktop */
--h00-size: 48px;
--h0-size: 40px;
--h1-size: 32px;
--h2-size: 24px;
--h3-size: 20px;
--h4-size: 16px;
--h5-size: 14px;
--h6-size: 12px;
--font-size-lg: 19px;
--font-size-sm: 13px;
--font-size: 16px;
--font-size-lg: 19px;
--font-size-sm: 13px;
--font-size: 16px;
/* Line heights */
--lh-condensed-ultra: 1;
--lh-condensed: 1.25;
--lh-default: 1.5;
/* Line heights */
--lh-condensed-ultra: 1;
--lh-condensed: 1.25;
--lh-default: 1.5;
/* Font weights */
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-semibold: 500;
--font-weight-bold: 600;
/* Font weights */
--font-weight-light: 300;
--font-weight-normal: 400;
--font-weight-semibold: 500;
--font-weight-bold: 600;
/* Font stacks */
--body-font: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji';
--mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
/* Font stacks */
--body-font: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji';
--mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
}

View File

@@ -4,56 +4,54 @@
* @see https://stackoverflow.com/a/67338038/938822
*/
export function ago(
/** A Date object, timestamp or string parsable with Date.parse() */
date: string | number | Date,
/** A Date object, timestamp or string parsable with Date.parse() */
nowDate: string | number | Date = Date.now(),
/** A Intl formater */
rft: Intl.RelativeTimeFormat = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })
/** A Date object, timestamp or string parsable with Date.parse() */
date: string | number | Date,
/** A Date object, timestamp or string parsable with Date.parse() */
nowDate: string | number | Date = Date.now(),
/** A Intl formater */
rft: Intl.RelativeTimeFormat = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto' })
): string {
const SECOND = 1000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
const MONTH = 30 * DAY;
const YEAR = 365 * DAY;
const intervals = [
{ ge: YEAR, divisor: YEAR, unit: 'year' },
{ ge: MONTH, divisor: MONTH, unit: 'month' },
{ ge: WEEK, divisor: WEEK, unit: 'week' },
{ ge: DAY, divisor: DAY, unit: 'day' },
{ ge: HOUR, divisor: HOUR, unit: 'hour' },
{ ge: MINUTE, divisor: MINUTE, unit: 'minute' },
{ ge: 30 * SECOND, divisor: SECOND, unit: 'seconds' },
{ ge: 0, divisor: 1, text: 'just now' },
];
const now = typeof nowDate === 'object' ? nowDate.getTime() : new Date(nowDate).getTime();
const diff = now - (typeof date === 'object' ? date : new Date(date)).getTime();
const diffAbs = Math.abs(diff);
for (const interval of intervals) {
if (diffAbs >= interval.ge) {
const x = Math.round(Math.abs(diff) / interval.divisor);
const isFuture = diff < 0;
return interval.unit
? rft.format(isFuture ? x : -x, interval.unit as Unit)
: interval.text;
}
}
const SECOND = 1000
const MINUTE = 60 * SECOND
const HOUR = 60 * MINUTE
const DAY = 24 * HOUR
const WEEK = 7 * DAY
const MONTH = 30 * DAY
const YEAR = 365 * DAY
const intervals = [
{ ge: YEAR, divisor: YEAR, unit: 'year' },
{ ge: MONTH, divisor: MONTH, unit: 'month' },
{ ge: WEEK, divisor: WEEK, unit: 'week' },
{ ge: DAY, divisor: DAY, unit: 'day' },
{ ge: HOUR, divisor: HOUR, unit: 'hour' },
{ ge: MINUTE, divisor: MINUTE, unit: 'minute' },
{ ge: 30 * SECOND, divisor: SECOND, unit: 'seconds' },
{ ge: 0, divisor: 1, text: 'just now' },
]
const now = typeof nowDate === 'object' ? nowDate.getTime() : new Date(nowDate).getTime()
const diff = now - (typeof date === 'object' ? date : new Date(date)).getTime()
const diffAbs = Math.abs(diff)
for (const interval of intervals) {
if (diffAbs >= interval.ge) {
const x = Math.round(Math.abs(diff) / interval.divisor)
const isFuture = diff < 0
return interval.unit ? rft.format(isFuture ? x : -x, interval.unit as Unit) : interval.text
}
}
}
type Unit =
| 'second'
| 'seconds'
| 'minute'
| 'minutes'
| 'hour'
| 'hours'
| 'day'
| 'days'
| 'week'
| 'weeks'
| 'month'
| 'months'
| 'year'
| 'years';
| 'second'
| 'seconds'
| 'minute'
| 'minutes'
| 'hour'
| 'hours'
| 'day'
| 'days'
| 'week'
| 'weeks'
| 'month'
| 'months'
| 'year'
| 'years'

View File

@@ -1,3 +1,3 @@
export function classCombine(names) {
return names.filter((name) => name && !name.includes('undefined')).join(' ');
return names.filter((name) => name && !name.includes('undefined')).join(' ')
}

View File

@@ -1,4 +1,4 @@
export { ago } from './ago';
export { Permissions } from './permissions';
export { formatVersions, getPrimary, downloadUrl } from './versions';
export { markdown, markdownInline } from './parse';
export { ago } from './ago'
export { Permissions } from './permissions'
export { formatVersions, getPrimary, downloadUrl } from './versions'
export { markdown, markdownInline } from './parse'

View File

@@ -1,142 +1,142 @@
import { marked } from 'marked';
import hljs from 'highlight.js';
import insane from 'insane';
import { marked } from 'marked'
import hljs from 'highlight.js'
import insane from 'insane'
const renderer = new marked.Renderer();
const renderer = new marked.Renderer()
renderer.image = (href, text) => {
if (/^https?:\/\/(www\.)?youtube\.com\/watch\?v=[a-zA-Z0-9_]{11}$/.test(href)) {
const id = href.substring(32, 43);
return `<iframe src="https://www.youtube-nocookie.com/embed/${id}?&modestbranding=1&autoplay=0&rel=0" frameborder="0" allowfullscreen></iframe>`;
} else {
return `<img src="${href}" alt="${text}" />`;
}
};
if (/^https?:\/\/(www\.)?youtube\.com\/watch\?v=[a-zA-Z0-9_]{11}$/.test(href)) {
const id = href.substring(32, 43)
return `<iframe src="https://www.youtube-nocookie.com/embed/${id}?&modestbranding=1&autoplay=0&rel=0" frameborder="0" allowfullscreen></iframe>`
} else {
return `<img src="${href}" alt="${text}" />`
}
}
renderer.link = (href, title, text) => {
if (href === null) {
return text;
}
let out = '<a href="' + href + '" rel="external nofollow"';
if (title) {
out += ' title="' + title + '"';
}
out += '>' + text + '</a>';
return out;
};
if (href === null) {
return text
}
let out = '<a href="' + href + '" rel="external nofollow"'
if (title) {
out += ' title="' + title + '"'
}
out += '>' + text + '</a>'
return out
}
marked.setOptions({
renderer,
highlight: function (code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return hljs.highlight(code, { language }).value;
},
langPrefix: 'hljs language-',
headerPrefix: '',
gfm: true,
smartLists: true,
});
renderer,
highlight: function (code, lang) {
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
return hljs.highlight(code, { language }).value
},
langPrefix: 'hljs language-',
headerPrefix: '',
gfm: true,
smartLists: true,
})
function sanitize(html: string): string {
return insane(html, {
allowedAttributes: {
a: ['href', 'target', 'title', 'rel'],
iframe: ['allowfullscreen', 'src', 'width', 'height'],
img: ['src', 'width', 'height', 'alt'],
h1: ['id'],
h2: ['id'],
h3: ['id'],
h4: ['id'],
h5: ['id'],
h6: ['id'],
code: ['class'],
span: ['class'],
input: ['type', 'checked', 'disabled'],
font: ['color'],
},
allowedClasses: {},
allowedSchemes: ['http', 'https', 'mailto'],
allowedTags: [
'a',
'b',
'blockquote',
'br',
'caption',
'center',
'code',
'del',
'details',
'div',
'em',
'font',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hr',
'i',
'iframe',
'img',
'input',
'ins',
'kbd',
'li',
'main',
'ol',
'p',
'pre',
'span',
'strike',
'strong',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'th',
'thead',
'tr',
'u',
'ul',
],
filter: ({ tag, attrs }): boolean => {
if (tag === 'iframe') {
return /^https?:\/\/(www\.)?(youtube|youtube-nocookie)\.com\/embed\/[a-zA-Z0-9_]{11}(\?)?(&modestbranding=1)?(&autoplay=0)?(&loop=1)?(&playlist=[a-zA-Z0-9_]{11})?(&rel=0)?$/.test(
attrs.src || ''
);
} else if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)) {
return attrs.id !== 'svelte';
} else if (tag === 'input') {
return attrs.type === 'checkbox' && attrs.disabled === '';
} else if (tag === 'code' || tag === 'span') {
return !attrs.class || attrs.class.replace(' ', '').startsWith('hljs');
} else {
return true;
}
},
transformText: null,
});
return insane(html, {
allowedAttributes: {
a: ['href', 'target', 'title', 'rel'],
iframe: ['allowfullscreen', 'src', 'width', 'height'],
img: ['src', 'width', 'height', 'alt'],
h1: ['id'],
h2: ['id'],
h3: ['id'],
h4: ['id'],
h5: ['id'],
h6: ['id'],
code: ['class'],
span: ['class'],
input: ['type', 'checked', 'disabled'],
font: ['color'],
},
allowedClasses: {},
allowedSchemes: ['http', 'https', 'mailto'],
allowedTags: [
'a',
'b',
'blockquote',
'br',
'caption',
'center',
'code',
'del',
'details',
'div',
'em',
'font',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'hr',
'i',
'iframe',
'img',
'input',
'ins',
'kbd',
'li',
'main',
'ol',
'p',
'pre',
'span',
'strike',
'strong',
'sub',
'summary',
'sup',
'table',
'tbody',
'td',
'th',
'thead',
'tr',
'u',
'ul',
],
filter: ({ tag, attrs }): boolean => {
if (tag === 'iframe') {
return /^https?:\/\/(www\.)?(youtube|youtube-nocookie)\.com\/embed\/[a-zA-Z0-9_]{11}(\?)?(&modestbranding=1)?(&autoplay=0)?(&loop=1)?(&playlist=[a-zA-Z0-9_]{11})?(&rel=0)?$/.test(
attrs.src || ''
)
} else if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)) {
return attrs.id !== 'svelte'
} else if (tag === 'input') {
return attrs.type === 'checkbox' && attrs.disabled === ''
} else if (tag === 'code' || tag === 'span') {
return !attrs.class || attrs.class.replace(' ', '').startsWith('hljs')
} else {
return true
}
},
transformText: null,
})
}
export function markdownInline(markdown: string): string {
return insane(
marked.parseInline(markdown),
{
allowedAttributes: {
a: ['href', 'target', 'title', 'rel'],
},
allowedClasses: {},
allowedSchemes: ['http', 'https', 'mailto'],
allowedTags: ['a', 'b', 'br', 'code', 'em', 'i', 'strike', 'strong', 'sub', 'sup', 'u'],
transformText: null,
},
true
);
return insane(
marked.parseInline(markdown),
{
allowedAttributes: {
a: ['href', 'target', 'title', 'rel'],
},
allowedClasses: {},
allowedSchemes: ['http', 'https', 'mailto'],
allowedTags: ['a', 'b', 'br', 'code', 'em', 'i', 'strike', 'strong', 'sub', 'sup', 'u'],
transformText: null,
},
true
)
}
export function markdown(markdown: string): string {
return sanitize(marked.parse(markdown));
return sanitize(marked.parse(markdown))
}

View File

@@ -1,38 +1,38 @@
export class Permissions {
data = {
uploadVersions: false,
deleteVersion: false,
editDetails: false,
editBody: false,
manageInvites: false,
removeMember: false,
editMember: false,
deleteProject: false,
};
data = {
uploadVersions: false,
deleteVersion: false,
editDetails: false,
editBody: false,
manageInvites: false,
removeMember: false,
editMember: false,
deleteProject: false,
}
get settingsPage(): boolean {
return (
this.data.manageInvites ||
this.data.removeMember ||
this.data.editMember ||
this.data.deleteProject
);
}
get settingsPage(): boolean {
return (
this.data.manageInvites ||
this.data.removeMember ||
this.data.editMember ||
this.data.deleteProject
)
}
constructor(from: number | 'ALL' | null) {
if (from === 'ALL' || from === 0b11111111 || from === null) {
Object.keys(this.data).forEach((v) => (this.data[v] = true));
} else if (typeof from === 'number') {
this.data = {
uploadVersions: !!(from & (1 << 0)),
deleteVersion: !!(from & (1 << 1)),
editDetails: !!(from & (1 << 2)),
editBody: !!(from & (1 << 3)),
manageInvites: !!(from & (1 << 4)),
removeMember: !!(from & (1 << 5)),
editMember: !!(from & (1 << 6)),
deleteProject: !!(from & (1 << 7)),
};
}
}
constructor(from: number | 'ALL' | null) {
if (from === 'ALL' || from === 0b11111111 || from === null) {
Object.keys(this.data).forEach((v) => (this.data[v] = true))
} else if (typeof from === 'number') {
this.data = {
uploadVersions: !!(from & (1 << 0)),
deleteVersion: !!(from & (1 << 1)),
editDetails: !!(from & (1 << 2)),
editBody: !!(from & (1 << 3)),
manageInvites: !!(from & (1 << 4)),
removeMember: !!(from & (1 << 5)),
editMember: !!(from & (1 << 6)),
deleteProject: !!(from & (1 << 7)),
}
}
}
}

View File

@@ -1,6 +1,6 @@
let idCounter = 0;
let idCounter = 0
export function uniqueId(prefix = ''): string {
const id = ++idCounter;
return prefix + id;
const id = ++idCounter
return prefix + id
}

View File

@@ -1,86 +1,83 @@
import gameVersions from '$generated/gameVersions.json';
import gameVersions from '$generated/gameVersions.json'
export function formatVersions(versionArray: string[]): string {
const allVersions = gameVersions.slice().reverse();
const allReleases = allVersions.filter((x) => x.version_type === 'release');
const allVersions = gameVersions.slice().reverse()
const allReleases = allVersions.filter((x) => x.version_type === 'release')
const intervals = [];
let currentInterval = 0;
const intervals = []
let currentInterval = 0
for (let i = 0; i < versionArray.length; i++) {
const index = allVersions.findIndex((x) => x.version === versionArray[i]);
const releaseIndex = allReleases.findIndex((x) => x.version === versionArray[i]);
for (let i = 0; i < versionArray.length; i++) {
const index = allVersions.findIndex((x) => x.version === versionArray[i])
const releaseIndex = allReleases.findIndex((x) => x.version === versionArray[i])
if (i === 0) {
intervals.push([[versionArray[i], index, releaseIndex]]);
} else {
const intervalBase = intervals[currentInterval];
if (i === 0) {
intervals.push([[versionArray[i], index, releaseIndex]])
} else {
const intervalBase = intervals[currentInterval]
if (
(index - intervalBase[intervalBase.length - 1][1] === 1 ||
releaseIndex - intervalBase[intervalBase.length - 1][2] === 1) &&
(allVersions[intervalBase[0][1]].version_type === 'release' ||
allVersions[index].version_type !== 'release')
) {
intervalBase[1] = [versionArray[i], index, releaseIndex];
} else {
currentInterval += 1;
intervals[currentInterval] = [[versionArray[i], index, releaseIndex]];
}
}
}
if (
(index - intervalBase[intervalBase.length - 1][1] === 1 ||
releaseIndex - intervalBase[intervalBase.length - 1][2] === 1) &&
(allVersions[intervalBase[0][1]].version_type === 'release' ||
allVersions[index].version_type !== 'release')
) {
intervalBase[1] = [versionArray[i], index, releaseIndex]
} else {
currentInterval += 1
intervals[currentInterval] = [[versionArray[i], index, releaseIndex]]
}
}
}
const newIntervals = [];
for (let i = 0; i < intervals.length; i++) {
const interval = intervals[i];
const newIntervals = []
for (let i = 0; i < intervals.length; i++) {
const interval = intervals[i]
if (interval.length === 2 && interval[0][2] !== -1 && interval[1][2] === -1) {
let lastSnapshot = null;
for (let j = interval[1][1]; j > interval[0][1]; j--) {
if (allVersions[j].version_type === 'release') {
newIntervals.push([
interval[0],
[
allVersions[j].version,
j,
allReleases.findIndex((x) => x.version === allVersions[j].version),
],
]);
if (interval.length === 2 && interval[0][2] !== -1 && interval[1][2] === -1) {
let lastSnapshot = null
for (let j = interval[1][1]; j > interval[0][1]; j--) {
if (allVersions[j].version_type === 'release') {
newIntervals.push([
interval[0],
[
allVersions[j].version,
j,
allReleases.findIndex((x) => x.version === allVersions[j].version),
],
])
if (lastSnapshot !== null && lastSnapshot !== j + 1) {
newIntervals.push([
[allVersions[lastSnapshot].version, lastSnapshot, -1],
interval[1],
]);
} else {
newIntervals.push([interval[1]]);
}
if (lastSnapshot !== null && lastSnapshot !== j + 1) {
newIntervals.push([[allVersions[lastSnapshot].version, lastSnapshot, -1], interval[1]])
} else {
newIntervals.push([interval[1]])
}
break;
} else {
lastSnapshot = j;
}
}
} else {
newIntervals.push(interval);
}
}
break
} else {
lastSnapshot = j
}
}
} else {
newIntervals.push(interval)
}
}
const output = [];
const output = []
for (const interval of newIntervals) {
if (interval.length === 2) {
output.push(`${interval[0][0]}${interval[1][0]}`);
} else {
output.push(interval[0][0]);
}
}
for (const interval of newIntervals) {
if (interval.length === 2) {
output.push(`${interval[0][0]}${interval[1][0]}`)
} else {
output.push(interval[0][0])
}
}
return output.join(', ');
return output.join(', ')
}
export const getPrimary = (files) => files.find((file) => file.primary) || files[0];
export const getPrimary = (files) => files.find((file) => file.primary) || files[0]
export function downloadUrl(file): string {
return import.meta.env.VITE_API_URL + `version_file/${file?.hashes.sha1}/download`;
return import.meta.env.VITE_API_URL + `version_file/${file?.hashes.sha1}/download`
}