+
- Changelog
-
-
-
- Files
-
-
-
-
-
- {{ file.filename }}
-
- ({{ formatBytes(file.size) }})
- Primary
-
-
-
-
-
- {{ installed ? 'Installed' : 'Install' }}
-
-
-
-
- Dependencies
-
-
-
-
- {{ dependency.title }}
- {{ dependency.subtitle }}
-
-
-
-
-
-
{{ dependency.title }}
-
{{ dependency.subtitle }}
-
+
+
+
{{ version.name }}
+
+
-
-
-
- Metadata
-
-
diff --git a/apps/app-frontend/src/pages/project/Versions.vue b/apps/app-frontend/src/pages/project/Versions.vue
index d52a9f21b4..02073a38bc 100644
--- a/apps/app-frontend/src/pages/project/Versions.vue
+++ b/apps/app-frontend/src/pages/project/Versions.vue
@@ -1,195 +1,196 @@
-
-
-
-
- install(version.id)"
- >
-
-
-
-
-
-
-
-
-
-
- Add to another instance
+
+
+
+
+ install(version.id)"
+ >
+
+
+
+
+
+
+
+
+
+
+ Add to another instance
+
+ Open in browser
+
+
+
+
+
- Open in browser
-
-
-
-
-
-
-
-
+
+
diff --git a/apps/app-frontend/src/pages/project/index.js b/apps/app-frontend/src/pages/project/index.js
index 56a7b3b9d3..924bbf1bb3 100644
--- a/apps/app-frontend/src/pages/project/index.js
+++ b/apps/app-frontend/src/pages/project/index.js
@@ -1,7 +1,7 @@
-import Index from './Index.vue'
import Description from './Description.vue'
-import Versions from './Versions.vue'
import Gallery from './Gallery.vue'
+import Index from './Index.vue'
import Version from './Version.vue'
+import Versions from './Versions.vue'
-export { Index, Description, Versions, Gallery, Version }
+export { Description, Gallery, Index, Version, Versions }
diff --git a/apps/app-frontend/src/routes.js b/apps/app-frontend/src/routes.js
index 67172e68df..f2bfcb50d8 100644
--- a/apps/app-frontend/src/routes.js
+++ b/apps/app-frontend/src/routes.js
@@ -1,183 +1,202 @@
import { createRouter, createWebHistory } from 'vue-router'
+
import * as Pages from '@/pages'
-import * as Project from '@/pages/project'
import * as Instance from '@/pages/instance'
import * as Library from '@/pages/library'
+import * as Project from '@/pages/project'
/**
* Configures application routing. Add page to pages/index and then add to route table here.
*/
export default new createRouter({
- history: createWebHistory(),
- routes: [
- {
- path: '/',
- name: 'Home',
- component: Pages.Index,
- meta: {
- breadcrumb: [{ name: 'Home' }],
- },
- },
- {
- path: '/worlds',
- name: 'Worlds',
- component: Pages.Worlds,
- meta: {
- breadcrumb: [{ name: 'Worlds' }],
- },
- },
- {
- path: '/browse/:projectType',
- name: 'Discover content',
- component: Pages.Browse,
- meta: {
- breadcrumb: [{ name: 'Discover content' }],
- },
- },
- {
- path: '/skins',
- name: 'Skins',
- component: Pages.Skins,
- meta: {
- breadcrumb: [{ name: 'Skins' }],
- },
- },
- {
- path: '/library',
- name: 'Library',
- component: Library.Index,
- meta: {
- breadcrumb: [{ name: 'Library' }],
- },
- children: [
+ history: createWebHistory(),
+ routes: [
{
- path: '',
- name: 'Overview',
- component: Library.Overview,
+ path: '/',
+ name: 'Home',
+ component: Pages.Index,
+ meta: {
+ breadcrumb: [{ name: 'Home' }],
+ },
},
{
- path: 'downloaded',
- name: 'Downloaded',
- component: Library.Downloaded,
+ path: '/worlds',
+ name: 'Worlds',
+ component: Pages.Worlds,
+ meta: {
+ breadcrumb: [{ name: 'Worlds' }],
+ },
},
{
- path: 'custom',
- name: 'Custom',
- component: Library.Custom,
- },
- ],
- },
- {
- path: '/project/:id',
- name: 'Project',
- component: Project.Index,
- props: true,
- children: [
- {
- path: '',
- name: 'Description',
- component: Project.Description,
- meta: {
- useContext: true,
- breadcrumb: [{ name: '?Project' }],
- },
+ path: '/browse/:projectType',
+ name: 'Discover content',
+ component: Pages.Browse,
+ meta: {
+ breadcrumb: [{ name: 'Discover content' }],
+ },
},
{
- path: 'versions',
- name: 'Versions',
- component: Project.Versions,
- meta: {
- useContext: true,
- breadcrumb: [{ name: '?Project', link: '/project/{id}/' }, { name: 'Versions' }],
- },
+ path: '/skins',
+ name: 'Skins',
+ component: Pages.Skins,
+ meta: {
+ breadcrumb: [{ name: 'Skins' }],
+ },
},
{
- path: 'version/:version',
- name: 'Version',
- component: Project.Version,
- props: true,
- meta: {
- useContext: true,
- breadcrumb: [
- { name: '?Project', link: '/project/{id}/' },
- { name: 'Versions', link: '/project/{id}/versions' },
- { name: '?Version' },
+ path: '/library',
+ name: 'Library',
+ component: Library.Index,
+ meta: {
+ breadcrumb: [{ name: 'Library' }],
+ },
+ children: [
+ {
+ path: '',
+ name: 'Overview',
+ component: Library.Overview,
+ },
+ {
+ path: 'downloaded',
+ name: 'Downloaded',
+ component: Library.Downloaded,
+ },
+ {
+ path: 'custom',
+ name: 'Custom',
+ component: Library.Custom,
+ },
],
- },
},
{
- path: 'gallery',
- name: 'Gallery',
- component: Project.Gallery,
- meta: {
- useContext: true,
- breadcrumb: [{ name: '?Project', link: '/project/{id}/' }, { name: 'Gallery' }],
- },
- },
- ],
- },
- {
- path: '/instance/:id',
- name: 'Instance',
- component: Instance.Index,
- props: true,
- children: [
- // {
- // path: '',
- // name: 'Overview',
- // component: Instance.Overview,
- // meta: {
- // useRootContext: true,
- // breadcrumb: [{ name: '?Instance' }],
- // },
- // },
- {
- path: 'worlds',
- name: 'InstanceWorlds',
- component: Instance.Worlds,
- meta: {
- useRootContext: true,
- breadcrumb: [{ name: '?Instance', link: '/instance/{id}/' }, { name: 'Worlds' }],
- },
- },
- {
- path: '',
- name: 'Mods',
- component: Instance.Mods,
- meta: {
- useRootContext: true,
- breadcrumb: [{ name: '?Instance', link: '/instance/{id}/' }, { name: 'Content' }],
- },
- },
- {
- path: 'projects/:type',
- name: 'ModsFilter',
- component: Instance.Mods,
- meta: {
- useRootContext: true,
- breadcrumb: [{ name: '?Instance', link: '/instance/{id}/' }, { name: 'Content' }],
- },
+ path: '/project/:id',
+ name: 'Project',
+ component: Project.Index,
+ props: true,
+ children: [
+ {
+ path: '',
+ name: 'Description',
+ component: Project.Description,
+ meta: {
+ useContext: true,
+ breadcrumb: [{ name: '?Project' }],
+ },
+ },
+ {
+ path: 'versions',
+ name: 'Versions',
+ component: Project.Versions,
+ meta: {
+ useContext: true,
+ breadcrumb: [
+ { name: '?Project', link: '/project/{id}/' },
+ { name: 'Versions' },
+ ],
+ },
+ },
+ {
+ path: 'version/:version',
+ name: 'Version',
+ component: Project.Version,
+ props: true,
+ meta: {
+ useContext: true,
+ breadcrumb: [
+ { name: '?Project', link: '/project/{id}/' },
+ { name: 'Versions', link: '/project/{id}/versions' },
+ { name: '?Version' },
+ ],
+ },
+ },
+ {
+ path: 'gallery',
+ name: 'Gallery',
+ component: Project.Gallery,
+ meta: {
+ useContext: true,
+ breadcrumb: [
+ { name: '?Project', link: '/project/{id}/' },
+ { name: 'Gallery' },
+ ],
+ },
+ },
+ ],
},
{
- path: 'logs',
- name: 'Logs',
- component: Instance.Logs,
- meta: {
- useRootContext: true,
- breadcrumb: [{ name: '?Instance', link: '/instance/{id}/' }, { name: 'Logs' }],
- },
+ path: '/instance/:id',
+ name: 'Instance',
+ component: Instance.Index,
+ props: true,
+ children: [
+ // {
+ // path: '',
+ // name: 'Overview',
+ // component: Instance.Overview,
+ // meta: {
+ // useRootContext: true,
+ // breadcrumb: [{ name: '?Instance' }],
+ // },
+ // },
+ {
+ path: 'worlds',
+ name: 'InstanceWorlds',
+ component: Instance.Worlds,
+ meta: {
+ useRootContext: true,
+ breadcrumb: [
+ { name: '?Instance', link: '/instance/{id}/' },
+ { name: 'Worlds' },
+ ],
+ },
+ },
+ {
+ path: '',
+ name: 'Mods',
+ component: Instance.Mods,
+ meta: {
+ useRootContext: true,
+ breadcrumb: [
+ { name: '?Instance', link: '/instance/{id}/' },
+ { name: 'Content' },
+ ],
+ },
+ },
+ {
+ path: 'projects/:type',
+ name: 'ModsFilter',
+ component: Instance.Mods,
+ meta: {
+ useRootContext: true,
+ breadcrumb: [
+ { name: '?Instance', link: '/instance/{id}/' },
+ { name: 'Content' },
+ ],
+ },
+ },
+ {
+ path: 'logs',
+ name: 'Logs',
+ component: Instance.Logs,
+ meta: {
+ useRootContext: true,
+ breadcrumb: [
+ { name: '?Instance', link: '/instance/{id}/' },
+ { name: 'Logs' },
+ ],
+ },
+ },
+ ],
},
- ],
+ ],
+ linkActiveClass: 'router-link-active',
+ linkExactActiveClass: 'router-link-exact-active',
+ scrollBehavior() {
+ // Sometimes Vue's scroll behavior is not working as expected, so we need to manually scroll to top (especially on Linux)
+ document.querySelector('.app-viewport')?.scrollTo(0, 0)
+ return {
+ el: '.app-viewport',
+ top: 0,
+ }
},
- ],
- linkActiveClass: 'router-link-active',
- linkExactActiveClass: 'router-link-exact-active',
- scrollBehavior() {
- // Sometimes Vue's scroll behavior is not working as expected, so we need to manually scroll to top (especially on Linux)
- document.querySelector('.app-viewport')?.scrollTo(0, 0)
- return {
- el: '.app-viewport',
- top: 0,
- }
- },
})
diff --git a/apps/app-frontend/src/store/breadcrumbs.js b/apps/app-frontend/src/store/breadcrumbs.js
index da07d41490..820d365935 100644
--- a/apps/app-frontend/src/store/breadcrumbs.js
+++ b/apps/app-frontend/src/store/breadcrumbs.js
@@ -1,37 +1,37 @@
import { defineStore } from 'pinia'
export const useBreadcrumbs = defineStore('breadcrumbsStore', {
- state: () => ({
- names: new Map(),
- context: null,
- rootContext: null,
- }),
- actions: {
- getName(route) {
- return this.names.get(route) ?? ''
+ state: () => ({
+ names: new Map(),
+ context: null,
+ rootContext: null,
+ }),
+ actions: {
+ getName(route) {
+ return this.names.get(route) ?? ''
+ },
+ setName(route, title) {
+ this.names.set(route, title)
+ },
+ // resets breadcrumbs to only included ones as to not have stale breadcrumbs
+ resetToNames(breadcrumbs) {
+ if (!breadcrumbs) return
+ // names is an array of every breadcrumb.name that starts with a ?
+ const names = breadcrumbs
+ .filter((breadcrumb) => breadcrumb.name.charAt(0) === '?')
+ .map((breadcrumb) => breadcrumb.name.slice(1))
+ // remove all names that are not in the names array
+ for (const [route] of this.names) {
+ if (!names.includes(route)) {
+ this.names.delete(route)
+ }
+ }
+ },
+ setContext(context) {
+ this.context = context
+ },
+ setRootContext(context) {
+ this.rootContext = context
+ },
},
- setName(route, title) {
- this.names.set(route, title)
- },
- // resets breadcrumbs to only included ones as to not have stale breadcrumbs
- resetToNames(breadcrumbs) {
- if (!breadcrumbs) return
- // names is an array of every breadcrumb.name that starts with a ?
- const names = breadcrumbs
- .filter((breadcrumb) => breadcrumb.name.charAt(0) === '?')
- .map((breadcrumb) => breadcrumb.name.slice(1))
- // remove all names that are not in the names array
- for (const [route] of this.names) {
- if (!names.includes(route)) {
- this.names.delete(route)
- }
- }
- },
- setContext(context) {
- this.context = context
- },
- setRootContext(context) {
- this.rootContext = context
- },
- },
})
diff --git a/apps/app-frontend/src/store/error.js b/apps/app-frontend/src/store/error.js
index 6c3c0c12e2..59d85df122 100644
--- a/apps/app-frontend/src/store/error.js
+++ b/apps/app-frontend/src/store/error.js
@@ -1,21 +1,21 @@
import { defineStore } from 'pinia'
export const useError = defineStore('errorsStore', {
- state: () => ({
- errorModal: null,
- }),
- actions: {
- setErrorModal(ref) {
- this.errorModal = ref
+ state: () => ({
+ errorModal: null,
+ }),
+ actions: {
+ setErrorModal(ref) {
+ this.errorModal = ref
+ },
+ showError(error, context, closable = true, source = null) {
+ this.errorModal.show(error, context, closable, source)
+ },
},
- showError(error, context, closable = true, source = null) {
- this.errorModal.show(error, context, closable, source)
- },
- },
})
export const handleSevereError = (err, context) => {
- const error = useError()
- error.showError(err, context)
- console.error(err)
+ const error = useError()
+ error.showError(err, context)
+ console.error(err)
}
diff --git a/apps/app-frontend/src/store/install.js b/apps/app-frontend/src/store/install.js
index 034e30a29c..c4c8de7c1e 100644
--- a/apps/app-frontend/src/store/install.js
+++ b/apps/app-frontend/src/store/install.js
@@ -1,201 +1,210 @@
+import dayjs from 'dayjs'
import { defineStore } from 'pinia'
+
+import { trackEvent } from '@/helpers/analytics.js'
+import { get_project, get_version_many } from '@/helpers/cache.js'
+import { create_profile_and_install as packInstall } from '@/helpers/pack.js'
import {
- add_project_from_version,
- check_installed,
- get,
- get_projects,
- list,
- remove_project,
+ add_project_from_version,
+ check_installed,
+ get,
+ get_projects,
+ list,
+ remove_project,
} from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
-import { get_project, get_version_many } from '@/helpers/cache.js'
-import { create_profile_and_install as packInstall } from '@/helpers/pack.js'
-import { trackEvent } from '@/helpers/analytics.js'
-import dayjs from 'dayjs'
export const useInstall = defineStore('installStore', {
- state: () => ({
- installConfirmModal: null,
- modInstallModal: null,
- incompatibilityWarningModal: null,
- }),
- actions: {
- setInstallConfirmModal(ref) {
- this.installConfirmModal = ref
- },
- showInstallConfirmModal(project, version_id, onInstall, createInstanceCallback) {
- this.installConfirmModal.show(project, version_id, onInstall, createInstanceCallback)
- },
- setIncompatibilityWarningModal(ref) {
- this.incompatibilityWarningModal = ref
+ state: () => ({
+ installConfirmModal: null,
+ modInstallModal: null,
+ incompatibilityWarningModal: null,
+ }),
+ actions: {
+ setInstallConfirmModal(ref) {
+ this.installConfirmModal = ref
+ },
+ showInstallConfirmModal(project, version_id, onInstall, createInstanceCallback) {
+ this.installConfirmModal.show(project, version_id, onInstall, createInstanceCallback)
+ },
+ setIncompatibilityWarningModal(ref) {
+ this.incompatibilityWarningModal = ref
+ },
+ showIncompatibilityWarningModal(instance, project, versions, selected, onInstall) {
+ this.incompatibilityWarningModal.show(instance, project, versions, selected, onInstall)
+ },
+ setModInstallModal(ref) {
+ this.modInstallModal = ref
+ },
+ showModInstallModal(project, versions, onInstall) {
+ this.modInstallModal.show(project, versions, onInstall)
+ },
},
- showIncompatibilityWarningModal(instance, project, versions, selected, onInstall) {
- this.incompatibilityWarningModal.show(instance, project, versions, selected, onInstall)
- },
- setModInstallModal(ref) {
- this.modInstallModal = ref
- },
- showModInstallModal(project, versions, onInstall) {
- this.modInstallModal.show(project, versions, onInstall)
- },
- },
})
export const install = async (
- projectId,
- versionId,
- instancePath,
- source,
- callback = () => {},
- createInstanceCallback = () => {},
+ projectId,
+ versionId,
+ instancePath,
+ source,
+ callback = () => {},
+ createInstanceCallback = () => {},
) => {
- const project = await get_project(projectId, 'must_revalidate').catch(handleError)
-
- if (project.project_type === 'modpack') {
- const version = versionId ?? project.versions[project.versions.length - 1]
- const packs = await list().catch(handleError)
-
- if (packs.length === 0 || !packs.find((pack) => pack.linked_data?.project_id === project.id)) {
- await packInstall(
- project.id,
- version,
- project.title,
- project.icon_url,
- createInstanceCallback,
- ).catch(handleError)
-
- trackEvent('PackInstall', {
- id: project.id,
- version_id: version,
- title: project.title,
- source,
- })
-
- callback(version)
- } else {
- const install = useInstall()
- install.showInstallConfirmModal(project, version, callback, createInstanceCallback)
- }
- } else {
- if (instancePath) {
- const [instance, instanceProjects, versions] = await Promise.all([
- await get(instancePath).catch(handleError),
- await get_projects(instancePath).catch(handleError),
- await get_version_many(project.versions, 'must_revalidate'),
- ])
-
- const projectVersions = versions.sort(
- (a, b) => dayjs(b.date_published) - dayjs(a.date_published),
- )
-
- let version
- if (versionId) {
- version = projectVersions.find((x) => x.id === versionId)
- } else {
- version = projectVersions.find(
- (v) =>
- v.game_versions.includes(instance.game_version) &&
- (project.project_type === 'mod'
- ? v.loaders.includes(instance.loader) || v.loaders.includes('minecraft')
- : true),
- )
- }
-
- if (!version) {
- version = projectVersions[0]
- }
-
- if (
- version.game_versions.includes(instance.game_version) &&
- (project.project_type === 'mod'
- ? version.loaders.includes(instance.loader) || version.loaders.includes('minecraft')
- : true)
- ) {
- for (const [path, file] of Object.entries(instanceProjects)) {
- if (file.metadata && file.metadata.project_id === project.id) {
- await remove_project(instance.path, path)
- }
+ const project = await get_project(projectId, 'must_revalidate').catch(handleError)
+
+ if (project.project_type === 'modpack') {
+ const version = versionId ?? project.versions[project.versions.length - 1]
+ const packs = await list().catch(handleError)
+
+ if (
+ packs.length === 0 ||
+ !packs.find((pack) => pack.linked_data?.project_id === project.id)
+ ) {
+ await packInstall(
+ project.id,
+ version,
+ project.title,
+ project.icon_url,
+ createInstanceCallback,
+ ).catch(handleError)
+
+ trackEvent('PackInstall', {
+ id: project.id,
+ version_id: version,
+ title: project.title,
+ source,
+ })
+
+ callback(version)
+ } else {
+ const install = useInstall()
+ install.showInstallConfirmModal(project, version, callback, createInstanceCallback)
}
-
- await add_project_from_version(instance.path, version.id).catch(handleError)
- await installVersionDependencies(instance, version)
-
- trackEvent('ProjectInstall', {
- loader: instance.loader,
- game_version: instance.game_version,
- id: project.id,
- project_type: project.project_type,
- version_id: version.id,
- title: project.title,
- source,
- })
-
- callback(version.id)
- } else {
- const install = useInstall()
- install.showIncompatibilityWarningModal(
- instance,
- project,
- projectVersions,
- version,
- callback,
- )
- }
} else {
- const versions = (await get_version_many(project.versions).catch(handleError)).sort(
- (a, b) => dayjs(b.date_published) - dayjs(a.date_published),
- )
-
- const install = useInstall()
- install.showModInstallModal(project, versions, callback)
+ if (instancePath) {
+ const [instance, instanceProjects, versions] = await Promise.all([
+ await get(instancePath).catch(handleError),
+ await get_projects(instancePath).catch(handleError),
+ await get_version_many(project.versions, 'must_revalidate'),
+ ])
+
+ const projectVersions = versions.sort(
+ (a, b) => dayjs(b.date_published) - dayjs(a.date_published),
+ )
+
+ let version
+ if (versionId) {
+ version = projectVersions.find((x) => x.id === versionId)
+ } else {
+ version = projectVersions.find(
+ (v) =>
+ v.game_versions.includes(instance.game_version) &&
+ (project.project_type === 'mod'
+ ? v.loaders.includes(instance.loader) || v.loaders.includes('minecraft')
+ : true),
+ )
+ }
+
+ if (!version) {
+ version = projectVersions[0]
+ }
+
+ if (
+ version.game_versions.includes(instance.game_version) &&
+ (project.project_type === 'mod'
+ ? version.loaders.includes(instance.loader) ||
+ version.loaders.includes('minecraft')
+ : true)
+ ) {
+ for (const [path, file] of Object.entries(instanceProjects)) {
+ if (file.metadata && file.metadata.project_id === project.id) {
+ await remove_project(instance.path, path)
+ }
+ }
+
+ await add_project_from_version(instance.path, version.id).catch(handleError)
+ await installVersionDependencies(instance, version)
+
+ trackEvent('ProjectInstall', {
+ loader: instance.loader,
+ game_version: instance.game_version,
+ id: project.id,
+ project_type: project.project_type,
+ version_id: version.id,
+ title: project.title,
+ source,
+ })
+
+ callback(version.id)
+ } else {
+ const install = useInstall()
+ install.showIncompatibilityWarningModal(
+ instance,
+ project,
+ projectVersions,
+ version,
+ callback,
+ )
+ }
+ } else {
+ const versions = (await get_version_many(project.versions).catch(handleError)).sort(
+ (a, b) => dayjs(b.date_published) - dayjs(a.date_published),
+ )
+
+ const install = useInstall()
+ install.showModInstallModal(project, versions, callback)
+ }
}
- }
-
- // If project is modpack:
- // - We check all available instances if modpack is already installed
- // If true: show confirmation modal
- // If false: install it (latest version if passed version is null)
- // If project is mod:
- // - If instance is selected:
- // - If project is already installed
- // We first uninstall the project
- // - If no version is selected, we look check the instance for versions to select based on the versions
- // - If there are no versions, we show the incompat modal
- // - If a version is selected, and the version is incompatible, we show the incompat modal
- // - Version is inarlled, as well as version dependencies
+
+ // If project is modpack:
+ // - We check all available instances if modpack is already installed
+ // If true: show confirmation modal
+ // If false: install it (latest version if passed version is null)
+ // If project is mod:
+ // - If instance is selected:
+ // - If project is already installed
+ // We first uninstall the project
+ // - If no version is selected, we look check the instance for versions to select based on the versions
+ // - If there are no versions, we show the incompat modal
+ // - If a version is selected, and the version is incompatible, we show the incompat modal
+ // - Version is inarlled, as well as version dependencies
}
export const installVersionDependencies = async (profile, version) => {
- for (const dep of version.dependencies) {
- if (dep.dependency_type !== 'required') continue
- // disallow fabric api install on quilt
- if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue
- if (dep.version_id) {
- if (
- dep.project_id &&
- (await check_installed(profile.path, dep.project_id).catch(handleError))
- )
- continue
- await add_project_from_version(profile.path, dep.version_id)
- } else {
- if (
- dep.project_id &&
- (await check_installed(profile.path, dep.project_id).catch(handleError))
- )
- continue
-
- const depProject = await get_project(dep.project_id, 'must_revalidate').catch(handleError)
-
- const depVersions = (
- await get_version_many(depProject.versions, 'must_revalidate').catch(handleError)
- ).sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published))
-
- const latest = depVersions.find(
- (v) => v.game_versions.includes(profile.game_version) && v.loaders.includes(profile.loader),
- )
- if (latest) {
- await add_project_from_version(profile.path, latest.id).catch(handleError)
- }
+ for (const dep of version.dependencies) {
+ if (dep.dependency_type !== 'required') continue
+ // disallow fabric api install on quilt
+ if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue
+ if (dep.version_id) {
+ if (
+ dep.project_id &&
+ (await check_installed(profile.path, dep.project_id).catch(handleError))
+ )
+ continue
+ await add_project_from_version(profile.path, dep.version_id)
+ } else {
+ if (
+ dep.project_id &&
+ (await check_installed(profile.path, dep.project_id).catch(handleError))
+ )
+ continue
+
+ const depProject = await get_project(dep.project_id, 'must_revalidate').catch(
+ handleError,
+ )
+
+ const depVersions = (
+ await get_version_many(depProject.versions, 'must_revalidate').catch(handleError)
+ ).sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published))
+
+ const latest = depVersions.find(
+ (v) =>
+ v.game_versions.includes(profile.game_version) &&
+ v.loaders.includes(profile.loader),
+ )
+ if (latest) {
+ await add_project_from_version(profile.path, latest.id).catch(handleError)
+ }
+ }
}
- }
}
diff --git a/apps/app-frontend/src/store/loading.js b/apps/app-frontend/src/store/loading.js
index 8c300a1f0d..9212e3b4c2 100644
--- a/apps/app-frontend/src/store/loading.js
+++ b/apps/app-frontend/src/store/loading.js
@@ -1,19 +1,19 @@
import { defineStore } from 'pinia'
export const useLoading = defineStore('loadingStore', {
- state: () => ({
- loading: false,
- barEnabled: false,
- }),
- actions: {
- setEnabled(enabled) {
- this.barEnabled = enabled
+ state: () => ({
+ loading: false,
+ barEnabled: false,
+ }),
+ actions: {
+ setEnabled(enabled) {
+ this.barEnabled = enabled
+ },
+ startLoading() {
+ this.loading = true
+ },
+ stopLoading() {
+ this.loading = false
+ },
},
- startLoading() {
- this.loading = true
- },
- stopLoading() {
- this.loading = false
- },
- },
})
diff --git a/apps/app-frontend/src/store/notifications.js b/apps/app-frontend/src/store/notifications.js
index 52cc534762..e789b2e2b3 100644
--- a/apps/app-frontend/src/store/notifications.js
+++ b/apps/app-frontend/src/store/notifications.js
@@ -1,25 +1,25 @@
import { defineStore } from 'pinia'
export const useNotifications = defineStore('notificationsStore', {
- state: () => ({
- notificationsWrapper: null,
- }),
- actions: {
- setNotifs(notifs) {
- this.notificationsWrapper = notifs
+ state: () => ({
+ notificationsWrapper: null,
+ }),
+ actions: {
+ setNotifs(notifs) {
+ this.notificationsWrapper = notifs
+ },
+ addNotification(notif) {
+ this.notificationsWrapper.addNotification(notif)
+ },
},
- addNotification(notif) {
- this.notificationsWrapper.addNotification(notif)
- },
- },
})
export const handleError = (err) => {
- const notifs = useNotifications()
- notifs.addNotification({
- title: 'An error occurred',
- text: err.message ?? err,
- type: 'error',
- })
- console.error(err)
+ const notifs = useNotifications()
+ notifs.addNotification({
+ title: 'An error occurred',
+ text: err.message ?? err,
+ type: 'error',
+ })
+ console.error(err)
}
diff --git a/apps/app-frontend/src/store/state.js b/apps/app-frontend/src/store/state.js
index e9811e6238..08172fc1cb 100644
--- a/apps/app-frontend/src/store/state.js
+++ b/apps/app-frontend/src/store/state.js
@@ -1,7 +1,7 @@
-import { useTheming } from './theme.ts'
import { useBreadcrumbs } from './breadcrumbs'
-import { useLoading } from './loading'
-import { useNotifications, handleError } from './notifications'
import { useInstall } from './install'
+import { useLoading } from './loading'
+import { handleError, useNotifications } from './notifications'
+import { useTheming } from './theme.ts'
-export { useTheming, useBreadcrumbs, useLoading, useNotifications, handleError, useInstall }
+export { handleError, useBreadcrumbs, useInstall, useLoading, useNotifications, useTheming }
diff --git a/apps/app-frontend/src/store/theme.ts b/apps/app-frontend/src/store/theme.ts
index a79094167b..269a8681fa 100644
--- a/apps/app-frontend/src/store/theme.ts
+++ b/apps/app-frontend/src/store/theme.ts
@@ -1,10 +1,10 @@
import { defineStore } from 'pinia'
export const DEFAULT_FEATURE_FLAGS = {
- project_background: false,
- page_path: false,
- worlds_tab: false,
- worlds_in_home: true,
+ project_background: false,
+ page_path: false,
+ worlds_tab: false,
+ worlds_in_home: true,
}
export const THEME_OPTIONS = ['dark', 'light', 'oled', 'system'] as const
@@ -14,57 +14,57 @@ export type FeatureFlags = Record
export type ColorTheme = (typeof THEME_OPTIONS)[number]
export type ThemeStore = {
- selectedTheme: ColorTheme
- advancedRendering: boolean
- toggleSidebar: boolean
+ selectedTheme: ColorTheme
+ advancedRendering: boolean
+ toggleSidebar: boolean
- devMode: boolean
- featureFlags: FeatureFlags
+ devMode: boolean
+ featureFlags: FeatureFlags
}
export const DEFAULT_THEME_STORE: ThemeStore = {
- selectedTheme: 'dark',
- advancedRendering: true,
- toggleSidebar: false,
+ selectedTheme: 'dark',
+ advancedRendering: true,
+ toggleSidebar: false,
- devMode: false,
- featureFlags: DEFAULT_FEATURE_FLAGS,
+ devMode: false,
+ featureFlags: DEFAULT_FEATURE_FLAGS,
}
export const useTheming = defineStore('themeStore', {
- state: () => DEFAULT_THEME_STORE,
- actions: {
- setThemeState(newTheme: ColorTheme) {
- if (THEME_OPTIONS.includes(newTheme)) {
- this.selectedTheme = newTheme
- } else {
- console.warn('Selected theme is not present. Check themeOptions.')
- }
+ state: () => DEFAULT_THEME_STORE,
+ actions: {
+ setThemeState(newTheme: ColorTheme) {
+ if (THEME_OPTIONS.includes(newTheme)) {
+ this.selectedTheme = newTheme
+ } else {
+ console.warn('Selected theme is not present. Check themeOptions.')
+ }
- this.setThemeClass()
- },
- setThemeClass() {
- for (const theme of THEME_OPTIONS) {
- document.getElementsByTagName('html')[0].classList.remove(`${theme}-mode`)
- }
+ this.setThemeClass()
+ },
+ setThemeClass() {
+ for (const theme of THEME_OPTIONS) {
+ document.getElementsByTagName('html')[0].classList.remove(`${theme}-mode`)
+ }
- let theme = this.selectedTheme
- if (this.selectedTheme === 'system') {
- const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
- if (darkThemeMq.matches) {
- theme = 'dark'
- } else {
- theme = 'light'
- }
- }
+ let theme = this.selectedTheme
+ if (this.selectedTheme === 'system') {
+ const darkThemeMq = window.matchMedia('(prefers-color-scheme: dark)')
+ if (darkThemeMq.matches) {
+ theme = 'dark'
+ } else {
+ theme = 'light'
+ }
+ }
- document.getElementsByTagName('html')[0].classList.add(`${theme}-mode`)
- },
- getFeatureFlag(key: FeatureFlag) {
- return this.featureFlags[key] ?? DEFAULT_FEATURE_FLAGS[key]
- },
- getThemeOptions() {
- return THEME_OPTIONS
+ document.getElementsByTagName('html')[0].classList.add(`${theme}-mode`)
+ },
+ getFeatureFlag(key: FeatureFlag) {
+ return this.featureFlags[key] ?? DEFAULT_FEATURE_FLAGS[key]
+ },
+ getThemeOptions() {
+ return THEME_OPTIONS
+ },
},
- },
})
diff --git a/apps/app-frontend/tailwind.config.js b/apps/app-frontend/tailwind.config.js
index b5196b3682..b5f4012af9 100644
--- a/apps/app-frontend/tailwind.config.js
+++ b/apps/app-frontend/tailwind.config.js
@@ -1,153 +1,153 @@
/** @type {import('tailwindcss').Config} */
export default {
- content: [
- './src/components/**/*.{js,vue,ts}',
- './src/layouts/**/*.vue',
- './src/pages/**/*.vue',
- './src/plugins/**/*.{js,ts}',
- './src/App.vue',
- './src/error.vue',
- // monorepo - TODO: migrate this to its own package
- '../../packages/**/*.{js,vue,ts}',
- ],
- theme: {
- extend: {
- colors: {
- icon: 'var(--color-base)',
- // Text
- primary: 'var(--color-base)',
- contrast: 'var(--color-contrast)',
- secondary: 'var(--color-secondary)',
- inactive: 'var(--color-text-inactive)',
- dark: 'var(--color-text-dark)',
- inverted: 'var(--color-text-inverted)',
- heading: 'var(--color-heading)',
- red: 'var(--color-red)',
- orange: 'var(--color-orange)',
- purple: 'var(--color-purple)',
- bg: {
- DEFAULT: 'var(--color-bg)',
- red: 'var(--color-red-bg)',
- orange: 'var(--color-orange-bg)',
- green: 'var(--color-green-bg)',
- blue: 'var(--color-blue-bg)',
- purple: 'var(--color-purple-bg)',
- raised: 'var(--color-raised-bg)',
+ content: [
+ './src/components/**/*.{js,vue,ts}',
+ './src/layouts/**/*.vue',
+ './src/pages/**/*.vue',
+ './src/plugins/**/*.{js,ts}',
+ './src/App.vue',
+ './src/error.vue',
+ // monorepo - TODO: migrate this to its own package
+ '../../packages/**/*.{js,vue,ts}',
+ ],
+ theme: {
+ extend: {
+ colors: {
+ icon: 'var(--color-base)',
+ // Text
+ primary: 'var(--color-base)',
+ contrast: 'var(--color-contrast)',
+ secondary: 'var(--color-secondary)',
+ inactive: 'var(--color-text-inactive)',
+ dark: 'var(--color-text-dark)',
+ inverted: 'var(--color-text-inverted)',
+ heading: 'var(--color-heading)',
+ red: 'var(--color-red)',
+ orange: 'var(--color-orange)',
+ purple: 'var(--color-purple)',
+ bg: {
+ DEFAULT: 'var(--color-bg)',
+ red: 'var(--color-red-bg)',
+ orange: 'var(--color-orange-bg)',
+ green: 'var(--color-green-bg)',
+ blue: 'var(--color-blue-bg)',
+ purple: 'var(--color-purple-bg)',
+ raised: 'var(--color-raised-bg)',
+ },
+ highlight: {
+ DEFAULT: 'var(--color-brand-highlight)',
+ red: 'var(--color-red-highlight)',
+ orange: 'var(--color-orange-highlight)',
+ green: 'var(--color-green-highlight)',
+ blue: 'var(--color-blue-highlight)',
+ purple: 'var(--color-purple-highlight)',
+ gray: 'var(--color-gray-highlight)',
+ },
+ divider: {
+ DEFAULT: 'var(--color-divider)',
+ dark: 'var(--color-divider-dark)',
+ },
+ brand: {
+ DEFAULT: 'var(--color-brand)',
+ red: 'var(--color-red)',
+ orange: 'var(--color-orange)',
+ green: 'var(--color-green)',
+ blue: 'var(--color-blue)',
+ purple: 'var(--color-purple)',
+ highlight: 'var(--color-brand-highlight)',
+ shadow: 'var(--color-brand-shadow)',
+ inverted: 'var(--color-accent-contrast)',
+ },
+ tabUnderlineHovered: 'var(--tab-underline-hovered)',
+ button: {
+ bg: 'var(--color-button-bg)',
+ text: 'var(--color-button-text)',
+ bgHover: 'var(--color-button-bg-hover)',
+ textHover: 'var(--color-button-text-hover)',
+ bgActive: 'var(--color-button-bg-active)',
+ textActive: 'var(--color-button-text-active)',
+ border: 'var(--color-button-border)',
+ bgSelected: 'var(--color-button-bg-selected)',
+ textSelected: 'var(--color-button-text-selected)',
+ },
+ toggleHandle: 'var(--color-toggle-handle)',
+ dropdown: {
+ bg: 'var(--color-dropdown-bg)',
+ text: 'var(--color-dropdown-text)',
+ },
+ tooltip: {
+ bg: 'var(--color-tooltip-bg)',
+ text: 'var(--color-tooltip-text)',
+ },
+ code: {
+ bg: 'var(--color-code-bg)',
+ text: 'var(--color-code-text)',
+ },
+ kbdShadow: 'var(--color-kbd-shadow)',
+ ad: {
+ DEFAULT: 'var(--color-ad)',
+ raised: 'var(--color-ad-raised)',
+ contrast: 'var(--color-ad-contrast)',
+ highlight: 'var(--color-ad-highlight)',
+ },
+ greyLink: {
+ DEFAULT: 'var(--color-grey-link)',
+ hover: 'var(--color-grey-link-hover)',
+ active: 'var(--color-grey-link-active)',
+ },
+ link: {
+ DEFAULT: 'var(--color-link)',
+ hover: 'var(--color-link-hover)',
+ active: 'var(--color-link-active)',
+ },
+ warning: {
+ bg: 'var(--color-warning-bg)',
+ text: 'var(--color-warning-text)',
+ banner: {
+ text: 'var(--color-warning-banner-text)',
+ bg: 'var(--color-warning-banner-bg)',
+ side: 'var(--color-warning-banner-side)',
+ },
+ },
+ infoBanner: {
+ text: 'var(--color-info-banner-text)',
+ bg: 'var(--color-info-banner-bg)',
+ side: 'var(--color-info-banner-side)',
+ },
+ blockQuote: 'var(--color-block-quote)',
+ headerUnderline: 'var(--color-header-underline)',
+ hr: 'var(--color-hr)',
+ table: {
+ border: 'var(--color-table-border)',
+ alternateRow: ' var(--color-table-alternate-row)',
+ },
+ },
+ backgroundImage: {
+ mazeBg: 'var(--landing-maze-bg)',
+ mazeGradientBg: 'var(--landing-maze-gradient-bg)',
+ landing: {
+ mazeOuterBg: 'var(--landing-maze-outer-bg)',
+ colorHeading: 'var(--landing-color-heading)',
+ colorSubheading: 'var(--landing-color-subheading)',
+ transitionGradientStart: 'var(--landing-transition-gradient-start)',
+ transitionGradientEnd: 'var(--landing-transition-gradient-end)',
+ hoverCardGradient: 'var(--landing-hover-card-gradient)',
+ borderGradient: 'var(--landing-border-gradient)',
+ borderColor: 'var(--landing-border-color)',
+ creatorGradient: 'var(--landing-creator-gradient)',
+ blobGradient: 'var(--landing-blob-gradient)',
+ cardBg: 'var(--landing-card-bg)',
+ blueLabel: 'var(--landing-blue-label)',
+ blueLabelBg: 'var(--landing-blue-label-bg)',
+ greenLabel: 'var(--landing-green-label)',
+ greenLabelBg: 'var(--landing-green-label-bg)',
+ rawBg: 'var(--landing-raw-bg)',
+ },
+ },
},
- highlight: {
- DEFAULT: 'var(--color-brand-highlight)',
- red: 'var(--color-red-highlight)',
- orange: 'var(--color-orange-highlight)',
- green: 'var(--color-green-highlight)',
- blue: 'var(--color-blue-highlight)',
- purple: 'var(--color-purple-highlight)',
- gray: 'var(--color-gray-highlight)',
- },
- divider: {
- DEFAULT: 'var(--color-divider)',
- dark: 'var(--color-divider-dark)',
- },
- brand: {
- DEFAULT: 'var(--color-brand)',
- red: 'var(--color-red)',
- orange: 'var(--color-orange)',
- green: 'var(--color-green)',
- blue: 'var(--color-blue)',
- purple: 'var(--color-purple)',
- highlight: 'var(--color-brand-highlight)',
- shadow: 'var(--color-brand-shadow)',
- inverted: 'var(--color-accent-contrast)',
- },
- tabUnderlineHovered: 'var(--tab-underline-hovered)',
- button: {
- bg: 'var(--color-button-bg)',
- text: 'var(--color-button-text)',
- bgHover: 'var(--color-button-bg-hover)',
- textHover: 'var(--color-button-text-hover)',
- bgActive: 'var(--color-button-bg-active)',
- textActive: 'var(--color-button-text-active)',
- border: 'var(--color-button-border)',
- bgSelected: 'var(--color-button-bg-selected)',
- textSelected: 'var(--color-button-text-selected)',
- },
- toggleHandle: 'var(--color-toggle-handle)',
- dropdown: {
- bg: 'var(--color-dropdown-bg)',
- text: 'var(--color-dropdown-text)',
- },
- tooltip: {
- bg: 'var(--color-tooltip-bg)',
- text: 'var(--color-tooltip-text)',
- },
- code: {
- bg: 'var(--color-code-bg)',
- text: 'var(--color-code-text)',
- },
- kbdShadow: 'var(--color-kbd-shadow)',
- ad: {
- DEFAULT: 'var(--color-ad)',
- raised: 'var(--color-ad-raised)',
- contrast: 'var(--color-ad-contrast)',
- highlight: 'var(--color-ad-highlight)',
- },
- greyLink: {
- DEFAULT: 'var(--color-grey-link)',
- hover: 'var(--color-grey-link-hover)',
- active: 'var(--color-grey-link-active)',
- },
- link: {
- DEFAULT: 'var(--color-link)',
- hover: 'var(--color-link-hover)',
- active: 'var(--color-link-active)',
- },
- warning: {
- bg: 'var(--color-warning-bg)',
- text: 'var(--color-warning-text)',
- banner: {
- text: 'var(--color-warning-banner-text)',
- bg: 'var(--color-warning-banner-bg)',
- side: 'var(--color-warning-banner-side)',
- },
- },
- infoBanner: {
- text: 'var(--color-info-banner-text)',
- bg: 'var(--color-info-banner-bg)',
- side: 'var(--color-info-banner-side)',
- },
- blockQuote: 'var(--color-block-quote)',
- headerUnderline: 'var(--color-header-underline)',
- hr: 'var(--color-hr)',
- table: {
- border: 'var(--color-table-border)',
- alternateRow: ' var(--color-table-alternate-row)',
- },
- },
- backgroundImage: {
- mazeBg: 'var(--landing-maze-bg)',
- mazeGradientBg: 'var(--landing-maze-gradient-bg)',
- landing: {
- mazeOuterBg: 'var(--landing-maze-outer-bg)',
- colorHeading: 'var(--landing-color-heading)',
- colorSubheading: 'var(--landing-color-subheading)',
- transitionGradientStart: 'var(--landing-transition-gradient-start)',
- transitionGradientEnd: 'var(--landing-transition-gradient-end)',
- hoverCardGradient: 'var(--landing-hover-card-gradient)',
- borderGradient: 'var(--landing-border-gradient)',
- borderColor: 'var(--landing-border-color)',
- creatorGradient: 'var(--landing-creator-gradient)',
- blobGradient: 'var(--landing-blob-gradient)',
- cardBg: 'var(--landing-card-bg)',
- blueLabel: 'var(--landing-blue-label)',
- blueLabelBg: 'var(--landing-blue-label-bg)',
- greenLabel: 'var(--landing-green-label)',
- greenLabelBg: 'var(--landing-green-label-bg)',
- rawBg: 'var(--landing-raw-bg)',
- },
- },
},
- },
- plugins: [],
- corePlugins: {
- preflight: false,
- },
+ plugins: [],
+ corePlugins: {
+ preflight: false,
+ },
}
diff --git a/apps/app-frontend/vite.config.ts b/apps/app-frontend/vite.config.ts
index 8adf5fb2d0..ee7799a0db 100644
--- a/apps/app-frontend/vite.config.ts
+++ b/apps/app-frontend/vite.config.ts
@@ -1,76 +1,75 @@
+import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { defineConfig } from 'vite'
import svgLoader from 'vite-svg-loader'
-import vue from '@vitejs/plugin-vue'
-
import tauriConf from '../app/tauri.conf.json'
const projectRootDir = resolve(__dirname)
// https://vitejs.dev/config/
export default defineConfig({
- resolve: {
- alias: [
- {
- find: '@',
- replacement: resolve(projectRootDir, 'src'),
- },
- ],
- },
- plugins: [
- vue(),
- svgLoader({
- svgoConfig: {
- plugins: [
- {
- name: 'preset-default',
- params: {
- overrides: {
- removeViewBox: false,
- },
+ resolve: {
+ alias: [
+ {
+ find: '@',
+ replacement: resolve(projectRootDir, 'src'),
},
- },
],
- },
- }),
- ],
+ },
+ plugins: [
+ vue(),
+ svgLoader({
+ svgoConfig: {
+ plugins: [
+ {
+ name: 'preset-default',
+ params: {
+ overrides: {
+ removeViewBox: false,
+ },
+ },
+ },
+ ],
+ },
+ }),
+ ],
- // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
- // prevent vite from obscuring rust errors
- clearScreen: false,
- // tauri expects a fixed port, fail if that port is not available
- server: {
- port: 1420,
- strictPort: true,
- headers: {
- 'content-security-policy': Object.entries(tauriConf.app.security.csp)
- .map(([directive, sources]) => {
- // An additional websocket connect-src is required for Vite dev tools to work
- if (directive === 'connect-src') {
- sources = Array.isArray(sources) ? sources : [sources]
- sources.push('ws://localhost:1420')
- }
+ // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
+ // prevent vite from obscuring rust errors
+ clearScreen: false,
+ // tauri expects a fixed port, fail if that port is not available
+ server: {
+ port: 1420,
+ strictPort: true,
+ headers: {
+ 'content-security-policy': Object.entries(tauriConf.app.security.csp)
+ .map(([directive, sources]) => {
+ // An additional websocket connect-src is required for Vite dev tools to work
+ if (directive === 'connect-src') {
+ sources = Array.isArray(sources) ? sources : [sources]
+ sources.push('ws://localhost:1420')
+ }
- return Array.isArray(sources)
- ? `${directive} ${sources.join(' ')}`
- : `${directive} ${sources}`
- })
- .join('; '),
+ return Array.isArray(sources)
+ ? `${directive} ${sources.join(' ')}`
+ : `${directive} ${sources}`
+ })
+ .join('; '),
+ },
},
- },
- // to make use of `TAURI_ENV_DEBUG` and other env variables
- // https://v2.tauri.app/reference/environment-variables/#tauri-cli-hook-commands
- envPrefix: ['VITE_', 'TAURI_'],
- build: {
- // Tauri supports es2021
- target: process.env.TAURI_ENV_PLATFORM == 'windows' ? 'chrome105' : 'safari13', // eslint-disable-line turbo/no-undeclared-env-vars
- // don't minify for debug builds
- minify: !process.env.TAURI_ENV_DEBUG ? 'esbuild' : false, // eslint-disable-line turbo/no-undeclared-env-vars
- // produce sourcemaps for debug builds
- sourcemap: !!process.env.TAURI_ENV_DEBUG, // eslint-disable-line turbo/no-undeclared-env-vars
- commonjsOptions: {
- esmExternals: true,
+ // to make use of `TAURI_ENV_DEBUG` and other env variables
+ // https://v2.tauri.app/reference/environment-variables/#tauri-cli-hook-commands
+ envPrefix: ['VITE_', 'TAURI_'],
+ build: {
+ // Tauri supports es2021
+ target: process.env.TAURI_ENV_PLATFORM == 'windows' ? 'chrome105' : 'safari13', // eslint-disable-line turbo/no-undeclared-env-vars
+ // don't minify for debug builds
+ minify: !process.env.TAURI_ENV_DEBUG ? 'esbuild' : false, // eslint-disable-line turbo/no-undeclared-env-vars
+ // produce sourcemaps for debug builds
+ sourcemap: !!process.env.TAURI_ENV_DEBUG, // eslint-disable-line turbo/no-undeclared-env-vars
+ commonjsOptions: {
+ esmExternals: true,
+ },
},
- },
})
diff --git a/apps/frontend/.eslintrc.cjs b/apps/frontend/.eslintrc.cjs
deleted file mode 100644
index b0faf89489..0000000000
--- a/apps/frontend/.eslintrc.cjs
+++ /dev/null
@@ -1,7 +0,0 @@
-module.exports = {
- root: true,
- extends: ["../../packages/eslint-config-custom/nuxt.js"],
- rules: {
- "import/no-unresolved": "off",
- },
-};
diff --git a/apps/frontend/.prettierrc b/apps/frontend/.prettierrc
deleted file mode 100644
index 8c662d196c..0000000000
--- a/apps/frontend/.prettierrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "endOfLine": "auto",
- "plugins": ["prettier-plugin-tailwindcss"]
-}
diff --git a/apps/frontend/crowdin.yml b/apps/frontend/crowdin.yml
index e7c47c7daf..2817cde427 100644
--- a/apps/frontend/crowdin.yml
+++ b/apps/frontend/crowdin.yml
@@ -1,6 +1,6 @@
project_id: 518556
preserve_hierarchy: true
-commit_message: "[ci skip]"
+commit_message: '[ci skip]'
files:
- source: /locales/en-US/*
diff --git a/apps/frontend/eslint.config.mjs b/apps/frontend/eslint.config.mjs
new file mode 100644
index 0000000000..3ef5135daa
--- /dev/null
+++ b/apps/frontend/eslint.config.mjs
@@ -0,0 +1,10 @@
+import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
+export default config.append([
+ {
+ rules: {
+ '@typescript-eslint/no-explicit-any': 'off',
+ 'import/no-unresolved': 'off',
+ 'no-undef': 'off',
+ },
+ },
+])
diff --git a/apps/frontend/nuxt.config.ts b/apps/frontend/nuxt.config.ts
index 9ca6846ef8..d8c1256501 100644
--- a/apps/frontend/nuxt.config.ts
+++ b/apps/frontend/nuxt.config.ts
@@ -1,27 +1,28 @@
-import { promises as fs } from "fs";
-import { pathToFileURL } from "node:url";
-import svgLoader from "vite-svg-loader";
-import { resolve, basename, relative } from "pathe";
-import { defineNuxtConfig } from "nuxt/config";
-import { $fetch } from "ofetch";
-import { globIterate } from "glob";
-import { match as matchLocale } from "@formatjs/intl-localematcher";
-import { consola } from "consola";
-
-const STAGING_API_URL = "https://staging-api.modrinth.com/v2/";
+import { pathToFileURL } from 'node:url'
+
+import { match as matchLocale } from '@formatjs/intl-localematcher'
+import { consola } from 'consola'
+import { promises as fs } from 'fs'
+import { globIterate } from 'glob'
+import { defineNuxtConfig } from 'nuxt/config'
+import { $fetch } from 'ofetch'
+import { basename, relative, resolve } from 'pathe'
+import svgLoader from 'vite-svg-loader'
+
+const STAGING_API_URL = 'https://staging-api.modrinth.com/v2/'
const preloadedFonts = [
- "inter/Inter-Regular.woff2",
- "inter/Inter-Medium.woff2",
- "inter/Inter-SemiBold.woff2",
- "inter/Inter-Bold.woff2",
-];
+ 'inter/Inter-Regular.woff2',
+ 'inter/Inter-Medium.woff2',
+ 'inter/Inter-SemiBold.woff2',
+ 'inter/Inter-Bold.woff2',
+]
const favicons = {
- "(prefers-color-scheme:no-preference)": "/favicon-light.ico",
- "(prefers-color-scheme:light)": "/favicon-light.ico",
- "(prefers-color-scheme:dark)": "/favicon.ico",
-};
+ '(prefers-color-scheme:no-preference)': '/favicon-light.ico',
+ '(prefers-color-scheme:light)': '/favicon-light.ico',
+ '(prefers-color-scheme:dark)': '/favicon.ico',
+}
/**
* Tags of locales that are auto-discovered besides the default locale.
@@ -29,464 +30,478 @@ const favicons = {
* Preferably only the locales that reach a certain threshold of complete
* translations would be included in this array.
*/
-const enabledLocales: string[] = [];
+const enabledLocales: string[] = []
/**
* Overrides for the categories of the certain locales.
*/
-const localesCategoriesOverrides: Partial> = {
- "en-x-pirate": "fun",
- "en-x-updown": "fun",
- "en-x-lolcat": "fun",
- "en-x-uwu": "fun",
- "ru-x-bandit": "fun",
- ar: "experimental",
- he: "experimental",
- pes: "experimental",
-};
+const localesCategoriesOverrides: Partial> = {
+ 'en-x-pirate': 'fun',
+ 'en-x-updown': 'fun',
+ 'en-x-lolcat': 'fun',
+ 'en-x-uwu': 'fun',
+ 'ru-x-bandit': 'fun',
+ ar: 'experimental',
+ he: 'experimental',
+ pes: 'experimental',
+}
export default defineNuxtConfig({
- srcDir: "src/",
- app: {
- head: {
- htmlAttrs: {
- lang: "en",
- },
- title: "Modrinth",
- link: [
- // The type is necessary because the linter can't always compare this very nested/complex type on itself
- ...preloadedFonts.map((font): object => {
- return {
- rel: "preload",
- href: `https://cdn-raw.modrinth.com/fonts/${font}?v=3.19`,
- as: "font",
- type: "font/woff2",
- crossorigin: "anonymous",
- };
- }),
- ...Object.entries(favicons).map(([media, href]): object => {
- return { rel: "icon", type: "image/x-icon", href, media };
- }),
- ...Object.entries(favicons).map(([media, href]): object => {
- return { rel: "apple-touch-icon", type: "image/x-icon", href, media, sizes: "64x64" };
- }),
- {
- rel: "search",
- type: "application/opensearchdescription+xml",
- href: "/opensearch.xml",
- title: "Modrinth mods",
+ srcDir: 'src/',
+ app: {
+ head: {
+ htmlAttrs: {
+ lang: 'en',
+ },
+ title: 'Modrinth',
+ link: [
+ // The type is necessary because the linter can't always compare this very nested/complex type on itself
+ ...preloadedFonts.map((font): object => {
+ return {
+ rel: 'preload',
+ href: `https://cdn-raw.modrinth.com/fonts/${font}?v=3.19`,
+ as: 'font',
+ type: 'font/woff2',
+ crossorigin: 'anonymous',
+ }
+ }),
+ ...Object.entries(favicons).map(([media, href]): object => {
+ return { rel: 'icon', type: 'image/x-icon', href, media }
+ }),
+ ...Object.entries(favicons).map(([media, href]): object => {
+ return {
+ rel: 'apple-touch-icon',
+ type: 'image/x-icon',
+ href,
+ media,
+ sizes: '64x64',
+ }
+ }),
+ {
+ rel: 'search',
+ type: 'application/opensearchdescription+xml',
+ href: '/opensearch.xml',
+ title: 'Modrinth mods',
+ },
+ ],
},
- ],
- },
- },
- vite: {
- define: {
- global: {},
},
- esbuild: {
- define: {
- global: "globalThis",
- },
- },
- cacheDir: "../../node_modules/.vite/apps/knossos",
- resolve: {
- dedupe: ["vue"],
- },
- plugins: [
- svgLoader({
- svgoConfig: {
- plugins: [
- {
- name: "preset-default",
- params: {
- overrides: {
- removeViewBox: false,
- },
- },
+ vite: {
+ define: {
+ global: {},
+ },
+ esbuild: {
+ define: {
+ global: 'globalThis',
},
- ],
},
- }),
- ],
- },
- hooks: {
- async "build:before"() {
- // 30 minutes
- const TTL = 30 * 60 * 1000;
-
- let state: {
- lastGenerated?: string;
- apiUrl?: string;
- categories?: any[];
- loaders?: any[];
- gameVersions?: any[];
- donationPlatforms?: any[];
- reportTypes?: any[];
- homePageProjects?: any[];
- homePageSearch?: any[];
- homePageNotifs?: any[];
- products?: any[];
- errors?: number[];
- } = {};
-
- try {
- state = JSON.parse(await fs.readFile("./src/generated/state.json", "utf8"));
- } catch {
- // File doesn't exist, create folder
- await fs.mkdir("./src/generated", { recursive: true });
- }
-
- const API_URL = getApiUrl();
-
- if (
- // Skip regeneration if within TTL...
- state.lastGenerated &&
- new Date(state.lastGenerated).getTime() + TTL > new Date().getTime() &&
- // ...but only if the API URL is the same
- state.apiUrl === API_URL &&
- // ...and if no errors were caught during the last generation
- (state.errors ?? []).length === 0
- ) {
- console.log(
- "Tags already recently generated. Delete apps/frontend/generated/state.json to force regeneration.",
- );
- return;
- }
-
- state.lastGenerated = new Date().toISOString();
-
- state.apiUrl = API_URL;
-
- const headers = {
- headers: {
- "user-agent": "Knossos generator (support@modrinth.com)",
+ cacheDir: '../../node_modules/.vite/apps/knossos',
+ resolve: {
+ dedupe: ['vue'],
},
- };
-
- const caughtErrorCodes = new Set();
-
- function handleFetchError(err: any, defaultValue: any) {
- console.error("Error generating state: ", err);
- caughtErrorCodes.add(err.status);
- return defaultValue;
- }
-
- const [
- categories,
- loaders,
- gameVersions,
- donationPlatforms,
- reportTypes,
- homePageProjects,
- homePageSearch,
- homePageNotifs,
- products,
- ] = await Promise.all([
- $fetch(`${API_URL}tag/category`, headers).catch((err) => handleFetchError(err, [])),
- $fetch(`${API_URL}tag/loader`, headers).catch((err) => handleFetchError(err, [])),
- $fetch(`${API_URL}tag/game_version`, headers).catch((err) => handleFetchError(err, [])),
- $fetch(`${API_URL}tag/donation_platform`, headers).catch((err) =>
- handleFetchError(err, []),
- ),
- $fetch(`${API_URL}tag/report_type`, headers).catch((err) => handleFetchError(err, [])),
- $fetch(`${API_URL}projects_random?count=60`, headers).catch((err) =>
- handleFetchError(err, []),
- ),
- $fetch(`${API_URL}search?limit=3&query=leave&index=relevance`, headers).catch((err) =>
- handleFetchError(err, {}),
- ),
- $fetch(`${API_URL}search?limit=3&query=&index=updated`, headers).catch((err) =>
- handleFetchError(err, {}),
- ),
- $fetch(`${API_URL.replace("/v2/", "/_internal/")}billing/products`, headers).catch((err) =>
- handleFetchError(err, []),
- ),
- ]);
-
- state.categories = categories;
- state.loaders = loaders;
- state.gameVersions = gameVersions;
- state.donationPlatforms = donationPlatforms;
- state.reportTypes = reportTypes;
- state.homePageProjects = homePageProjects;
- state.homePageSearch = homePageSearch;
- state.homePageNotifs = homePageNotifs;
- state.products = products;
- state.errors = [...caughtErrorCodes];
-
- await fs.writeFile("./src/generated/state.json", JSON.stringify(state));
-
- console.log("Tags generated!");
- },
- "pages:extend"(routes) {
- routes.splice(
- routes.findIndex((x) => x.name === "search-searchProjectType"),
- 1,
- );
-
- const types = ["mods", "modpacks", "plugins", "resourcepacks", "shaders", "datapacks"];
-
- types.forEach((type) =>
- routes.push({
- name: `search-${type}`,
- path: `/${type}`,
- file: resolve(__dirname, "src/pages/search/[searchProjectType].vue"),
- children: [],
- }),
- );
+ plugins: [
+ svgLoader({
+ svgoConfig: {
+ plugins: [
+ {
+ name: 'preset-default',
+ params: {
+ overrides: {
+ removeViewBox: false,
+ },
+ },
+ },
+ ],
+ },
+ }),
+ ],
},
- async "vintl:extendOptions"(opts) {
- opts.locales ??= [];
-
- const isProduction = getDomain() === "https://modrinth.com";
-
- const resolveCompactNumberDataImport = await (async () => {
- const compactNumberLocales: string[] = [];
+ hooks: {
+ async 'build:before'() {
+ // 30 minutes
+ const TTL = 30 * 60 * 1000
+
+ let state: {
+ lastGenerated?: string
+ apiUrl?: string
+ categories?: any[]
+ loaders?: any[]
+ gameVersions?: any[]
+ donationPlatforms?: any[]
+ reportTypes?: any[]
+ homePageProjects?: any[]
+ homePageSearch?: any[]
+ homePageNotifs?: any[]
+ products?: any[]
+ errors?: number[]
+ } = {}
+
+ try {
+ state = JSON.parse(await fs.readFile('./src/generated/state.json', 'utf8'))
+ } catch {
+ // File doesn't exist, create folder
+ await fs.mkdir('./src/generated', { recursive: true })
+ }
- for await (const localeFile of globIterate(
- "node_modules/@vintl/compact-number/dist/locale-data/*.mjs",
- { ignore: "**/*.data.mjs" },
- )) {
- const tag = basename(localeFile, ".mjs");
- compactNumberLocales.push(tag);
- }
+ const API_URL = getApiUrl()
+
+ if (
+ // Skip regeneration if within TTL...
+ state.lastGenerated &&
+ new Date(state.lastGenerated).getTime() + TTL > new Date().getTime() &&
+ // ...but only if the API URL is the same
+ state.apiUrl === API_URL &&
+ // ...and if no errors were caught during the last generation
+ (state.errors ?? []).length === 0
+ ) {
+ return
+ }
- function resolveImport(tag: string) {
- const matchedTag = matchLocale([tag], compactNumberLocales, "en-x-placeholder");
- return matchedTag === "en-x-placeholder"
- ? undefined
- : `@vintl/compact-number/locale-data/${matchedTag}`;
- }
+ state.lastGenerated = new Date().toISOString()
- return resolveImport;
- })();
+ state.apiUrl = API_URL
- const resolveOmorphiaLocaleImport = await (async () => {
- const omorphiaLocales: string[] = [];
- const omorphiaLocaleSets = new Map();
+ const headers = {
+ headers: {
+ 'user-agent': 'Knossos generator (support@modrinth.com)',
+ },
+ }
- for await (const localeDir of globIterate("node_modules/@modrinth/ui/src/locales/*", {
- posix: true,
- })) {
- const tag = basename(localeDir);
- omorphiaLocales.push(tag);
+ const caughtErrorCodes = new Set()
- const localeFiles: { from: string; format?: string }[] = [];
+ function handleFetchError(err: any, defaultValue: any) {
+ console.error('Error generating state: ', err)
+ caughtErrorCodes.add(err.status)
+ return defaultValue
+ }
- omorphiaLocaleSets.set(tag, { files: localeFiles });
+ const [
+ categories,
+ loaders,
+ gameVersions,
+ donationPlatforms,
+ reportTypes,
+ homePageProjects,
+ homePageSearch,
+ homePageNotifs,
+ products,
+ ] = await Promise.all([
+ $fetch(`${API_URL}tag/category`, headers).catch((err) => handleFetchError(err, [])),
+ $fetch(`${API_URL}tag/loader`, headers).catch((err) => handleFetchError(err, [])),
+ $fetch(`${API_URL}tag/game_version`, headers).catch((err) =>
+ handleFetchError(err, []),
+ ),
+ $fetch(`${API_URL}tag/donation_platform`, headers).catch((err) =>
+ handleFetchError(err, []),
+ ),
+ $fetch(`${API_URL}tag/report_type`, headers).catch((err) =>
+ handleFetchError(err, []),
+ ),
+ $fetch(`${API_URL}projects_random?count=60`, headers).catch((err) =>
+ handleFetchError(err, []),
+ ),
+ $fetch(`${API_URL}search?limit=3&query=leave&index=relevance`, headers).catch(
+ (err) => handleFetchError(err, {}),
+ ),
+ $fetch(`${API_URL}search?limit=3&query=&index=updated`, headers).catch((err) =>
+ handleFetchError(err, {}),
+ ),
+ $fetch(`${API_URL.replace('/v2/', '/_internal/')}billing/products`, headers).catch(
+ (err) => handleFetchError(err, []),
+ ),
+ ])
+
+ state.categories = categories
+ state.loaders = loaders
+ state.gameVersions = gameVersions
+ state.donationPlatforms = donationPlatforms
+ state.reportTypes = reportTypes
+ state.homePageProjects = homePageProjects
+ state.homePageSearch = homePageSearch
+ state.homePageNotifs = homePageNotifs
+ state.products = products
+ state.errors = [...caughtErrorCodes]
+
+ await fs.writeFile('./src/generated/state.json', JSON.stringify(state))
+
+ console.log('Tags generated!')
+ },
+ 'pages:extend'(routes) {
+ routes.splice(
+ routes.findIndex((x) => x.name === 'search-searchProjectType'),
+ 1,
+ )
+
+ const types = ['mods', 'modpacks', 'plugins', 'resourcepacks', 'shaders', 'datapacks']
+
+ types.forEach((type) =>
+ routes.push({
+ name: `search-${type}`,
+ path: `/${type}`,
+ file: resolve(__dirname, 'src/pages/search/[searchProjectType].vue'),
+ children: [],
+ }),
+ )
+ },
+ async 'vintl:extendOptions'(opts) {
+ opts.locales ??= []
+
+ const isProduction = getDomain() === 'https://modrinth.com'
+
+ const resolveCompactNumberDataImport = await (async () => {
+ const compactNumberLocales: string[] = []
+
+ for await (const localeFile of globIterate(
+ 'node_modules/@vintl/compact-number/dist/locale-data/*.mjs',
+ { ignore: '**/*.data.mjs' },
+ )) {
+ const tag = basename(localeFile, '.mjs')
+ compactNumberLocales.push(tag)
+ }
+
+ function resolveImport(tag: string) {
+ const matchedTag = matchLocale([tag], compactNumberLocales, 'en-x-placeholder')
+ return matchedTag === 'en-x-placeholder'
+ ? undefined
+ : `@vintl/compact-number/locale-data/${matchedTag}`
+ }
+
+ return resolveImport
+ })()
+
+ const resolveOmorphiaLocaleImport = await (async () => {
+ const omorphiaLocales: string[] = []
+ const omorphiaLocaleSets = new Map()
+
+ for await (const localeDir of globIterate(
+ 'node_modules/@modrinth/ui/src/locales/*',
+ {
+ posix: true,
+ },
+ )) {
+ const tag = basename(localeDir)
+ omorphiaLocales.push(tag)
+
+ const localeFiles: { from: string; format?: string }[] = []
+
+ omorphiaLocaleSets.set(tag, { files: localeFiles })
+
+ for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
+ localeFiles.push({
+ from: pathToFileURL(localeFile).toString(),
+ format: 'default',
+ })
+ }
+ }
+
+ return function resolveLocaleImport(tag: string) {
+ return omorphiaLocaleSets.get(
+ matchLocale([tag], omorphiaLocales, 'en-x-placeholder'),
+ )
+ }
+ })()
+
+ for await (const localeDir of globIterate('src/locales/*/', { posix: true })) {
+ const tag = basename(localeDir)
+ if (isProduction && !enabledLocales.includes(tag) && opts.defaultLocale !== tag)
+ continue
+
+ const locale =
+ opts.locales.find((locale) => locale.tag === tag) ??
+ opts.locales[opts.locales.push({ tag }) - 1]!
+
+ const localeFiles = (locale.files ??= [])
+
+ for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
+ const fileName = basename(localeFile)
+ if (fileName === 'index.json') {
+ localeFiles.push({
+ from: `./${relative('./src', localeFile)}`,
+ format: 'crowdin',
+ })
+ } else if (fileName === 'meta.json') {
+ const meta: Record = await fs
+ .readFile(localeFile, 'utf8')
+ .then((date) => JSON.parse(date))
+ const localeMeta = (locale.meta ??= {})
+ for (const key in meta) {
+ const value = meta[key]
+ if (value === undefined) continue
+ localeMeta[key] = value.message
+ }
+ } else {
+ ;(locale.resources ??= {})[fileName] = `./${relative('./src', localeFile)}`
+ }
+ }
+
+ const categoryOverride = localesCategoriesOverrides[tag]
+ if (categoryOverride != null) {
+ ;(locale.meta ??= {}).category = categoryOverride
+ }
+
+ const omorphiaLocaleData = resolveOmorphiaLocaleImport(tag)
+ if (omorphiaLocaleData != null) {
+ localeFiles.push(...omorphiaLocaleData.files)
+ }
+
+ const cnDataImport = resolveCompactNumberDataImport(tag)
+ if (cnDataImport != null) {
+ ;(locale.additionalImports ??= []).push({
+ from: cnDataImport,
+ resolve: false,
+ })
+ }
+ }
+ },
+ },
+ runtimeConfig: {
+ // @ts-expect-error
+ apiBaseUrl: process.env.BASE_URL ?? globalThis.BASE_URL ?? getApiUrl(),
+ // @ts-expect-error
+ rateLimitKey: process.env.RATE_LIMIT_IGNORE_KEY ?? globalThis.RATE_LIMIT_IGNORE_KEY,
+ pyroBaseUrl: process.env.PYRO_BASE_URL,
+ public: {
+ apiBaseUrl: getApiUrl(),
+ pyroBaseUrl: process.env.PYRO_BASE_URL,
+ siteUrl: getDomain(),
+ production: isProduction(),
+ featureFlagOverrides: getFeatureFlagOverrides(),
+
+ owner: process.env.VERCEL_GIT_REPO_OWNER || 'modrinth',
+ slug: process.env.VERCEL_GIT_REPO_SLUG || 'code',
+ branch:
+ process.env.VERCEL_GIT_COMMIT_REF ||
+ process.env.CF_PAGES_BRANCH ||
+ // @ts-expect-error
+ globalThis.CF_PAGES_BRANCH ||
+ 'master',
+ hash:
+ process.env.VERCEL_GIT_COMMIT_SHA ||
+ process.env.CF_PAGES_COMMIT_SHA ||
+ // @ts-expect-error
+ globalThis.CF_PAGES_COMMIT_SHA ||
+ 'unknown',
+
+ stripePublishableKey:
+ process.env.STRIPE_PUBLISHABLE_KEY ||
+ // @ts-expect-error
+ globalThis.STRIPE_PUBLISHABLE_KEY ||
+ 'pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b',
+ },
+ },
+ typescript: {
+ shim: false,
+ strict: true,
+ typeCheck: false,
+ tsConfig: {
+ compilerOptions: {
+ moduleResolution: 'bundler',
+ allowImportingTsExtensions: true,
+ },
+ },
+ },
+ modules: ['@vintl/nuxt', '@pinia/nuxt'],
+ vintl: {
+ defaultLocale: 'en-US',
+ locales: [
+ {
+ tag: 'en-US',
+ meta: {
+ static: {
+ iso: 'en',
+ },
+ },
+ },
+ ],
+ storage: 'cookie',
+ parserless: 'only-prod',
+ seo: {
+ defaultLocaleHasParameter: false,
+ },
+ onParseError({ error, message, messageId, moduleId, parseMessage, parserOptions }) {
+ const errorMessage = String(error)
+ const modulePath = relative(__dirname, moduleId)
- for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
- localeFiles.push({
- from: pathToFileURL(localeFile).toString(),
- format: "default",
- });
- }
- }
+ try {
+ const fallback = parseMessage(message, { ...parserOptions, ignoreTag: true })
- return function resolveLocaleImport(tag: string) {
- return omorphiaLocaleSets.get(matchLocale([tag], omorphiaLocales, "en-x-placeholder"));
- };
- })();
-
- for await (const localeDir of globIterate("src/locales/*/", { posix: true })) {
- const tag = basename(localeDir);
- if (isProduction && !enabledLocales.includes(tag) && opts.defaultLocale !== tag) continue;
-
- const locale =
- opts.locales.find((locale) => locale.tag === tag) ??
- opts.locales[opts.locales.push({ tag }) - 1]!;
-
- const localeFiles = (locale.files ??= []);
-
- for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
- const fileName = basename(localeFile);
- if (fileName === "index.json") {
- localeFiles.push({
- from: `./${relative("./src", localeFile)}`,
- format: "crowdin",
- });
- } else if (fileName === "meta.json") {
- const meta: Record = await fs
- .readFile(localeFile, "utf8")
- .then((date) => JSON.parse(date));
- const localeMeta = (locale.meta ??= {});
- for (const key in meta) {
- const value = meta[key];
- if (value === undefined) continue;
- localeMeta[key] = value.message;
- }
- } else {
- (locale.resources ??= {})[fileName] = `./${relative("./src", localeFile)}`;
- }
- }
+ consola.warn(
+ `[i18n] ${messageId} in ${modulePath} cannot be parsed normally due to ${errorMessage}. The tags will will not be parsed.`,
+ )
- const categoryOverride = localesCategoriesOverrides[tag];
- if (categoryOverride != null) {
- (locale.meta ??= {}).category = categoryOverride;
- }
+ return fallback
+ } catch (err) {
+ const secondaryErrorMessage = String(err)
- const omorphiaLocaleData = resolveOmorphiaLocaleImport(tag);
- if (omorphiaLocaleData != null) {
- localeFiles.push(...omorphiaLocaleData.files);
- }
+ const reason =
+ errorMessage === secondaryErrorMessage
+ ? errorMessage
+ : `${errorMessage} and ${secondaryErrorMessage}`
- const cnDataImport = resolveCompactNumberDataImport(tag);
- if (cnDataImport != null) {
- (locale.additionalImports ??= []).push({
- from: cnDataImport,
- resolve: false,
- });
- }
- }
+ consola.warn(
+ `[i18n] ${messageId} in ${modulePath} cannot be parsed due to ${reason}. It will be skipped.`,
+ )
+ }
+ },
},
- },
- runtimeConfig: {
- // @ts-ignore
- apiBaseUrl: process.env.BASE_URL ?? globalThis.BASE_URL ?? getApiUrl(),
- // @ts-ignore
- rateLimitKey: process.env.RATE_LIMIT_IGNORE_KEY ?? globalThis.RATE_LIMIT_IGNORE_KEY,
- pyroBaseUrl: process.env.PYRO_BASE_URL,
- public: {
- apiBaseUrl: getApiUrl(),
- pyroBaseUrl: process.env.PYRO_BASE_URL,
- siteUrl: getDomain(),
- production: isProduction(),
- featureFlagOverrides: getFeatureFlagOverrides(),
-
- owner: process.env.VERCEL_GIT_REPO_OWNER || "modrinth",
- slug: process.env.VERCEL_GIT_REPO_SLUG || "code",
- branch:
- process.env.VERCEL_GIT_COMMIT_REF ||
- process.env.CF_PAGES_BRANCH ||
- // @ts-ignore
- globalThis.CF_PAGES_BRANCH ||
- "master",
- hash:
- process.env.VERCEL_GIT_COMMIT_SHA ||
- process.env.CF_PAGES_COMMIT_SHA ||
- // @ts-ignore
- globalThis.CF_PAGES_COMMIT_SHA ||
- "unknown",
-
- stripePublishableKey:
- process.env.STRIPE_PUBLISHABLE_KEY ||
- globalThis.STRIPE_PUBLISHABLE_KEY ||
- "pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b",
+ nitro: {
+ moduleSideEffects: ['@vintl/compact-number/locale-data'],
},
- },
- typescript: {
- shim: false,
- strict: true,
- typeCheck: false,
- tsConfig: {
- compilerOptions: {
- moduleResolution: "bundler",
- allowImportingTsExtensions: true,
- },
+ devtools: {
+ enabled: true,
},
- },
- modules: ["@vintl/nuxt", "@pinia/nuxt"],
- vintl: {
- defaultLocale: "en-US",
- locales: [
- {
- tag: "en-US",
- meta: {
- static: {
- iso: "en",
- },
+ css: ['~/assets/styles/tailwind.css'],
+ postcss: {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
},
- },
- ],
- storage: "cookie",
- parserless: "only-prod",
- seo: {
- defaultLocaleHasParameter: false,
- },
- onParseError({ error, message, messageId, moduleId, parseMessage, parserOptions }) {
- const errorMessage = String(error);
- const modulePath = relative(__dirname, moduleId);
-
- try {
- const fallback = parseMessage(message, { ...parserOptions, ignoreTag: true });
-
- consola.warn(
- `[i18n] ${messageId} in ${modulePath} cannot be parsed normally due to ${errorMessage}. The tags will will not be parsed.`,
- );
-
- return fallback;
- } catch (err) {
- const secondaryErrorMessage = String(err);
-
- const reason =
- errorMessage === secondaryErrorMessage
- ? errorMessage
- : `${errorMessage} and ${secondaryErrorMessage}`;
-
- consola.warn(
- `[i18n] ${messageId} in ${modulePath} cannot be parsed due to ${reason}. It will be skipped.`,
- );
- }
},
- },
- nitro: {
- moduleSideEffects: ["@vintl/compact-number/locale-data"],
- },
- devtools: {
- enabled: true,
- },
- css: ["~/assets/styles/tailwind.css"],
- postcss: {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
- },
- routeRules: {
- "/**": {
- headers: {
- "Accept-CH": "Sec-CH-Prefers-Color-Scheme",
- "Critical-CH": "Sec-CH-Prefers-Color-Scheme",
- },
+ routeRules: {
+ '/**': {
+ headers: {
+ 'Accept-CH': 'Sec-CH-Prefers-Color-Scheme',
+ 'Critical-CH': 'Sec-CH-Prefers-Color-Scheme',
+ },
+ },
},
- },
- compatibilityDate: "2024-07-03",
- telemetry: false,
-});
+ compatibilityDate: '2024-07-03',
+ telemetry: false,
+})
function getApiUrl() {
- // @ts-ignore
- return process.env.BROWSER_BASE_URL ?? globalThis.BROWSER_BASE_URL ?? STAGING_API_URL;
+ // @ts-expect-error
+ return process.env.BROWSER_BASE_URL ?? globalThis.BROWSER_BASE_URL ?? STAGING_API_URL
}
function isProduction() {
- return process.env.NODE_ENV === "production";
+ return process.env.NODE_ENV === 'production'
}
function getFeatureFlagOverrides() {
- return JSON.parse(process.env.FLAG_OVERRIDES ?? "{}");
+ return JSON.parse(process.env.FLAG_OVERRIDES ?? '{}')
}
function getDomain() {
- if (process.env.NODE_ENV === "production") {
- if (process.env.SITE_URL) {
- return process.env.SITE_URL;
- }
- // @ts-ignore
- else if (process.env.CF_PAGES_URL || globalThis.CF_PAGES_URL) {
- // @ts-ignore
- return process.env.CF_PAGES_URL ?? globalThis.CF_PAGES_URL;
- } else if (process.env.HEROKU_APP_NAME) {
- return `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`;
- } else if (process.env.VERCEL_URL) {
- return `https://${process.env.VERCEL_URL}`;
- } else if (getApiUrl() === STAGING_API_URL) {
- return "https://staging.modrinth.com";
+ if (process.env.NODE_ENV === 'production') {
+ if (process.env.SITE_URL) {
+ return process.env.SITE_URL
+ }
+ // @ts-expect-error
+ else if (process.env.CF_PAGES_URL || globalThis.CF_PAGES_URL) {
+ // @ts-expect-error
+ return process.env.CF_PAGES_URL ?? globalThis.CF_PAGES_URL
+ } else if (process.env.HEROKU_APP_NAME) {
+ return `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`
+ } else if (process.env.VERCEL_URL) {
+ return `https://${process.env.VERCEL_URL}`
+ } else if (getApiUrl() === STAGING_API_URL) {
+ return 'https://staging.modrinth.com'
+ } else {
+ return 'https://modrinth.com'
+ }
} else {
- return "https://modrinth.com";
+ const port = process.env.PORT || 3000
+ return `http://localhost:${port}`
}
- } else {
- const port = process.env.PORT || 3000;
- return `http://localhost:${port}`;
- }
}
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 011eafb647..0414c145f7 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -71,5 +71,6 @@
"vue3-apexcharts": "^1.5.2",
"xss": "^1.0.14"
},
- "web-types": "../../web-types.json"
+ "web-types": "../../web-types.json",
+ "prettier": "@modrinth/tooling-config/prettier.nuxt.config.js"
}
diff --git a/apps/frontend/src/app.vue b/apps/frontend/src/app.vue
index ebd9fa5606..a1081af092 100644
--- a/apps/frontend/src/app.vue
+++ b/apps/frontend/src/app.vue
@@ -1,11 +1,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/apps/frontend/src/assets/styles/components.scss b/apps/frontend/src/assets/styles/components.scss
index f647e7e25d..743bc611d0 100644
--- a/apps/frontend/src/assets/styles/components.scss
+++ b/apps/frontend/src/assets/styles/components.scss
@@ -3,368 +3,368 @@
*/
.known-error .multiselect__tags {
- border-color: var(--color-red) !important;
- background-color: var(--color-warning-bg) !important;
+ border-color: var(--color-red) !important;
+ background-color: var(--color-warning-bg) !important;
- &::placeholder {
- color: var(--color-warning-text);
- }
+ &::placeholder {
+ color: var(--color-warning-text);
+ }
}
.grid-display {
- display: grid;
- grid-gap: var(--spacing-card-md);
- grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
+ display: grid;
+ grid-gap: var(--spacing-card-md);
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
- .grid-display__item {
- display: flex;
- flex-grow: 1;
- flex-direction: column;
- justify-content: flex-start;
- background-color: var(--color-bg);
- border-radius: var(--size-rounded-card);
- padding: var(--spacing-card-lg);
- gap: var(--spacing-card-md);
- outline: 1px solid transparent;
+ .grid-display__item {
+ display: flex;
+ flex-grow: 1;
+ flex-direction: column;
+ justify-content: flex-start;
+ background-color: var(--color-bg);
+ border-radius: var(--size-rounded-card);
+ padding: var(--spacing-card-lg);
+ gap: var(--spacing-card-md);
+ outline: 1px solid transparent;
+
+ .label {
+ color: var(--color-heading);
+ font-weight: bold;
+ font-size: 1rem;
+ }
- .label {
- color: var(--color-heading);
- font-weight: bold;
- font-size: 1rem;
- }
+ .value {
+ color: var(--color-text-dark);
+ font-weight: bold;
+ font-size: 2rem;
+ }
- .value {
- color: var(--color-text-dark);
- font-weight: bold;
- font-size: 2rem;
+ .goto-link {
+ margin-top: auto;
+ }
}
- .goto-link {
- margin-top: auto;
+ &.width-12 {
+ grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}
- }
-
- &.width-12 {
- grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
- }
- &.width-16 {
- grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
- }
+ &.width-16 {
+ grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
+ }
}
/*
Cards and body styling
*/
.base-card {
- @extend .padding-lg;
+ @extend .padding-lg;
- position: relative;
+ position: relative;
- background-color: var(--color-raised-bg);
- border-radius: var(--size-rounded-card);
+ background-color: var(--color-raised-bg);
+ border-radius: var(--size-rounded-card);
- margin-bottom: var(--spacing-card-md);
- outline: 2px solid transparent;
- outline-offset: -2px;
+ margin-bottom: var(--spacing-card-md);
+ outline: 2px solid transparent;
+ outline-offset: -2px;
- box-shadow: var(--shadow-card);
+ box-shadow: var(--shadow-card);
- .card__overlay {
- position: absolute;
- top: 1rem;
- right: 1rem;
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- grid-gap: 0.5rem;
- z-index: 2;
- }
+ .card__overlay {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ grid-gap: 0.5rem;
+ z-index: 2;
+ }
- &.moderation-card {
- background-color: var(--color-warning-banner-bg);
- }
+ &.moderation-card {
+ background-color: var(--color-warning-banner-bg);
+ }
- &.recessed {
- background-color: var(--color-bg);
- box-shadow: none;
- }
+ &.recessed {
+ background-color: var(--color-bg);
+ box-shadow: none;
+ }
}
.universal-labels {
- label,
- .label {
- :where(.label__title) {
- display: block;
- margin-block: var(--spacing-card-md) var(--spacing-card-sm);
-
- // Same styling as h3
- color: var(--color-text-dark);
- font-size: 1.17rem;
- font-weight: bold;
-
- .required {
- color: var(--color-red);
- }
-
- &.size-card-header {
- font-size: var(--font-size-xl);
- margin-bottom: 1rem;
- }
- }
+ label,
+ .label {
+ :where(.label__title) {
+ display: block;
+ margin-block: var(--spacing-card-md) var(--spacing-card-sm);
+
+ // Same styling as h3
+ color: var(--color-text-dark);
+ font-size: 1.17rem;
+ font-weight: bold;
+
+ .required {
+ color: var(--color-red);
+ }
+
+ &.size-card-header {
+ font-size: var(--font-size-xl);
+ margin-bottom: 1rem;
+ }
+ }
- :where(.label__description) {
- display: block;
- margin-block-end: var(--spacing-card-sm);
+ :where(.label__description) {
+ display: block;
+ margin-block-end: var(--spacing-card-sm);
- .label__subdescription {
- display: block;
- margin-block-start: var(--spacing-card-md);
- }
- }
+ .label__subdescription {
+ display: block;
+ margin-block-start: var(--spacing-card-md);
+ }
+ }
- :where(h1, h2, h3, h4) {
- margin-block: 0;
+ :where(h1, h2, h3, h4) {
+ margin-block: 0;
+ }
}
- }
}
.padding-lg {
- padding: var(--spacing-card-lg);
+ padding: var(--spacing-card-lg);
}
.padding-bg {
- padding: var(--spacing-card-bg);
+ padding: var(--spacing-card-bg);
}
.padding-md {
- padding: var(--spacing-card-md);
+ padding: var(--spacing-card-md);
}
.padding-sm {
- padding: var(--spacing-card-sm);
+ padding: var(--spacing-card-sm);
}
.padding-0 {
- padding: 0;
+ padding: 0;
}
.padding-block-lg {
- padding-block: var(--spacing-card-lg);
+ padding-block: var(--spacing-card-lg);
}
.padding-block-bg {
- padding-block: var(--spacing-card-bg);
+ padding-block: var(--spacing-card-bg);
}
.padding-block-md {
- padding-block: var(--spacing-card-md);
+ padding-block: var(--spacing-card-md);
}
.padding-block-sm {
- padding-block: var(--spacing-card-sm);
+ padding-block: var(--spacing-card-sm);
}
.padding-block-0 {
- padding-block: 0;
+ padding-block: 0;
}
.padding-inline-lg {
- padding-inline: var(--spacing-card-lg);
+ padding-inline: var(--spacing-card-lg);
}
.padding-inline-bg {
- padding-inline: var(--spacing-card-bg);
+ padding-inline: var(--spacing-card-bg);
}
.padding-inline-md {
- padding-inline: var(--spacing-card-md);
+ padding-inline: var(--spacing-card-md);
}
.padding-inline-sm {
- padding-inline: var(--spacing-card-sm);
+ padding-inline: var(--spacing-card-sm);
}
.padding-inline-0 {
- padding-inline: 0;
+ padding-inline: 0;
}
.universal-body {
- @extend .universal-labels;
-
- .multiselect {
- width: 15rem;
- }
-
- > :where(
- input + *,
- .input-group + *,
- .textarea-wrapper + *,
- .chips + *,
- .resizable-textarea-wrapper + *,
- .input-div + *
- ) {
- &:not(:empty) {
- margin-block-start: var(--spacing-card-md);
- }
- }
+ @extend .universal-labels;
- :where(button, .button, .iconified-button) {
- width: fit-content;
- }
-
- .input-group {
- input {
- width: auto;
- flex-basis: 0;
+ .multiselect {
+ width: 15rem;
}
- }
- :where(input) {
- box-sizing: border-box;
- max-height: 40px;
-
- &:not(.stylized-toggle) {
- max-width: 100%;
+ > :where(
+ input + *,
+ .input-group + *,
+ .textarea-wrapper + *,
+ .chips + *,
+ .resizable-textarea-wrapper + *,
+ .input-div + *
+ ) {
+ &:not(:empty) {
+ margin-block-start: var(--spacing-card-md);
+ }
}
- }
- :where(.adjacent-input, &.adjacent-input) {
- display: flex;
- flex-direction: row;
- align-items: center;
- flex-wrap: wrap;
- gap: var(--spacing-card-sm);
- margin-bottom: calc(var(--spacing-card-sm) + var(--spacing-card-md));
+ :where(button, .button, .iconified-button) {
+ width: fit-content;
+ }
- .iconified-button,
.input-group {
- flex-shrink: 0;
+ input {
+ width: auto;
+ flex-basis: 0;
+ }
}
- input {
- flex-shrink: 1;
- }
+ :where(input) {
+ box-sizing: border-box;
+ max-height: 40px;
- > :first-child {
- flex-shrink: 2;
- flex-grow: 1;
- flex-basis: min-content;
+ &:not(.stylized-toggle) {
+ max-width: 100%;
+ }
}
- label,
- .label {
- .label__title {
- margin-block: 0;
- }
+ :where(.adjacent-input, &.adjacent-input) {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: var(--spacing-card-sm);
+ margin-bottom: calc(var(--spacing-card-sm) + var(--spacing-card-md));
+
+ .iconified-button,
+ .input-group {
+ flex-shrink: 0;
+ }
- .label__description {
- margin-block-end: 0;
- }
+ input {
+ flex-shrink: 1;
+ }
- .label__description:not(:first-child) {
- margin-top: var(--spacing-card-sm);
- }
- }
+ > :first-child {
+ flex-shrink: 2;
+ flex-grow: 1;
+ flex-basis: min-content;
+ }
- @media screen and (max-width: 750px) {
- &:not(&.small) {
- flex-direction: column;
- align-items: flex-start;
+ label,
+ .label {
+ .label__title {
+ margin-block: 0;
+ }
- .stylized-toggle {
- flex-basis: 0;
- }
- }
- }
- }
+ .label__description {
+ margin-block-end: 0;
+ }
- h1 {
- display: flex;
- align-items: center;
- }
+ .label__description:not(:first-child) {
+ margin-top: var(--spacing-card-sm);
+ }
+ }
- .markdown-body h1 {
- display: block;
- }
+ @media screen and (max-width: 750px) {
+ &:not(&.small) {
+ flex-direction: column;
+ align-items: flex-start;
- > :first-child {
- :first-child {
- margin-block-start: 0;
+ .stylized-toggle {
+ flex-basis: 0;
+ }
+ }
+ }
}
- margin-block-start: 0;
- }
+ h1 {
+ display: flex;
+ align-items: center;
+ }
- > :last-child {
- margin-block-end: 0;
- }
+ .markdown-body h1 {
+ display: block;
+ }
- :where(.header__row) {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- gap: var(--spacing-card-sm);
+ > :first-child {
+ :first-child {
+ margin-block-start: 0;
+ }
- * {
- flex-shrink: 0;
+ margin-block-start: 0;
}
- .header__title {
- margin: 0;
- flex-grow: 1;
+ > :last-child {
+ margin-block-end: 0;
}
- &:not(:last-child) {
- margin-bottom: var(--spacing-card-md);
+ :where(.header__row) {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: var(--spacing-card-sm);
+
+ * {
+ flex-shrink: 0;
+ }
+
+ .header__title {
+ margin: 0;
+ flex-grow: 1;
+ }
+
+ &:not(:last-child) {
+ margin-bottom: var(--spacing-card-md);
+ }
}
- }
}
.universal-card {
- @extend .base-card;
- @extend .universal-body;
+ @extend .base-card;
+ @extend .universal-body;
}
.universal-modal {
- @extend .universal-body;
+ @extend .universal-body;
- padding: var(--spacing-card-bg);
- display: flex;
- flex-direction: column;
+ padding: var(--spacing-card-bg);
+ display: flex;
+ flex-direction: column;
- > p:first-child {
- margin-top: 0;
- }
+ > p:first-child {
+ margin-top: 0;
+ }
- @media screen and (max-width: 750px) {
- .adjacent-input,
- &.adjacent-input &:not(&.small) {
- flex-direction: row;
- align-items: center;
+ @media screen and (max-width: 750px) {
+ .adjacent-input,
+ &.adjacent-input &:not(&.small) {
+ flex-direction: row;
+ align-items: center;
+ }
}
- }
- @media screen and (max-width: calc(600px + 2rem)) {
- .adjacent-input,
- &.adjacent-input &:not(&.small) {
- flex-direction: column;
- align-items: flex-start;
+ @media screen and (max-width: calc(600px + 2rem)) {
+ .adjacent-input,
+ &.adjacent-input &:not(&.small) {
+ flex-direction: column;
+ align-items: flex-start;
+ }
}
- }
}
.navigation-card {
- @extend .base-card;
- @extend .padding-inline-lg;
- @extend .padding-block-md;
+ @extend .base-card;
+ @extend .padding-inline-lg;
+ @extend .padding-block-md;
- align-items: center;
- display: flex;
- justify-content: space-between;
- flex-wrap: wrap;
- row-gap: 0.5rem;
- min-height: 3.75rem;
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ row-gap: 0.5rem;
+ min-height: 3.75rem;
}
/*
@@ -372,921 +372,921 @@
*/
.text-link {
- color: var(--color-link);
+ color: var(--color-link);
- &:focus-visible,
- &:hover {
- color: var(--color-link-hover);
- cursor: pointer;
- text-decoration: underline;
- }
+ &:focus-visible,
+ &:hover {
+ color: var(--color-link-hover);
+ cursor: pointer;
+ text-decoration: underline;
+ }
- &:active {
- color: var(--color-link-active);
- }
+ &:active {
+ color: var(--color-link-active);
+ }
}
.title-link {
- text-decoration: underline;
+ text-decoration: underline;
- &:focus-visible,
- &:hover {
- color: var(--color-heading);
- }
+ &:focus-visible,
+ &:hover {
+ color: var(--color-heading);
+ }
- &:active {
- color: var(--color-text-dark);
- }
+ &:active {
+ color: var(--color-text-dark);
+ }
}
.button-base {
- @extend .button-animation;
- font-weight: 500;
- outline: 2px solid transparent;
+ @extend .button-animation;
+ font-weight: 500;
+ outline: 2px solid transparent;
- &:focus-visible:not(&:disabled),
- &:hover:not(&:disabled) {
- cursor: pointer;
- filter: brightness(0.85);
- }
+ &:focus-visible:not(&:disabled),
+ &:hover:not(&:disabled) {
+ cursor: pointer;
+ filter: brightness(0.85);
+ }
- &:active:not(&:disabled) {
- filter: brightness(0.8);
- }
+ &:active:not(&:disabled) {
+ filter: brightness(0.8);
+ }
- &:disabled,
- &[disabled="true"] {
- cursor: not-allowed;
- filter: grayscale(50%);
- opacity: 0.5;
- box-shadow: none;
- }
+ &:disabled,
+ &[disabled='true'] {
+ cursor: not-allowed;
+ filter: grayscale(50%);
+ opacity: 0.5;
+ box-shadow: none;
+ }
}
:not(tr).button-transparent {
- @extend .button-base;
- background-color: transparent;
- border-radius: var(--size-rounded-sm);
+ @extend .button-base;
+ background-color: transparent;
+ border-radius: var(--size-rounded-sm);
- &:focus-visible:not(&:disabled),
- &:hover:not(&:disabled),
- &:active:not(&:disabled) {
- background-color: var(--color-raised-bg);
- }
+ &:focus-visible:not(&:disabled),
+ &:hover:not(&:disabled),
+ &:active:not(&:disabled) {
+ background-color: var(--color-raised-bg);
+ }
- &.brand-button {
- color: var(--color-brand);
- }
+ &.brand-button {
+ color: var(--color-brand);
+ }
- &.danger-button {
- color: var(--color-red);
- }
+ &.danger-button {
+ color: var(--color-red);
+ }
}
tr.button-transparent {
- @extend .button-animation;
- background-color: transparent;
- border-radius: var(--size-rounded-sm);
+ @extend .button-animation;
+ background-color: transparent;
+ border-radius: var(--size-rounded-sm);
- &:focus-visible:not(&:disabled) > *,
- &:hover:not(&:disabled) > * {
- cursor: pointer;
- filter: brightness(0.85);
- background-color: var(--color-raised-bg);
- }
+ &:focus-visible:not(&:disabled) > *,
+ &:hover:not(&:disabled) > * {
+ cursor: pointer;
+ filter: brightness(0.85);
+ background-color: var(--color-raised-bg);
+ }
- &:active:not(&:disabled) > * {
- filter: brightness(0.8);
- background-color: var(--color-raised-bg);
- }
+ &:active:not(&:disabled) > * {
+ filter: brightness(0.8);
+ background-color: var(--color-raised-bg);
+ }
- &:disabled > *,
- &[disabled="true"] > * {
- cursor: not-allowed;
- filter: grayscale(50%);
- opacity: 0.5;
- box-shadow: none;
- }
+ &:disabled > *,
+ &[disabled='true'] > * {
+ cursor: not-allowed;
+ filter: grayscale(50%);
+ opacity: 0.5;
+ box-shadow: none;
+ }
}
.button-within {
- transition:
- opacity 0.5s ease-in-out,
- filter 0.2s ease-in-out,
- transform 0.05s ease-in-out,
- outline 0.2s ease-in-out;
-
- &:focus-visible:not(&.disabled),
- &:hover:not(&.disabled) {
- filter: brightness(0.85);
- }
+ transition:
+ opacity 0.5s ease-in-out,
+ filter 0.2s ease-in-out,
+ transform 0.05s ease-in-out,
+ outline 0.2s ease-in-out;
+
+ &:focus-visible:not(&.disabled),
+ &:hover:not(&.disabled) {
+ filter: brightness(0.85);
+ }
- &:active:not(&.disabled) {
- filter: brightness(0.8);
- }
+ &:active:not(&.disabled) {
+ filter: brightness(0.8);
+ }
- &.disabled {
- cursor: not-allowed;
- filter: grayscale(50%);
- opacity: 0.5;
- box-shadow: none;
+ &.disabled {
+ cursor: not-allowed;
+ filter: grayscale(50%);
+ opacity: 0.5;
+ box-shadow: none;
- &disabled,
- &[disabled="true"] {
- cursor: not-allowed;
- box-shadow: none;
+ &disabled,
+ &[disabled='true'] {
+ cursor: not-allowed;
+ box-shadow: none;
+ }
}
- }
}
.button-color-base {
- box-sizing: border-box;
- --text-color: var(--color-button-text);
- --background-color: var(--color-button-bg);
+ box-sizing: border-box;
+ --text-color: var(--color-button-text);
+ --background-color: var(--color-button-bg);
- color: var(--text-color);
- background-color: var(--background-color);
- box-shadow:
- var(--shadow-inset-sm),
- 0 0 0 0 transparent;
- border-radius: var(--size-rounded-sm);
+ color: var(--text-color);
+ background-color: var(--background-color);
+ box-shadow:
+ var(--shadow-inset-sm),
+ 0 0 0 0 transparent;
+ border-radius: var(--size-rounded-sm);
}
.iconified-button {
- @extend .button-base;
- @extend .button-color-base;
-
- display: flex;
- padding: var(--spacing-card-sm) var(--spacing-card-bg);
- margin: 0;
- font-size: var(--font-size-nm);
- align-items: center;
- cursor: pointer;
- width: fit-content;
- height: fit-content;
- transition:
- opacity 0.5s ease-in-out,
- filter 0.2s ease-in-out,
- scale 0.05s ease-in-out,
- outline 0.2s ease-in-out;
-
- text-decoration: none;
-
- svg {
- width: 1.1rem;
- height: 1.1rem;
- margin-right: 0.5rem;
- }
-
- &.icon-only {
- padding: 0 0.5rem;
+ @extend .button-base;
+ @extend .button-color-base;
+
+ display: flex;
+ padding: var(--spacing-card-sm) var(--spacing-card-bg);
+ margin: 0;
+ font-size: var(--font-size-nm);
+ align-items: center;
+ cursor: pointer;
+ width: fit-content;
+ height: fit-content;
+ transition:
+ opacity 0.5s ease-in-out,
+ filter 0.2s ease-in-out,
+ scale 0.05s ease-in-out,
+ outline 0.2s ease-in-out;
+
+ text-decoration: none;
svg {
- margin-right: 0;
+ width: 1.1rem;
+ height: 1.1rem;
+ margin-right: 0.5rem;
}
- }
- &.transparent {
- background: none;
- box-shadow: none;
- }
+ &.icon-only {
+ padding: 0 0.5rem;
- &.bold-button {
- font-weight: bold;
- }
+ svg {
+ margin-right: 0;
+ }
+ }
+
+ &.transparent {
+ background: none;
+ box-shadow: none;
+ }
+
+ &.bold-button {
+ font-weight: bold;
+ }
}
.square-button {
- @extend .button-base;
+ @extend .button-base;
- --text-color: var(--color-button-text);
- --background-color: var(--color-button-bg);
+ --text-color: var(--color-button-text);
+ --background-color: var(--color-button-bg);
- display: flex;
- align-items: center;
- justify-content: center;
- height: 2.25rem;
- width: 2.25rem;
- border-radius: var(--size-rounded-sm);
- color: var(--text-color);
- background-color: var(--background-color);
- box-shadow:
- var(--shadow-inset-sm),
- 0 0 0 0 transparent;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 2.25rem;
+ width: 2.25rem;
+ border-radius: var(--size-rounded-sm);
+ color: var(--text-color);
+ background-color: var(--background-color);
+ box-shadow:
+ var(--shadow-inset-sm),
+ 0 0 0 0 transparent;
- svg {
- min-width: 1.25rem;
- max-width: 1.25rem;
- min-height: 1.25rem;
- max-height: 1.25rem;
- }
+ svg {
+ min-width: 1.25rem;
+ max-width: 1.25rem;
+ min-height: 1.25rem;
+ max-height: 1.25rem;
+ }
- flex-shrink: 0;
+ flex-shrink: 0;
}
.header-button {
- @extend .iconified-button;
+ @extend .iconified-button;
- box-sizing: content-box;
- min-height: unset;
- border-radius: var(--size-rounded-max);
- white-space: nowrap;
- padding: 0.5rem 0.75rem;
- max-height: 1.75rem;
+ box-sizing: content-box;
+ min-height: unset;
+ border-radius: var(--size-rounded-max);
+ white-space: nowrap;
+ padding: 0.5rem 0.75rem;
+ max-height: 1.75rem;
- svg {
- vertical-align: middle;
- margin-right: 0.5rem;
- height: 1.25rem;
- width: 1.25rem;
- }
+ svg {
+ vertical-align: middle;
+ margin-right: 0.5rem;
+ height: 1.25rem;
+ width: 1.25rem;
+ }
}
.raised-button {
- --background-color: var(--color-raised-bg);
- box-shadow: var(--shadow-inset-sm), var(--shadow-raised);
+ --background-color: var(--color-raised-bg);
+ box-shadow: var(--shadow-inset-sm), var(--shadow-raised);
}
.danger-button {
- --background-color: var(--color-red);
- --text-color: var(--color-brand-inverted);
+ --background-color: var(--color-red);
+ --text-color: var(--color-brand-inverted);
}
.moderation-button {
- --background-color: var(--color-orange);
- --text-color: var(--color-brand-inverted);
+ --background-color: var(--color-orange);
+ --text-color: var(--color-brand-inverted);
}
.brand-button {
- --background-color: var(--color-brand);
- --text-color: var(--color-brand-inverted);
+ --background-color: var(--color-brand);
+ --text-color: var(--color-brand-inverted);
}
.alt-brand-button {
- --background-color: var(--color-brand-highlight);
- --text-color: var(--color-text);
+ --background-color: var(--color-brand-highlight);
+ --text-color: var(--color-text);
}
.button-group {
- display: flex;
- grid-gap: var(--spacing-card-sm);
- flex-wrap: wrap;
- margin-top: var(--spacing-card-md);
- justify-content: right;
+ display: flex;
+ grid-gap: var(--spacing-card-sm);
+ flex-wrap: wrap;
+ margin-top: var(--spacing-card-md);
+ justify-content: right;
}
.multiselect--above .multiselect__content-wrapper {
- border-top: none !important;
- border-top-left-radius: var(--size-rounded-card) !important;
- border-top-right-radius: var(--size-rounded-card) !important;
+ border-top: none !important;
+ border-top-left-radius: var(--size-rounded-card) !important;
+ border-top-right-radius: var(--size-rounded-card) !important;
}
.known-error .multiselect__tags {
- border-color: var(--color-red) !important;
- background-color: var(--color-warning-bg) !important;
+ border-color: var(--color-red) !important;
+ background-color: var(--color-warning-bg) !important;
- &::placeholder {
- color: var(--color-warning-text);
- }
+ &::placeholder {
+ color: var(--color-warning-text);
+ }
}
.switch {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- -webkit-tap-highlight-color: transparent;
- cursor: pointer;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ -webkit-tap-highlight-color: transparent;
+ cursor: pointer;
- &:focus {
- //outline: 0; Bad for accessibility
- }
+ &:focus {
+ //outline: 0; Bad for accessibility
+ }
}
.stylized-toggle {
- @extend .button-base;
-
- box-sizing: content-box;
- min-height: 32px;
- height: 32px;
- width: 52px;
- max-width: 52px;
- border-radius: var(--size-rounded-max);
- display: inline-block;
- position: relative;
- margin: 0;
- transition: all 0.2s ease;
- background: var(--color-button-bg);
-
- &:after {
- content: "";
- position: absolute;
- top: 7px;
- left: 7px;
- width: 18px;
- height: 18px;
- border-radius: 50%;
- background: var(--color-toggle-handle);
- transition: all 0.2s cubic-bezier(0.5, 0.1, 0.75, 1.35);
- outline: 2px solid transparent;
+ @extend .button-base;
+
+ box-sizing: content-box;
+ min-height: 32px;
+ height: 32px;
+ width: 52px;
+ max-width: 52px;
+ border-radius: var(--size-rounded-max);
+ display: inline-block;
+ position: relative;
+ margin: 0;
+ transition: all 0.2s ease;
+ background: var(--color-button-bg);
- @media (prefers-reduced-motion) {
- transition: none;
+ &:after {
+ content: '';
+ position: absolute;
+ top: 7px;
+ left: 7px;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: var(--color-toggle-handle);
+ transition: all 0.2s cubic-bezier(0.5, 0.1, 0.75, 1.35);
+ outline: 2px solid transparent;
+
+ @media (prefers-reduced-motion) {
+ transition: none;
+ }
}
- }
- &:checked {
- background-color: var(--color-brand);
+ &:checked {
+ background-color: var(--color-brand);
- &:after {
- transform: translatex(20px);
- background: var(--color-brand-inverted);
+ &:after {
+ transform: translatex(20px);
+ background: var(--color-brand-inverted);
+ }
}
- }
- &:hover &:focus {
- background: var(--color-button-bg);
- }
+ &:hover &:focus {
+ background: var(--color-button-bg);
+ }
}
.textarea-wrapper {
- display: flex;
- flex-direction: column;
- align-items: stretch;
-
- textarea {
- border-radius: var(--size-rounded-sm);
- flex: 1;
- overflow-y: auto;
- resize: none;
- max-width: 100%;
- }
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+
+ textarea {
+ border-radius: var(--size-rounded-sm);
+ flex: 1;
+ overflow-y: auto;
+ resize: none;
+ max-width: 100%;
+ }
}
.resizable-textarea-wrapper {
- display: block;
+ display: block;
- textarea {
- border-radius: var(--size-rounded-sm);
- min-height: 10rem;
- width: calc(100% - var(--spacing-card-lg) - var(--spacing-card-sm));
- resize: vertical;
- }
+ textarea {
+ border-radius: var(--size-rounded-sm);
+ min-height: 10rem;
+ width: calc(100% - var(--spacing-card-lg) - var(--spacing-card-sm));
+ resize: vertical;
+ }
}
.error {
- display: flex;
- flex-direction: column;
- width: 100%;
- justify-content: center;
- align-items: center;
-
- .icon {
- width: 8rem;
- height: 8rem;
- margin: 1.5rem 0;
- }
-
- .text {
- margin-bottom: 2rem;
- font-size: 1.25rem;
- text-align: center;
- }
-
- .link {
- cursor: pointer;
- text-decoration: underline;
- }
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+
+ .icon {
+ width: 8rem;
+ height: 8rem;
+ margin: 1.5rem 0;
+ }
+
+ .text {
+ margin-bottom: 2rem;
+ font-size: 1.25rem;
+ text-align: center;
+ }
+
+ .link {
+ cursor: pointer;
+ text-decoration: underline;
+ }
}
.card-divider {
- background-color: var(--color-button-bg);
- border: none;
- color: var(--color-button-bg);
- height: 1px;
- margin: var(--spacing-card-bg) 0;
+ background-color: var(--color-button-bg);
+ border: none;
+ color: var(--color-button-bg);
+ height: 1px;
+ margin: var(--spacing-card-bg) 0;
}
.text-input-wrapper.known-error,
input.known-error,
textarea.known-error {
- outline: 2px solid var(--color-red);
- background-color: var(--color-warning-bg) !important;
+ outline: 2px solid var(--color-red);
+ background-color: var(--color-warning-bg) !important;
- &::placeholder {
- color: var(--color-warning-text);
- }
+ &::placeholder {
+ color: var(--color-warning-text);
+ }
}
.known-errors {
- min-height: 0;
- color: var(--color-red);
+ min-height: 0;
+ color: var(--color-red);
- ul {
- margin: 0;
- }
+ ul {
+ margin: 0;
+ }
}
.goto-link {
- display: flex;
- align-items: center;
- gap: 3px;
+ display: flex;
+ align-items: center;
+ gap: 3px;
- color: var(--color-link);
+ color: var(--color-link);
}
.goto-link:hover,
.goto-link:focus-visible {
- color: var(--color-link-hover);
- text-decoration: underline;
+ color: var(--color-link-hover);
+ text-decoration: underline;
}
.goto-link:active {
- color: var(--color-link-active);
+ color: var(--color-link-active);
}
h1 {
- .beta-badge {
- font-size: 0.4em;
- }
+ .beta-badge {
+ font-size: 0.4em;
+ }
}
.beta-badge {
- font-size: 0.7em;
- padding: 0.2rem 0.4rem;
- background-color: transparent;
- box-sizing: border-box;
- border: 2px solid var(--color-text);
- color: var(--color-text);
- border-radius: var(--size-rounded-max);
- margin-left: 0.5rem;
- font-weight: bold;
+ font-size: 0.7em;
+ padding: 0.2rem 0.4rem;
+ background-color: transparent;
+ box-sizing: border-box;
+ border: 2px solid var(--color-text);
+ color: var(--color-text);
+ border-radius: var(--size-rounded-max);
+ margin-left: 0.5rem;
+ font-weight: bold;
}
.router-link-exact-active,
h1,
h2,
h3 {
- .beta-badge {
- background-color: var(--color-button-text-active);
- box-sizing: border-box;
- border-color: transparent;
- color: var(--color-raised-bg);
- }
+ .beta-badge {
+ background-color: var(--color-button-text-active);
+ box-sizing: border-box;
+ border-color: transparent;
+ color: var(--color-raised-bg);
+ }
}
@media (prefers-reduced-motion) {
- .button-animation,
- button {
- transform: none !important;
- }
+ .button-animation,
+ button {
+ transform: none !important;
+ }
}
.push-right:not(.input-group),
.push-right.input-group > :first-child {
- margin-left: auto;
- margin-right: 0;
+ margin-left: auto;
+ margin-right: 0;
}
.full-width-inputs {
- .multiselect,
- input,
- .iconified-input {
- width: 100%;
- flex-basis: 100%;
- }
+ .multiselect,
+ input,
+ .iconified-input {
+ width: 100%;
+ flex-basis: 100%;
+ }
}
input,
button {
- &:disabled {
- cursor: not-allowed !important;
- }
+ &:disabled {
+ cursor: not-allowed !important;
+ }
}
.input-group {
- display: flex;
- flex-direction: row;
- grid-gap: var(--spacing-card-sm);
- flex-wrap: wrap;
- max-width: 100%;
- align-items: center;
-
- .multiselect {
- width: 15rem;
- }
+ display: flex;
+ flex-direction: row;
+ grid-gap: var(--spacing-card-sm);
+ flex-wrap: wrap;
+ max-width: 100%;
+ align-items: center;
- input {
- flex-shrink: 2;
- }
+ .multiselect {
+ width: 15rem;
+ }
- &.shrink-first {
- :first-child {
- flex-shrink: 2;
- flex-grow: 1;
- flex-basis: min-content;
+ input {
+ flex-shrink: 2;
}
- :not(:first-child) {
- flex-shrink: 1;
+ &.shrink-first {
+ :first-child {
+ flex-shrink: 2;
+ flex-grow: 1;
+ flex-basis: min-content;
+ }
+
+ :not(:first-child) {
+ flex-shrink: 1;
+ }
}
- }
- &.right-aligned {
- justify-content: end;
- }
+ &.right-aligned {
+ justify-content: end;
+ }
}
.input-stack {
- display: flex;
- flex-direction: column;
+ display: flex;
+ flex-direction: column;
- > *:not(:last-child) {
- margin-bottom: var(--spacing-card-sm);
- }
+ > *:not(:last-child) {
+ margin-bottom: var(--spacing-card-sm);
+ }
- > .multiselect {
- width: unset;
- height: inherit;
- }
+ > .multiselect {
+ width: unset;
+ height: inherit;
+ }
}
.text-input-wrapper {
- display: flex;
- flex-direction: row;
- flex-wrap: nowrap;
- background: var(--color-button-bg);
- width: fit-content;
- border-radius: var(--size-rounded-sm);
- box-shadow:
- var(--shadow-inset-sm),
- 0 0 0 0 transparent;
- transition: box-shadow 0.1s ease-in-out;
- overflow: hidden;
- max-width: 100%;
- outline: 2px solid transparent;
-
- .text-input-wrapper__before {
display: flex;
- color: var(--color-text);
- padding: 0.5rem 0 0.5rem 1rem;
- font-weight: var(--font-weight-medium);
- min-height: 36px;
- box-sizing: border-box;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ background: var(--color-button-bg);
width: fit-content;
- align-items: center;
- filter: grayscale(50%);
- opacity: 0.5;
- box-shadow: none;
- flex-shrink: 0;
- }
-
- input,
- textarea {
- border-radius: 0;
- background-color: transparent;
- box-shadow: unset !important;
- padding-left: 0;
- flex-grow: 1;
- outline: none !important;
- }
-
- &:focus,
- &:focus-visible,
- &:focus-within {
+ border-radius: var(--size-rounded-sm);
box-shadow:
- inset 0 0 0 transparent,
- 0 0 0 0.25rem var(--color-brand-shadow);
- color: var(--color-button-text-active);
- }
+ var(--shadow-inset-sm),
+ 0 0 0 0 transparent;
+ transition: box-shadow 0.1s ease-in-out;
+ overflow: hidden;
+ max-width: 100%;
+ outline: 2px solid transparent;
+
+ .text-input-wrapper__before {
+ display: flex;
+ color: var(--color-text);
+ padding: 0.5rem 0 0.5rem 1rem;
+ font-weight: var(--font-weight-medium);
+ min-height: 36px;
+ box-sizing: border-box;
+ width: fit-content;
+ align-items: center;
+ filter: grayscale(50%);
+ opacity: 0.5;
+ box-shadow: none;
+ flex-shrink: 0;
+ }
+
+ input,
+ textarea {
+ border-radius: 0;
+ background-color: transparent;
+ box-shadow: unset !important;
+ padding-left: 0;
+ flex-grow: 1;
+ outline: none !important;
+ }
+
+ &:focus,
+ &:focus-visible,
+ &:focus-within {
+ box-shadow:
+ inset 0 0 0 transparent,
+ 0 0 0 0.25rem var(--color-brand-shadow);
+ color: var(--color-button-text-active);
+ }
}
.primary-stat {
- align-items: center;
- display: flex;
- margin-bottom: 0.6rem;
+ align-items: center;
+ display: flex;
+ margin-bottom: 0.6rem;
- .primary-stat__icon {
- height: 1rem;
- width: 1rem;
- }
+ .primary-stat__icon {
+ height: 1rem;
+ width: 1rem;
+ }
- .primary-stat__text {
- margin-left: 0.4rem;
- }
+ .primary-stat__text {
+ margin-left: 0.4rem;
+ }
- .primary-stat__counter {
- font-size: var(--font-size-lg);
- font-weight: bold;
- }
+ .primary-stat__counter {
+ font-size: var(--font-size-lg);
+ font-weight: bold;
+ }
- &.no-margin {
- margin: 0;
- }
+ &.no-margin {
+ margin: 0;
+ }
}
.project-list {
- width: 100%;
- gap: var(--spacing-card-md);
- overflow: hidden;
+ width: 100%;
+ gap: var(--spacing-card-md);
+ overflow: hidden;
- &:not(:first-child) {
- margin-top: var(--spacing-card-md);
- }
+ &:not(:first-child) {
+ margin-top: var(--spacing-card-md);
+ }
- &:not(:empty) {
- margin-bottom: var(--spacing-card-md);
- }
+ &:not(:empty) {
+ margin-bottom: var(--spacing-card-md);
+ }
}
.project-list.display-mode--list {
- display: flex;
- flex-direction: column;
+ display: flex;
+ flex-direction: column;
}
.project-list.display-mode--gallery {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
- @media screen and (max-width: 750px) {
- grid-template-columns: repeat(1, minmax(0, 1fr));
- }
+ @media screen and (max-width: 750px) {
+ grid-template-columns: repeat(1, minmax(0, 1fr));
+ }
}
.project-list.display-mode--grid {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
- @media screen and (max-width: 80rem) {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
+ @media screen and (max-width: 80rem) {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
- @media screen and (max-width: 1024px) {
- grid-template-columns: repeat(3, minmax(0, 1fr));
- }
+ @media screen and (max-width: 1024px) {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
- @media screen and (max-width: 860px) {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
+ @media screen and (max-width: 860px) {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
- @media screen and (max-width: 550px) {
- display: flex;
- flex-direction: column;
- }
+ @media screen and (max-width: 550px) {
+ display: flex;
+ flex-direction: column;
+ }
}
.wrap-as-needed {
- overflow-wrap: break-word;
- word-wrap: break-word;
- word-break: break-word;
- -webkit-hyphens: auto;
- hyphens: auto;
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ word-break: break-word;
+ -webkit-hyphens: auto;
+ hyphens: auto;
}
.sr-only {
- position: absolute;
- width: 0;
- height: 0;
- overflow: hidden;
+ position: absolute;
+ width: 0;
+ height: 0;
+ overflow: hidden;
}
.backed-svg {
- --size: 2.5rem;
- border-radius: var(--size-rounded-sm);
- background-color: var(--color-button-bg);
- width: var(--size);
- height: var(--size);
- display: inline-flex;
- justify-content: center;
- align-items: center;
-
- svg {
- width: calc(0.6 * var(--size));
- height: calc(0.6 * var(--size));
- }
-
- &.circle {
- border-radius: 50%;
- }
-
- &.raised {
- background-color: var(--color-raised-bg);
- }
+ --size: 2.5rem;
+ border-radius: var(--size-rounded-sm);
+ background-color: var(--color-button-bg);
+ width: var(--size);
+ height: var(--size);
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+
+ svg {
+ width: calc(0.6 * var(--size));
+ height: calc(0.6 * var(--size));
+ }
+
+ &.circle {
+ border-radius: 50%;
+ }
+
+ &.raised {
+ background-color: var(--color-raised-bg);
+ }
}
a.iconified-link,
a.iconified-stacked-link {
- display: contents;
+ display: contents;
- .space {
- opacity: 0;
- }
+ .space {
+ opacity: 0;
+ }
- .title {
- font-weight: bold;
- }
+ .title {
+ font-weight: bold;
+ }
- .stacked {
- display: inline-flex;
- flex-direction: column;
- }
+ .stacked {
+ display: inline-flex;
+ flex-direction: column;
+ }
- &:focus-visible .title,
- &:hover .title {
- text-decoration: underline;
- filter: var(--hover-filter);
- }
+ &:focus-visible .title,
+ &:hover .title {
+ text-decoration: underline;
+ filter: var(--hover-filter);
+ }
- &:active .title {
- filter: var(--active-filter);
- }
+ &:active .title {
+ filter: var(--active-filter);
+ }
}
a.iconified-link {
- &:focus-visible span,
- &:hover span {
- text-decoration: underline;
- filter: var(--hover-filter);
- }
+ &:focus-visible span,
+ &:hover span {
+ text-decoration: underline;
+ filter: var(--hover-filter);
+ }
- &:active span {
- filter: var(--active-filter);
- }
+ &:active span {
+ filter: var(--active-filter);
+ }
}
a.subtle-link {
- &:focus-visible,
- &:hover {
- text-decoration: underline;
- filter: var(--hover-filter);
- }
+ &:focus-visible,
+ &:hover {
+ text-decoration: underline;
+ filter: var(--hover-filter);
+ }
- &:active {
- filter: var(--active-filter);
- }
+ &:active {
+ filter: var(--active-filter);
+ }
}
.inline-svg svg,
svg.inline-svg {
- vertical-align: middle;
+ vertical-align: middle;
}
// START STUFF FOR OMORPHIA
.experimental-styles-within {
- .tag-list {
- display: flex;
- flex-wrap: wrap;
- gap: var(--gap-4);
- }
-
- .tag-list__item {
- background-color: var(--_bg-color, var(--color-button-bg));
- padding: var(--gap-4) var(--gap-8);
- border-radius: var(--radius-max);
- font-weight: var(--weight-bold);
- font-size: var(--text-14);
- display: flex;
- gap: var(--gap-4);
- align-items: center;
- vertical-align: middle;
- color: var(--_color, var(--color-secondary));
-
- svg {
- width: var(--icon-14);
- height: var(--icon-14);
- display: flex;
+ .tag-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--gap-4);
}
- }
-
- .status-list {
- display: flex;
- flex-direction: column;
- gap: var(--gap-8);
- padding-left: var(--gap-6);
-
- color: var(--color-base);
- font-weight: var(--weight-bold);
- }
- .status-list__item {
- display: flex;
- align-items: center;
- gap: var(--gap-4);
-
- svg {
- width: var(--icon-16);
- height: var(--icon-16);
- margin-right: var(--gap-4);
+ .tag-list__item {
+ background-color: var(--_bg-color, var(--color-button-bg));
+ padding: var(--gap-4) var(--gap-8);
+ border-radius: var(--radius-max);
+ font-weight: var(--weight-bold);
+ font-size: var(--text-14);
+ display: flex;
+ gap: var(--gap-4);
+ align-items: center;
+ vertical-align: middle;
+ color: var(--_color, var(--color-secondary));
+
+ svg {
+ width: var(--icon-14);
+ height: var(--icon-14);
+ display: flex;
+ }
}
- span {
- color: var(--color-secondary);
- font-style: italic;
- font-weight: var(--weight-normal);
- }
- }
+ .status-list {
+ display: flex;
+ flex-direction: column;
+ gap: var(--gap-8);
+ padding-left: var(--gap-6);
- .status-list__item--color-green svg {
- color: var(--color-green);
- }
+ color: var(--color-base);
+ font-weight: var(--weight-bold);
+ }
- .status-list__item--color-orange svg {
- color: var(--color-orange);
- }
+ .status-list__item {
+ display: flex;
+ align-items: center;
+ gap: var(--gap-4);
- .status-list__item--color-red svg {
- color: var(--color-red);
- }
+ svg {
+ width: var(--icon-16);
+ height: var(--icon-16);
+ margin-right: var(--gap-4);
+ }
- .status-list__item--color-blue svg {
- color: var(--color-blue);
- }
+ span {
+ color: var(--color-secondary);
+ font-style: italic;
+ font-weight: var(--weight-normal);
+ }
+ }
- .status-list__item--color-purple svg {
- color: var(--color-purple);
- }
+ .status-list__item--color-green svg {
+ color: var(--color-green);
+ }
- &.flex-card,
- .flex-card {
- display: flex;
- flex-direction: column;
- gap: var(--gap-12);
- padding: var(--gap-16) var(--gap-24);
+ .status-list__item--color-orange svg {
+ color: var(--color-orange);
+ }
- h2 {
- font-size: var(--text-18);
- font-weight: var(--weight-extrabold);
- color: var(--color-contrast);
- line-height: initial;
- margin: 0;
+ .status-list__item--color-red svg {
+ color: var(--color-red);
}
- h3 {
- font-size: var(--text-16);
- font-weight: var(--weight-bold);
- color: var(--color-base);
- margin: 0;
+ .status-list__item--color-blue svg {
+ color: var(--color-blue);
}
- > section {
- display: flex;
- flex-direction: column;
- gap: var(--gap-8);
+ .status-list__item--color-purple svg {
+ color: var(--color-purple);
}
- }
- .list-style {
- display: flex;
- flex-direction: column;
- gap: var(--gap-12);
- font-weight: var(--weight-bold);
+ &.flex-card,
+ .flex-card {
+ display: flex;
+ flex-direction: column;
+ gap: var(--gap-12);
+ padding: var(--gap-16) var(--gap-24);
+
+ h2 {
+ font-size: var(--text-18);
+ font-weight: var(--weight-extrabold);
+ color: var(--color-contrast);
+ line-height: initial;
+ margin: 0;
+ }
+
+ h3 {
+ font-size: var(--text-16);
+ font-weight: var(--weight-bold);
+ color: var(--color-base);
+ margin: 0;
+ }
- hr {
- width: 100%;
- border-color: var(--color-button-border);
- margin-block: var(--gap-2);
+ > section {
+ display: flex;
+ flex-direction: column;
+ gap: var(--gap-8);
+ }
}
- }
- .iconified-list-item {
- display: flex;
- gap: var(--gap-8);
- vertical-align: middle;
- align-items: center;
- width: fit-content;
+ .list-style {
+ display: flex;
+ flex-direction: column;
+ gap: var(--gap-12);
+ font-weight: var(--weight-bold);
- svg {
- width: var(--icon-16);
- height: var(--icon-16);
+ hr {
+ width: 100%;
+ border-color: var(--color-button-border);
+ margin-block: var(--gap-2);
+ }
}
- > svg:first-child {
- flex-shrink: 0;
+ .iconified-list-item {
+ display: flex;
+ gap: var(--gap-8);
+ vertical-align: middle;
+ align-items: center;
+ width: fit-content;
+
+ svg {
+ width: var(--icon-16);
+ height: var(--icon-16);
+ }
+
+ > svg:first-child {
+ flex-shrink: 0;
+ }
}
- }
- .links-list {
- @extend .list-style;
+ .links-list {
+ @extend .list-style;
- > a {
- @extend .iconified-list-item;
+ > a {
+ @extend .iconified-list-item;
- &:hover {
- text-decoration: underline;
- }
+ &:hover {
+ text-decoration: underline;
+ }
+ }
}
- }
- .details-list {
- @extend .list-style;
- }
+ .details-list {
+ @extend .list-style;
+ }
- .details-list__item {
- @extend .iconified-list-item;
+ .details-list__item {
+ @extend .iconified-list-item;
- .details-list__item__text--style-secondary {
- color: var(--color-secondary);
- font-weight: var(--weight-normal);
- font-size: var(--text-14);
+ .details-list__item__text--style-secondary {
+ color: var(--color-secondary);
+ font-weight: var(--weight-normal);
+ font-size: var(--text-14);
+ }
}
- }
}
diff --git a/apps/frontend/src/assets/styles/global.scss b/apps/frontend/src/assets/styles/global.scss
index cefb9460c5..b7330d85c0 100644
--- a/apps/frontend/src/assets/styles/global.scss
+++ b/apps/frontend/src/assets/styles/global.scss
@@ -1,558 +1,558 @@
html {
- @extend .dark-mode;
- --dark-color-text: #b0bac5;
- --dark-color-text-dark: #ecf9fb;
- --color-text-secondary: var(--color-icon);
+ @extend .dark-mode;
+ --dark-color-text: #b0bac5;
+ --dark-color-text-dark: #ecf9fb;
+ --color-text-secondary: var(--color-icon);
}
// TO BE MOVED TO OMORPHIA
:root {
- --gap-2: 0.125rem;
- --gap-4: calc(2 * var(--gap-2));
- --gap-6: calc(3 * var(--gap-2));
- --gap-8: calc(2 * var(--gap-4));
- --gap-12: calc(3 * var(--gap-4));
- --gap-16: calc(2 * var(--gap-8));
- --gap-24: calc(3 * var(--gap-8));
-
- --radius-card: 1rem;
- --radius-max: 999999999px;
-
- --radius-btn: 0.75rem;
- --radius-btn-large: 1rem;
- --radius-btn-circle: var(--radius-max);
-
- --text-14: 0.875rem;
- --text-16: 1rem;
- --text-18: 1.125rem;
- --text-24: 1.5rem;
- --text-32: 2rem;
-
- --weight-normal: 500; // used for general body text
- --weight-bold: 600; // used for text needing extra emphasis
- --weight-extrabold: 800; // used for main titles and headings
-
- --icon-14: 0.875rem; // used for icons inside a small container alongside a label
- --icon-16: 1rem; // used for normal-sized icons alongside a label
- --icon-20: 1.25rem; // used for icons in normal sized buttons
- --icon-24: 1.5rem; // used for icons that are used as a primary label or in large buttons
- --icon-32: 2rem;
-
- interpolate-size: allow-keywords;
+ --gap-2: 0.125rem;
+ --gap-4: calc(2 * var(--gap-2));
+ --gap-6: calc(3 * var(--gap-2));
+ --gap-8: calc(2 * var(--gap-4));
+ --gap-12: calc(3 * var(--gap-4));
+ --gap-16: calc(2 * var(--gap-8));
+ --gap-24: calc(3 * var(--gap-8));
+
+ --radius-card: 1rem;
+ --radius-max: 999999999px;
+
+ --radius-btn: 0.75rem;
+ --radius-btn-large: 1rem;
+ --radius-btn-circle: var(--radius-max);
+
+ --text-14: 0.875rem;
+ --text-16: 1rem;
+ --text-18: 1.125rem;
+ --text-24: 1.5rem;
+ --text-32: 2rem;
+
+ --weight-normal: 500; // used for general body text
+ --weight-bold: 600; // used for text needing extra emphasis
+ --weight-extrabold: 800; // used for main titles and headings
+
+ --icon-14: 0.875rem; // used for icons inside a small container alongside a label
+ --icon-16: 1rem; // used for normal-sized icons alongside a label
+ --icon-20: 1.25rem; // used for icons in normal sized buttons
+ --icon-24: 1.5rem; // used for icons that are used as a primary label or in large buttons
+ --icon-32: 2rem;
+
+ interpolate-size: allow-keywords;
}
.light-mode {
- --color-secondary: #6b7280;
- --color-icon: var(--color-secondary);
- --color-text: hsl(221, 39%, 11%);
- --color-text-inactive: hsl(215, 14%, 34%);
- --color-text-dark: #1a202c;
- --color-heading: #2c313d;
- --color-bg: #e5e7eb;
- --color-raised-bg: #ffffff;
- --color-divider: hsl(220, 13%, 91%);
- --color-divider-dark: #c8cdd3;
-
- --color-text-inverted: var(--color-bg);
- --color-bg-inverted: var(--color-text);
-
- --color-brand: var(--color-green);
- --color-brand-highlight: rgba(0, 175, 92, 0.25);
- --color-brand-shadow: rgba(0, 175, 92, 0.7);
- --color-brand-inverted: #ffffff;
-
- --tab-underline-hovered: #e2e8f0;
-
- --color-button-bg: hsl(220, 13%, 91%);
- --color-button-text: var(--color-text-dark);
- --color-button-bg-hover: #d9dce0;
- --color-button-text-hover: #1b1e24;
- --color-button-bg-active: #c3c6cb;
- --color-button-text-active: var(--color-button-text-hover);
-
- --color-toggle-handle: var(--color-icon);
-
- --color-dropdown-bg: var(--color-button-bg);
- --color-dropdown-text: var(--color-button-text);
-
- --color-code-bg: var(--color-bg);
- --color-code-text: var(--color-text-dark);
-
- --color-kbd-shadow: rgba(0, 0, 0, 0.25);
-
- --color-ad: #d6e6f9;
- --color-ad-raised: #b1c8e4;
- --color-ad-contrast: var(--color-text);
- --color-ad-highlight: #088cdb;
-
- --color-grey-link: var(--color-text);
- --color-grey-link-hover: var(--color-heading);
- --color-grey-link-active: var(--color-text-dark);
- --color-link: #0d60bb;
- --color-link-hover: #1a76e7;
- --color-link-active: #146fd7;
-
- --color-warning-bg: hsl(355, 70%, 88%);
- --color-warning-text: hsl(342, 70%, 35%);
-
- --color-warning-banner-text: hsl(0, 11%, 16%);
- --color-warning-banner-bg: hsl(0, 100%, 95%);
- --color-warning-banner-side: hsl(357, 78%, 40%);
-
- --color-info-banner-text: var(--color-text);
- --color-info-banner-bg: var(--color-ad);
- --color-info-banner-side: var(--color-blue);
-
- --color-block-quote: var(--color-tooltip-bg);
- --color-header-underline: var(--color-divider-dark);
- --color-hr: var(--color-text);
-
- --color-table-border: #dfe2e5;
- --color-table-alternate-row: #f2f4f7;
-
- --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:
- 0.3px 0.5px 0.6px hsl(var(--shadow-color) / 0.15),
- 1px 2px 2.2px -1.7px hsl(var(--shadow-color) / 0.12),
- 4.4px 8.8px 9.7px -3.4px hsl(var(--shadow-color) / 0.09);
- --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-card: rgba(50, 50, 100, 0.1) 0px 2px 4px 0px;
-
- --landing-maze-bg: url("https://cdn.modrinth.com/landing-new/landing-light.webp");
- --landing-maze-gradient-bg: url("https://cdn.modrinth.com/landing-new/landing-lower-light.webp");
- --landing-maze-outer-bg: linear-gradient(180deg, #f0f0f0 0%, #ffffff 100%);
-
- --landing-color-heading: #000;
- --landing-color-subheading: #3a3f45;
-
- --landing-transition-gradient-start: rgba(255, 255, 255, 0);
- --landing-transition-gradient-end: #ffffff;
- --landing-hover-card-gradient: radial-gradient(
- 50% 50% at 50% 50%,
- #fff 0%,
- rgba(204, 204, 204, 0.77) 100%
- );
- --landing-border-gradient: linear-gradient(
- to bottom right,
- rgba(129, 137, 175, 0.75) 0%,
- rgba(66, 71, 97, 0.34) 100%
- );
- --landing-border-color: rgba(129, 137, 175, 0.55);
- --landing-creator-gradient: linear-gradient(180deg, #f8f8f8 0%, #f8f8f8 63.19%);
-
- --landing-blob-gradient: radial-gradient(
- 50% 50% at 50% 50%,
- rgba(255, 255, 255, 0.35) 0%,
- rgba(255, 255, 255, 0.2695) 100%
- );
- --landing-blob-shadow:
- 2px 2px 12px rgba(0, 0, 0, 0.16), inset 2px 2px 64px rgba(255, 255, 255, 0.45);
-
- --landing-card-bg: rgba(255, 255, 255, 0.8);
- --landing-card-shadow: 2px 2px 12px rgba(0, 0, 0, 0.16);
-
- --landing-blue-label: #0098ba;
- --landing-blue-label-bg: rgba(0, 177, 216, 0.15);
- --landing-green-label: #00a936;
- --landing-green-label-bg: rgba(0, 216, 69, 0.15);
-
- --landing-raw-bg: #fff;
-
- --banner-error-bg: #fee2e2;
- --banner-error-text: #991b1b;
- --banner-error-border: #ef4444;
-
- --banner-warning-bg: #ffedd5;
- --banner-warning-text: #713f12;
- --banner-warning-border: #f97316;
-
- --banner-info-bg: #dbeafe;
- --banner-info-text: #1e3a8a;
- --banner-info-border: #3b82f6;
+ --color-secondary: #6b7280;
+ --color-icon: var(--color-secondary);
+ --color-text: hsl(221, 39%, 11%);
+ --color-text-inactive: hsl(215, 14%, 34%);
+ --color-text-dark: #1a202c;
+ --color-heading: #2c313d;
+ --color-bg: #e5e7eb;
+ --color-raised-bg: #ffffff;
+ --color-divider: hsl(220, 13%, 91%);
+ --color-divider-dark: #c8cdd3;
+
+ --color-text-inverted: var(--color-bg);
+ --color-bg-inverted: var(--color-text);
+
+ --color-brand: var(--color-green);
+ --color-brand-highlight: rgba(0, 175, 92, 0.25);
+ --color-brand-shadow: rgba(0, 175, 92, 0.7);
+ --color-brand-inverted: #ffffff;
+
+ --tab-underline-hovered: #e2e8f0;
+
+ --color-button-bg: hsl(220, 13%, 91%);
+ --color-button-text: var(--color-text-dark);
+ --color-button-bg-hover: #d9dce0;
+ --color-button-text-hover: #1b1e24;
+ --color-button-bg-active: #c3c6cb;
+ --color-button-text-active: var(--color-button-text-hover);
+
+ --color-toggle-handle: var(--color-icon);
+
+ --color-dropdown-bg: var(--color-button-bg);
+ --color-dropdown-text: var(--color-button-text);
+
+ --color-code-bg: var(--color-bg);
+ --color-code-text: var(--color-text-dark);
+
+ --color-kbd-shadow: rgba(0, 0, 0, 0.25);
+
+ --color-ad: #d6e6f9;
+ --color-ad-raised: #b1c8e4;
+ --color-ad-contrast: var(--color-text);
+ --color-ad-highlight: #088cdb;
+
+ --color-grey-link: var(--color-text);
+ --color-grey-link-hover: var(--color-heading);
+ --color-grey-link-active: var(--color-text-dark);
+ --color-link: #0d60bb;
+ --color-link-hover: #1a76e7;
+ --color-link-active: #146fd7;
+
+ --color-warning-bg: hsl(355, 70%, 88%);
+ --color-warning-text: hsl(342, 70%, 35%);
+
+ --color-warning-banner-text: hsl(0, 11%, 16%);
+ --color-warning-banner-bg: hsl(0, 100%, 95%);
+ --color-warning-banner-side: hsl(357, 78%, 40%);
+
+ --color-info-banner-text: var(--color-text);
+ --color-info-banner-bg: var(--color-ad);
+ --color-info-banner-side: var(--color-blue);
+
+ --color-block-quote: var(--color-tooltip-bg);
+ --color-header-underline: var(--color-divider-dark);
+ --color-hr: var(--color-text);
+
+ --color-table-border: #dfe2e5;
+ --color-table-alternate-row: #f2f4f7;
+
+ --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:
+ 0.3px 0.5px 0.6px hsl(var(--shadow-color) / 0.15),
+ 1px 2px 2.2px -1.7px hsl(var(--shadow-color) / 0.12),
+ 4.4px 8.8px 9.7px -3.4px hsl(var(--shadow-color) / 0.09);
+ --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-card: rgba(50, 50, 100, 0.1) 0px 2px 4px 0px;
+
+ --landing-maze-bg: url('https://cdn.modrinth.com/landing-new/landing-light.webp');
+ --landing-maze-gradient-bg: url('https://cdn.modrinth.com/landing-new/landing-lower-light.webp');
+ --landing-maze-outer-bg: linear-gradient(180deg, #f0f0f0 0%, #ffffff 100%);
+
+ --landing-color-heading: #000;
+ --landing-color-subheading: #3a3f45;
+
+ --landing-transition-gradient-start: rgba(255, 255, 255, 0);
+ --landing-transition-gradient-end: #ffffff;
+ --landing-hover-card-gradient: radial-gradient(
+ 50% 50% at 50% 50%,
+ #fff 0%,
+ rgba(204, 204, 204, 0.77) 100%
+ );
+ --landing-border-gradient: linear-gradient(
+ to bottom right,
+ rgba(129, 137, 175, 0.75) 0%,
+ rgba(66, 71, 97, 0.34) 100%
+ );
+ --landing-border-color: rgba(129, 137, 175, 0.55);
+ --landing-creator-gradient: linear-gradient(180deg, #f8f8f8 0%, #f8f8f8 63.19%);
+
+ --landing-blob-gradient: radial-gradient(
+ 50% 50% at 50% 50%,
+ rgba(255, 255, 255, 0.35) 0%,
+ rgba(255, 255, 255, 0.2695) 100%
+ );
+ --landing-blob-shadow:
+ 2px 2px 12px rgba(0, 0, 0, 0.16), inset 2px 2px 64px rgba(255, 255, 255, 0.45);
+
+ --landing-card-bg: rgba(255, 255, 255, 0.8);
+ --landing-card-shadow: 2px 2px 12px rgba(0, 0, 0, 0.16);
+
+ --landing-blue-label: #0098ba;
+ --landing-blue-label-bg: rgba(0, 177, 216, 0.15);
+ --landing-green-label: #00a936;
+ --landing-green-label-bg: rgba(0, 216, 69, 0.15);
+
+ --landing-raw-bg: #fff;
+
+ --banner-error-bg: #fee2e2;
+ --banner-error-text: #991b1b;
+ --banner-error-border: #ef4444;
+
+ --banner-warning-bg: #ffedd5;
+ --banner-warning-text: #713f12;
+ --banner-warning-border: #f97316;
+
+ --banner-info-bg: #dbeafe;
+ --banner-info-text: #1e3a8a;
+ --banner-info-border: #3b82f6;
}
.dark,
.dark-mode,
.oled-mode,
.retro-mode {
- --color-secondary: #96a2b0;
- --color-icon: var(--color-secondary);
- --color-text: var(--dark-color-text);
- --color-text-inactive: #929aa3;
- --color-text-dark: var(--dark-color-text-dark);
- --color-heading: #c4cfdd;
- --color-bg: #16181c;
- --color-raised-bg: #26292f;
- --color-divider: #474b54;
- --color-divider-dark: #646c75;
-
- --color-text-inverted: var(--color-bg);
- --color-bg-inverted: var(--color-text);
-
- --color-brand: var(--color-green);
- --color-brand-highlight: rgba(27, 217, 106, 0.25);
- --color-brand-shadow: rgba(27, 217, 106, 0.7);
- --color-brand-inverted: #000;
-
- --tab-underline-hovered: #414146;
-
- --color-button-bg: hsl(222, 13%, 30%);
- --color-button-text: var(--color-text);
- --color-button-bg-hover: #494f58;
- --color-button-text-hover: #ffffff;
- --color-button-bg-active: #616570;
- --color-button-text-active: var(--color-button-text-hover);
-
- --color-toggle-handle: var(--color-button-text);
-
- --color-dropdown-bg: var(--color-button-bg);
- --color-dropdown-text: var(--color-button-text);
-
- --color-code-bg: var(--color-button-bg);
- --color-code-text: var(--color-text-dark);
-
- --color-kbd-shadow: rgba(0, 0, 0, 0.35);
-
- --color-ad: #1f324a;
- --color-ad-raised: #2e4057;
- --color-ad-contrast: var(--color-text);
- --color-ad-highlight: #088cdb;
-
- --color-link: #74b6f3;
- --color-link-hover: #92c0f5;
- --color-link-active: #b5d5fd;
-
- --color-warning-bg: hsl(355, 70%, 20%);
- --color-warning-text: hsl(342, 70%, 75%);
-
- --color-warning-banner-text: hsl(0, 100%, 96%);
- --color-warning-banner-bg: hsl(356, 18%, 18%);
- --color-warning-banner-side: hsl(357, 78%, 40%);
-
- --color-info-banner-text: var(--color-text);
- --color-info-banner-bg: var(--color-ad);
- --color-info-banner-side: var(--color-blue);
-
- --color-block-quote: var(--color-code-bg);
- --color-header-underline: var(--color-divider-dark);
- --color-hr: var(--color-text);
-
- --color-table-border: #4f5864;
- --color-table-alternate-row: #202228;
-
- --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-card: rgba(0, 0, 0, 0.25) 0px 2px 4px 0px;
-
- --landing-maze-bg: url("https://cdn.modrinth.com/landing-new/landing.webp");
- --landing-maze-gradient-bg:
- linear-gradient(0deg, #31375f 0%, rgba(8, 14, 55, 0) 100%),
- url("https://cdn.modrinth.com/landing-new/landing-lower.webp");
- --landing-maze-outer-bg: linear-gradient(180deg, #06060d 0%, #000000 100%);
-
- --landing-color-heading: #fff;
- --landing-color-subheading: #afb6be;
-
- --landing-transition-gradient-start: rgba(14, 16, 32, 0);
- --landing-transition-gradient-end: #060a1c;
- --landing-hover-card-gradient: radial-gradient(
- 50% 50% at 50% 50%,
- #2c304f 0%,
- rgba(32, 35, 50, 0.77) 100%
- );
- --landing-border-gradient: linear-gradient(
- to bottom right,
- rgba(168, 177, 221, 0.75) 0%,
- rgba(168, 177, 221, 0.18) 100%
- );
- --landing-border-color: rgba(168, 177, 221, 0.55);
- --landing-creator-gradient: linear-gradient(180deg, #000000 0%, #0e101d 100%);
-
- --landing-blob-gradient: radial-gradient(
- 50% 50% at 50% 50%,
- rgba(44, 48, 79, 0.35) 0%,
- rgba(32, 35, 50, 0.2695) 100%
- );
- --landing-blob-shadow:
- 2px 2px 12px rgba(0, 0, 0, 0.16), inset 2px 2px 64px rgba(57, 61, 94, 0.45);
-
- --landing-card-bg: rgba(59, 63, 85, 0.15);
- --landing-card-shadow: 2px 2px 12px rgba(0, 0, 0, 0.16);
-
- --landing-blue-label: #10c0e7;
- --landing-blue-label-bg: rgba(0, 177, 216, 0.15);
- --landing-green-label: #00d845;
- --landing-green-label-bg: rgba(0, 216, 69, 0.15);
-
- --landing-raw-bg: #000;
-
- --hover-filter: brightness(120%);
- --active-filter: brightness(140%);
-
- --banner-error-bg: #4c1515;
- --banner-error-text: #fee2e2;
- --banner-error-border: #7f1d1d;
-
- --banner-warning-bg: #4a2a0a;
- --banner-warning-text: #ffe6c0;
- --banner-warning-border: #b54708;
-
- --banner-info-bg: #1e2a44;
- --banner-info-text: #dbeafe;
- --banner-info-border: #2563eb;
+ --color-secondary: #96a2b0;
+ --color-icon: var(--color-secondary);
+ --color-text: var(--dark-color-text);
+ --color-text-inactive: #929aa3;
+ --color-text-dark: var(--dark-color-text-dark);
+ --color-heading: #c4cfdd;
+ --color-bg: #16181c;
+ --color-raised-bg: #26292f;
+ --color-divider: #474b54;
+ --color-divider-dark: #646c75;
+
+ --color-text-inverted: var(--color-bg);
+ --color-bg-inverted: var(--color-text);
+
+ --color-brand: var(--color-green);
+ --color-brand-highlight: rgba(27, 217, 106, 0.25);
+ --color-brand-shadow: rgba(27, 217, 106, 0.7);
+ --color-brand-inverted: #000;
+
+ --tab-underline-hovered: #414146;
+
+ --color-button-bg: hsl(222, 13%, 30%);
+ --color-button-text: var(--color-text);
+ --color-button-bg-hover: #494f58;
+ --color-button-text-hover: #ffffff;
+ --color-button-bg-active: #616570;
+ --color-button-text-active: var(--color-button-text-hover);
+
+ --color-toggle-handle: var(--color-button-text);
+
+ --color-dropdown-bg: var(--color-button-bg);
+ --color-dropdown-text: var(--color-button-text);
+
+ --color-code-bg: var(--color-button-bg);
+ --color-code-text: var(--color-text-dark);
+
+ --color-kbd-shadow: rgba(0, 0, 0, 0.35);
+
+ --color-ad: #1f324a;
+ --color-ad-raised: #2e4057;
+ --color-ad-contrast: var(--color-text);
+ --color-ad-highlight: #088cdb;
+
+ --color-link: #74b6f3;
+ --color-link-hover: #92c0f5;
+ --color-link-active: #b5d5fd;
+
+ --color-warning-bg: hsl(355, 70%, 20%);
+ --color-warning-text: hsl(342, 70%, 75%);
+
+ --color-warning-banner-text: hsl(0, 100%, 96%);
+ --color-warning-banner-bg: hsl(356, 18%, 18%);
+ --color-warning-banner-side: hsl(357, 78%, 40%);
+
+ --color-info-banner-text: var(--color-text);
+ --color-info-banner-bg: var(--color-ad);
+ --color-info-banner-side: var(--color-blue);
+
+ --color-block-quote: var(--color-code-bg);
+ --color-header-underline: var(--color-divider-dark);
+ --color-hr: var(--color-text);
+
+ --color-table-border: #4f5864;
+ --color-table-alternate-row: #202228;
+
+ --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-card: rgba(0, 0, 0, 0.25) 0px 2px 4px 0px;
+
+ --landing-maze-bg: url('https://cdn.modrinth.com/landing-new/landing.webp');
+ --landing-maze-gradient-bg:
+ linear-gradient(0deg, #31375f 0%, rgba(8, 14, 55, 0) 100%),
+ url('https://cdn.modrinth.com/landing-new/landing-lower.webp');
+ --landing-maze-outer-bg: linear-gradient(180deg, #06060d 0%, #000000 100%);
+
+ --landing-color-heading: #fff;
+ --landing-color-subheading: #afb6be;
+
+ --landing-transition-gradient-start: rgba(14, 16, 32, 0);
+ --landing-transition-gradient-end: #060a1c;
+ --landing-hover-card-gradient: radial-gradient(
+ 50% 50% at 50% 50%,
+ #2c304f 0%,
+ rgba(32, 35, 50, 0.77) 100%
+ );
+ --landing-border-gradient: linear-gradient(
+ to bottom right,
+ rgba(168, 177, 221, 0.75) 0%,
+ rgba(168, 177, 221, 0.18) 100%
+ );
+ --landing-border-color: rgba(168, 177, 221, 0.55);
+ --landing-creator-gradient: linear-gradient(180deg, #000000 0%, #0e101d 100%);
+
+ --landing-blob-gradient: radial-gradient(
+ 50% 50% at 50% 50%,
+ rgba(44, 48, 79, 0.35) 0%,
+ rgba(32, 35, 50, 0.2695) 100%
+ );
+ --landing-blob-shadow:
+ 2px 2px 12px rgba(0, 0, 0, 0.16), inset 2px 2px 64px rgba(57, 61, 94, 0.45);
+
+ --landing-card-bg: rgba(59, 63, 85, 0.15);
+ --landing-card-shadow: 2px 2px 12px rgba(0, 0, 0, 0.16);
+
+ --landing-blue-label: #10c0e7;
+ --landing-blue-label-bg: rgba(0, 177, 216, 0.15);
+ --landing-green-label: #00d845;
+ --landing-green-label-bg: rgba(0, 216, 69, 0.15);
+
+ --landing-raw-bg: #000;
+
+ --hover-filter: brightness(120%);
+ --active-filter: brightness(140%);
+
+ --banner-error-bg: #4c1515;
+ --banner-error-text: #fee2e2;
+ --banner-error-border: #7f1d1d;
+
+ --banner-warning-bg: #4a2a0a;
+ --banner-warning-text: #ffe6c0;
+ --banner-warning-border: #b54708;
+
+ --banner-info-bg: #1e2a44;
+ --banner-info-text: #dbeafe;
+ --banner-info-border: #2563eb;
}
.oled-mode {
- --color-bg: #000000;
- --color-raised-bg: #101013;
+ --color-bg: #000000;
+ --color-raised-bg: #101013;
- --color-button-bg: #222329;
- --color-button-bg-hover: #2d2d32;
- --color-button-bg-active: #3c3c40;
- --color-table-alternate-row: #19191c;
- --color-divider-dark: #2c3134;
+ --color-button-bg: #222329;
+ --color-button-bg-hover: #2d2d32;
+ --color-button-bg-active: #3c3c40;
+ --color-table-alternate-row: #19191c;
+ --color-divider-dark: #2c3134;
- --color-warning-banner-bg: hsl(0, 45%, 11%);
- --color-ad: #0d1828;
+ --color-warning-banner-bg: hsl(0, 45%, 11%);
+ --color-ad: #0d1828;
}
.retro-mode {
- --color-bg: #191917;
- --color-raised-bg: #1d1e1b;
- --color-button-bg: #3a3b38;
- --color-base: #c3c4b3;
- --color-secondary: #777a74;
- --color-contrast: #e6e2d1;
-
- --color-brand: #4d9227;
- --color-brand-highlight: #25421e;
- --color-ad: var(--color-brand-highlight);
- --color-ad-raised: var(--color-brand);
- --color-ad-contrast: black;
- --color-ad-highlight: var(--color-brand);
-
- --color-red: rgb(232, 32, 13);
- --color-orange: rgb(232, 141, 13);
- --color-green: rgb(60, 219, 54);
- --color-blue: rgb(9, 159, 239);
- --color-purple: rgb(139, 129, 230);
- --color-gray: #718096;
-
- --color-red-highlight: rgba(232, 32, 13, 0.25);
- --color-orange-highlight: rgba(232, 141, 13, 0.25);
- --color-green-highlight: rgba(60, 219, 54, 0.25);
- --color-blue-highlight: rgba(9, 159, 239, 0.25);
- --color-purple-highlight: rgba(139, 129, 230, 0.25);
- --color-gray-highlight: rgba(113, 128, 150, 0.25);
+ --color-bg: #191917;
+ --color-raised-bg: #1d1e1b;
+ --color-button-bg: #3a3b38;
+ --color-base: #c3c4b3;
+ --color-secondary: #777a74;
+ --color-contrast: #e6e2d1;
+
+ --color-brand: #4d9227;
+ --color-brand-highlight: #25421e;
+ --color-ad: var(--color-brand-highlight);
+ --color-ad-raised: var(--color-brand);
+ --color-ad-contrast: black;
+ --color-ad-highlight: var(--color-brand);
+
+ --color-red: rgb(232, 32, 13);
+ --color-orange: rgb(232, 141, 13);
+ --color-green: rgb(60, 219, 54);
+ --color-blue: rgb(9, 159, 239);
+ --color-purple: rgb(139, 129, 230);
+ --color-gray: #718096;
+
+ --color-red-highlight: rgba(232, 32, 13, 0.25);
+ --color-orange-highlight: rgba(232, 141, 13, 0.25);
+ --color-green-highlight: rgba(60, 219, 54, 0.25);
+ --color-blue-highlight: rgba(9, 159, 239, 0.25);
+ --color-purple-highlight: rgba(139, 129, 230, 0.25);
+ --color-gray-highlight: rgba(113, 128, 150, 0.25);
}
body {
- // Defaults
- background-color: var(--color-bg);
- color: var(--color-text);
- --font-standard:
- Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto, Cantarell,
- Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
- --mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
- font-family: var(--font-standard);
- font-size: 16px;
- font-weight: var(--font-weight-regular);
- margin: 0;
- padding: 0;
-
- // Rounding sizes
- --size-rounded-xs: 0.5rem;
- --size-rounded-sm: 0.75rem;
- --size-rounded-md: 1rem;
- --size-rounded-lg: 1.25rem;
-
- --size-rounded-max: 999999999px;
- --size-rounded-card: 1rem;
- --size-rounded-icon: 1rem;
- --size-rounded-control: 0.25rem;
- --size-rounded-tooltip: 0.25rem;
-
- --size-navbar-height: 3.5rem;
- --size-mobile-navbar-height: 3.5rem;
- --size-mobile-navbar-height-expanded: 13.75rem;
-
- --spacing-card-lg: 1.5rem;
- --spacing-card-bg: 1rem;
- --spacing-card-md: 0.75rem;
- --spacing-card-sm: 0.5rem;
- --spacing-card-xs: 0.25rem;
-
- // Font Sizes
- --font-size-xxs: 0.625rem; //10px
- --font-size-xs: 0.75rem; //12px
- --font-size-sm: 0.875rem; //14px
- --font-size-nm: 1rem; //16px
- --font-size-md: 1.125rem; //18px
- --font-size-lg: 1.25rem; //20px
- --font-size-xl: 1.5rem; //24px
- --font-size-2xl: 2rem; //32px
- --font-size-3xl: 3rem; //48px
-
- // Font Weights
- --font-weight-regular: 400;
- --font-weight-medium: 500;
- --font-weight-bold: 700;
- --font-weight-extrabold: 800;
-
- --font-weight-text: var(--font-weight-medium);
- --font-weight-heading: var(--font-weight-extrabold);
- --font-weight-title: var(--font-weight-extrabold);
-
- @media screen and (min-width: 320px) {
- --size-mobile-navbar-height-expanded: 11.5rem;
- }
-
- @media screen and (min-width: 432px) {
- --size-mobile-navbar-height-expanded: 9.25rem;
- }
-
- @media screen and (min-width: 765px) {
- --size-mobile-navbar-height-expanded: 7rem;
- }
+ // Defaults
+ background-color: var(--color-bg);
+ color: var(--color-text);
+ --font-standard:
+ Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto, Cantarell,
+ Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+ --mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
+ font-family: var(--font-standard);
+ font-size: 16px;
+ font-weight: var(--font-weight-regular);
+ margin: 0;
+ padding: 0;
+
+ // Rounding sizes
+ --size-rounded-xs: 0.5rem;
+ --size-rounded-sm: 0.75rem;
+ --size-rounded-md: 1rem;
+ --size-rounded-lg: 1.25rem;
+
+ --size-rounded-max: 999999999px;
+ --size-rounded-card: 1rem;
+ --size-rounded-icon: 1rem;
+ --size-rounded-control: 0.25rem;
+ --size-rounded-tooltip: 0.25rem;
+
+ --size-navbar-height: 3.5rem;
+ --size-mobile-navbar-height: 3.5rem;
+ --size-mobile-navbar-height-expanded: 13.75rem;
+
+ --spacing-card-lg: 1.5rem;
+ --spacing-card-bg: 1rem;
+ --spacing-card-md: 0.75rem;
+ --spacing-card-sm: 0.5rem;
+ --spacing-card-xs: 0.25rem;
+
+ // Font Sizes
+ --font-size-xxs: 0.625rem; //10px
+ --font-size-xs: 0.75rem; //12px
+ --font-size-sm: 0.875rem; //14px
+ --font-size-nm: 1rem; //16px
+ --font-size-md: 1.125rem; //18px
+ --font-size-lg: 1.25rem; //20px
+ --font-size-xl: 1.5rem; //24px
+ --font-size-2xl: 2rem; //32px
+ --font-size-3xl: 3rem; //48px
+
+ // Font Weights
+ --font-weight-regular: 400;
+ --font-weight-medium: 500;
+ --font-weight-bold: 700;
+ --font-weight-extrabold: 800;
+
+ --font-weight-text: var(--font-weight-medium);
+ --font-weight-heading: var(--font-weight-extrabold);
+ --font-weight-title: var(--font-weight-extrabold);
+
+ @media screen and (min-width: 320px) {
+ --size-mobile-navbar-height-expanded: 11.5rem;
+ }
+
+ @media screen and (min-width: 432px) {
+ --size-mobile-navbar-height-expanded: 9.25rem;
+ }
+
+ @media screen and (min-width: 765px) {
+ --size-mobile-navbar-height-expanded: 7rem;
+ }
}
svg {
- height: 1em;
- width: 1em;
+ height: 1em;
+ width: 1em;
}
a {
- color: inherit;
- text-decoration: none;
+ color: inherit;
+ text-decoration: none;
}
h1 {
- color: var(--color-text-dark);
+ color: var(--color-text-dark);
}
h2 {
- margin-top: 0;
- margin-bottom: 1rem;
- color: var(--color-text-dark);
+ margin-top: 0;
+ margin-bottom: 1rem;
+ color: var(--color-text-dark);
}
h3 {
- margin-block: var(--spacing-card-md) var(--spacing-card-sm);
- color: var(--color-text-dark);
+ margin-block: var(--spacing-card-md) var(--spacing-card-sm);
+ color: var(--color-text-dark);
}
input {
- border-radius: var(--size-rounded-sm);
- box-sizing: border-box;
- border: 2px solid transparent;
- // safari iOS rounds inputs by default
- // set the appearance to none to prevent this
- appearance: none !important;
+ border-radius: var(--size-rounded-sm);
+ box-sizing: border-box;
+ border: 2px solid transparent;
+ // safari iOS rounds inputs by default
+ // set the appearance to none to prevent this
+ appearance: none !important;
}
pre {
- font-weight: var(--font-weight-regular);
+ font-weight: var(--font-weight-regular);
}
input,
textarea {
- background: var(--color-button-bg);
- color: var(--color-text);
- padding: 0.5rem 1rem;
- font-weight: var(--font-weight-medium);
- border: none;
- outline: 2px solid transparent;
- box-shadow:
- var(--shadow-inset-sm),
- 0 0 0 0 transparent;
- transition: box-shadow 0.1s ease-in-out;
- min-height: 36px;
-
- &:focus,
- &:focus-visible {
+ background: var(--color-button-bg);
+ color: var(--color-text);
+ padding: 0.5rem 1rem;
+ font-weight: var(--font-weight-medium);
+ border: none;
+ outline: 2px solid transparent;
box-shadow:
- inset 0 0 0 transparent,
- 0 0 0 0.25rem var(--color-brand-shadow);
- color: var(--color-button-text-active);
- }
-
- &:disabled,
- &[disabled="true"] {
- opacity: 0.6;
- pointer-events: none;
- cursor: not-allowed;
- }
-
- &:focus::placeholder {
- opacity: 0.8;
- }
-
- &::placeholder {
- color: var(--color-button-text);
- opacity: 0.6;
- }
+ var(--shadow-inset-sm),
+ 0 0 0 0 transparent;
+ transition: box-shadow 0.1s ease-in-out;
+ min-height: 36px;
+
+ &:focus,
+ &:focus-visible {
+ box-shadow:
+ inset 0 0 0 transparent,
+ 0 0 0 0.25rem var(--color-brand-shadow);
+ color: var(--color-button-text-active);
+ }
+
+ &:disabled,
+ &[disabled='true'] {
+ opacity: 0.6;
+ pointer-events: none;
+ cursor: not-allowed;
+ }
+
+ &:focus::placeholder {
+ opacity: 0.8;
+ }
+
+ &::placeholder {
+ color: var(--color-button-text);
+ opacity: 0.6;
+ }
}
button,
-input[type="button"] {
- cursor: pointer;
- border: none;
- outline: 2px solid transparent;
+input[type='button'] {
+ cursor: pointer;
+ border: none;
+ outline: 2px solid transparent;
}
kbd {
- background-color: var(--color-code-bg);
- color: var(--color-code-text);
- box-shadow: 0 2px 1px var(--color-kbd-shadow);
- padding: 0.2em 0.5em 0.1em;
- border-radius: 3px;
- line-height: 1;
- font-size: 0.85em !important;
+ background-color: var(--color-code-bg);
+ color: var(--color-code-text);
+ box-shadow: 0 2px 1px var(--color-kbd-shadow);
+ padding: 0.2em 0.5em 0.1em;
+ border-radius: 3px;
+ line-height: 1;
+ font-size: 0.85em !important;
}
-@import "~/assets/styles/layout.scss";
-@import "~/assets/styles/utils.scss";
-@import "~/assets/styles/components.scss";
+@import '~/assets/styles/layout.scss';
+@import '~/assets/styles/utils.scss';
+@import '~/assets/styles/components.scss';
button:focus-visible,
a:focus-visible,
-[tabindex="0"]:focus-visible {
- outline: 0.25rem solid #ea80ff;
- border-radius: 0.25rem;
+[tabindex='0']:focus-visible {
+ outline: 0.25rem solid #ea80ff;
+ border-radius: 0.25rem;
}
// OMORPHIA FIXES
.card {
- outline-offset: -2px;
+ outline-offset: -2px;
}
input {
- outline: 2px solid transparent !important;
+ outline: 2px solid transparent !important;
}
.button-animation {
- transition:
- opacity 0.5s ease-in-out,
- filter 0.2s ease-in-out,
- transform 0.05s ease-in-out,
- outline-width 0.2s ease-in-out;
+ transition:
+ opacity 0.5s ease-in-out,
+ filter 0.2s ease-in-out,
+ transform 0.05s ease-in-out,
+ outline-width 0.2s ease-in-out;
}
.button-transparent {
- box-shadow: none;
+ box-shadow: none;
}
diff --git a/apps/frontend/src/assets/styles/layout.scss b/apps/frontend/src/assets/styles/layout.scss
index 60e05cfdd5..13df4cb041 100644
--- a/apps/frontend/src/assets/styles/layout.scss
+++ b/apps/frontend/src/assets/styles/layout.scss
@@ -1,227 +1,227 @@
.columns {
- display: flex;
+ display: flex;
- @for $i from 1 through 5 {
- .column-grow-#{$i} {
- flex-grow: $i;
+ @for $i from 1 through 5 {
+ .column-grow-#{$i} {
+ flex-grow: $i;
+ }
}
- }
}
.rows {
- display: flex;
- flex-direction: column;
+ display: flex;
+ flex-direction: column;
- @for $i from 1 through 4 {
- .row-grow-#{$i} {
- flex-grow: $i;
+ @for $i from 1 through 4 {
+ .row-grow-#{$i} {
+ flex-grow: $i;
+ }
}
- }
}
.page-container {
- margin: var(--spacing-card-md);
- margin-top: 0;
+ margin: var(--spacing-card-md);
+ margin-top: 0;
- .page-contents {
- display: flex;
- flex-direction: column;
- .content {
- width: 100%;
- }
- @media screen and (min-width: 1024px) {
- flex-direction: row;
- //max-width: 1280px;
- margin-left: auto;
- margin-right: auto;
+ .page-contents {
+ display: flex;
+ flex-direction: column;
+ .content {
+ width: 100%;
+ }
+ @media screen and (min-width: 1024px) {
+ flex-direction: row;
+ //max-width: 1280px;
+ margin-left: auto;
+ margin-right: auto;
+ }
}
- }
}
.normal-page {
- display: grid;
- padding: 0 1.5rem;
-
- grid-template:
- "sidebar"
- "content"
- "info"
- / 100%;
-
- @media screen and (max-width: 1024px) {
- margin-top: var(--spacing-card-md);
- }
-
- .normal-page__sidebar {
- grid-area: sidebar;
- }
-
- .normal-page__info {
- grid-area: info;
- }
-
- .normal-page__content {
- grid-area: content;
- }
-
- .normal-page__header {
- grid-area: header;
- }
-}
+ display: grid;
+ padding: 0 1.5rem;
-@media (min-width: 1024px) {
- .full-page {
- margin: 0 auto;
- max-width: min(1280px, 100vw);
- width: 80rem;
- }
+ grid-template:
+ 'sidebar'
+ 'content'
+ 'info'
+ / 100%;
- .normal-page {
- margin: 0 auto;
- max-width: 80rem;
- column-gap: 0.75rem;
+ @media screen and (max-width: 1024px) {
+ margin-top: var(--spacing-card-md);
+ }
- grid-template:
- "sidebar content" auto
- "info content" auto
- "dummy content" 1fr
- / 18.75rem 1fr;
-
- &.alt-layout {
- grid-template:
- "content sidebar" auto
- "content info" auto
- "content dummy" 1fr
- / 1fr 18.75rem;
- }
-
- &.no-sidebar {
- grid-template:
- "header header" auto
- "content content" auto
- "info info" auto
- "dummy dummy" 1fr
- / 1fr 1fr;
-
- .normal-page__content {
- grid-area: content;
- max-width: 100%;
- }
+ .normal-page__sidebar {
+ grid-area: sidebar;
+ }
+
+ .normal-page__info {
+ grid-area: info;
}
- }
- .normal-page__sidebar {
- min-width: 18.75rem;
- width: 18.75rem;
- }
+ .normal-page__content {
+ grid-area: content;
+ }
- .normal-page__content {
- max-width: calc(80rem - 18.75rem - 1.5rem);
- //overflow-x: hidden;
- }
+ .normal-page__header {
+ grid-area: header;
+ }
}
-.new-page {
- display: grid;
- margin: 0 auto;
- max-width: 80rem;
- column-gap: 0.75rem;
- padding: 0 1.5rem;
- padding-bottom: 1.5rem;
-
- grid-template:
- "header"
- "content"
- "sidebar"
- / 100%;
-
- .normal-page__ultimate-sidebar {
- grid-area: ultimate-sidebar;
- position: fixed;
- bottom: 1rem;
- right: 1rem;
- z-index: 100;
- max-width: calc(100% - 2rem);
- max-height: calc(100vh - 2rem);
- overflow-y: auto;
-
- > div {
- box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
- }
- }
-
- @media screen and (min-width: 1024px) {
- &.sidebar {
- grid-template:
- "header header" auto
- "content sidebar" auto
- "content dummy" 1fr
- / 1fr 18.75rem;
-
- &.alt-layout {
+@media (min-width: 1024px) {
+ .full-page {
+ margin: 0 auto;
+ max-width: min(1280px, 100vw);
+ width: 80rem;
+ }
+
+ .normal-page {
+ margin: 0 auto;
+ max-width: 80rem;
+ column-gap: 0.75rem;
+
grid-template:
- "header header" auto
- "sidebar content" auto
- "dummy content" 1fr
- / 18.75rem 1fr;
- }
+ 'sidebar content' auto
+ 'info content' auto
+ 'dummy content' 1fr
+ / 18.75rem 1fr;
+
+ &.alt-layout {
+ grid-template:
+ 'content sidebar' auto
+ 'content info' auto
+ 'content dummy' 1fr
+ / 1fr 18.75rem;
+ }
+
+ &.no-sidebar {
+ grid-template:
+ 'header header' auto
+ 'content content' auto
+ 'info info' auto
+ 'dummy dummy' 1fr
+ / 1fr 1fr;
+
+ .normal-page__content {
+ grid-area: content;
+ max-width: 100%;
+ }
+ }
}
.normal-page__sidebar {
- min-width: 18.75rem;
- width: 18.75rem;
+ min-width: 18.75rem;
+ width: 18.75rem;
}
- }
- @media screen and (min-width: 1400px) {
- &.ultimate-sidebar {
- max-width: calc(80rem + 0.75rem + 600px);
+ .normal-page__content {
+ max-width: calc(80rem - 18.75rem - 1.5rem);
+ //overflow-x: hidden;
+ }
+}
- grid-template:
- "header header ultimate-sidebar" auto
- "content sidebar ultimate-sidebar" auto
- "content dummy ultimate-sidebar" 1fr
- / 1fr 18.75rem auto;
+.new-page {
+ display: grid;
+ margin: 0 auto;
+ max-width: 80rem;
+ column-gap: 0.75rem;
+ padding: 0 1.5rem;
+ padding-bottom: 1.5rem;
- .normal-page__header {
- max-width: 80rem;
- }
-
- .normal-page__ultimate-sidebar {
- position: sticky;
- top: 4.5rem;
- bottom: unset;
- right: unset;
- z-index: unset;
- align-self: start;
- display: flex;
- height: calc(100vh - 4.5rem * 2);
+ grid-template:
+ 'header'
+ 'content'
+ 'sidebar'
+ / 100%;
+
+ .normal-page__ultimate-sidebar {
+ grid-area: ultimate-sidebar;
+ position: fixed;
+ bottom: 1rem;
+ right: 1rem;
+ z-index: 100;
+ max-width: calc(100% - 2rem);
+ max-height: calc(100vh - 2rem);
+ overflow-y: auto;
> div {
- box-shadow: none;
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
}
- }
+ }
- &.alt-layout {
- grid-template:
- "ultimate-sidebar header header" auto
- "ultimate-sidebar sidebar content" auto
- "ultimate-sidebar dummy content" 1fr
- / auto 18.75rem 1fr;
- }
- }
- }
-
- .normal-page__sidebar {
- grid-area: sidebar;
- }
-
- .normal-page__content {
- grid-area: content;
- max-width: calc(80rem - 18.75rem - 1.5rem);
- //overflow-x: hidden;
- }
-
- .normal-page__header {
- grid-area: header;
- }
+ @media screen and (min-width: 1024px) {
+ &.sidebar {
+ grid-template:
+ 'header header' auto
+ 'content sidebar' auto
+ 'content dummy' 1fr
+ / 1fr 18.75rem;
+
+ &.alt-layout {
+ grid-template:
+ 'header header' auto
+ 'sidebar content' auto
+ 'dummy content' 1fr
+ / 18.75rem 1fr;
+ }
+ }
+
+ .normal-page__sidebar {
+ min-width: 18.75rem;
+ width: 18.75rem;
+ }
+ }
+
+ @media screen and (min-width: 1400px) {
+ &.ultimate-sidebar {
+ max-width: calc(80rem + 0.75rem + 600px);
+
+ grid-template:
+ 'header header ultimate-sidebar' auto
+ 'content sidebar ultimate-sidebar' auto
+ 'content dummy ultimate-sidebar' 1fr
+ / 1fr 18.75rem auto;
+
+ .normal-page__header {
+ max-width: 80rem;
+ }
+
+ .normal-page__ultimate-sidebar {
+ position: sticky;
+ top: 4.5rem;
+ bottom: unset;
+ right: unset;
+ z-index: unset;
+ align-self: start;
+ display: flex;
+ height: calc(100vh - 4.5rem * 2);
+
+ > div {
+ box-shadow: none;
+ }
+ }
+
+ &.alt-layout {
+ grid-template:
+ 'ultimate-sidebar header header' auto
+ 'ultimate-sidebar sidebar content' auto
+ 'ultimate-sidebar dummy content' 1fr
+ / auto 18.75rem 1fr;
+ }
+ }
+ }
+
+ .normal-page__sidebar {
+ grid-area: sidebar;
+ }
+
+ .normal-page__content {
+ grid-area: content;
+ max-width: calc(80rem - 18.75rem - 1.5rem);
+ //overflow-x: hidden;
+ }
+
+ .normal-page__header {
+ grid-area: header;
+ }
}
diff --git a/apps/frontend/src/assets/styles/tailwind.css b/apps/frontend/src/assets/styles/tailwind.css
index f56d34f772..608c14c82d 100644
--- a/apps/frontend/src/assets/styles/tailwind.css
+++ b/apps/frontend/src/assets/styles/tailwind.css
@@ -5,5 +5,5 @@
h1,
h2,
h3 {
- @apply font-bold;
+ @apply font-bold;
}
diff --git a/apps/frontend/src/assets/styles/utils.scss b/apps/frontend/src/assets/styles/utils.scss
index b2e96ea64a..c01e5bf16e 100644
--- a/apps/frontend/src/assets/styles/utils.scss
+++ b/apps/frontend/src/assets/styles/utils.scss
@@ -1,10 +1,10 @@
body {
- overflow-y: scroll;
- overflow-x: hidden;
+ overflow-y: scroll;
+ overflow-x: hidden;
}
.text-container p {
- line-height: 1.3;
+ line-height: 1.3;
}
// From the Bootstrap project
@@ -12,20 +12,20 @@ body {
// Copyright (c) 2011-2023 The Bootstrap Authors
// https://github.com/twbs/bootstrap/blob/2f617215755b066904248525a8c56ea425dde871/scss/mixins/_visually-hidden.scss#L8
.visually-hidden {
- width: 1px !important;
- height: 1px !important;
- padding: 0 !important;
- margin: -1px !important;
- overflow: hidden !important;
- clip: rect(0, 0, 0, 0) !important;
- white-space: nowrap !important;
- border: 0 !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+ border: 0 !important;
- &:not(caption) {
- position: absolute !important;
- }
+ &:not(caption) {
+ position: absolute !important;
+ }
}
.preserve-lines {
- white-space: pre-line;
+ white-space: pre-line;
}
diff --git a/apps/frontend/src/components/brand/LogoAnimated.vue b/apps/frontend/src/components/brand/LogoAnimated.vue
index a49454f2cd..11041249eb 100644
--- a/apps/frontend/src/components/brand/LogoAnimated.vue
+++ b/apps/frontend/src/components/brand/LogoAnimated.vue
@@ -1,101 +1,116 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/components/brand/TextLogo.vue b/apps/frontend/src/components/brand/TextLogo.vue
index a942c14016..54ec825714 100644
--- a/apps/frontend/src/components/brand/TextLogo.vue
+++ b/apps/frontend/src/components/brand/TextLogo.vue
@@ -1,97 +1,97 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/frontend/src/components/ui/Accordion.vue b/apps/frontend/src/components/ui/Accordion.vue
index 31fcfae6fa..f731937bda 100644
--- a/apps/frontend/src/components/ui/Accordion.vue
+++ b/apps/frontend/src/components/ui/Accordion.vue
@@ -1,92 +1,92 @@
-
-
- (isOpen ? close() : open())">
-
-
-
-
-
-
-
+
+
+ (isOpen ? close() : open())">
+
+
+
+
-
-
diff --git a/apps/frontend/src/components/ui/AdPlaceholder.vue b/apps/frontend/src/components/ui/AdPlaceholder.vue
index e2d5c9b462..3c477ff695 100644
--- a/apps/frontend/src/components/ui/AdPlaceholder.vue
+++ b/apps/frontend/src/components/ui/AdPlaceholder.vue
@@ -1,122 +1,122 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
-
diff --git a/apps/frontend/src/components/ui/AutomaticAccordion.vue b/apps/frontend/src/components/ui/AutomaticAccordion.vue
index 154f3fd09d..e5ce8f98fe 100644
--- a/apps/frontend/src/components/ui/AutomaticAccordion.vue
+++ b/apps/frontend/src/components/ui/AutomaticAccordion.vue
@@ -1,70 +1,70 @@
-