1
0

feat: frontend explicit imports + error page fix (#4184)

* feat: frontend explicit imports

* fix: error handling

* fix: dashboard missing import

* fix: error page issues

* fix: exclude RouterView

* feat: fix lint issues

* fix: lint issues

* fix: import issues

* add getVersionLink

* make articles.json use tabs on generation so it doesn't have to be reformatted

* fix: lint issues

---------

Signed-off-by: Cal H. <hendersoncal117@gmail.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Cal H.
2025-08-17 12:15:49 +01:00
committed by GitHub
parent 74d2d85cb5
commit 3e735b99eb
54 changed files with 1295 additions and 1020 deletions

View File

@@ -2,8 +2,9 @@
::backdrop,
:root[data-theme='light'],
[data-theme='light'] ::backdrop {
--sl-font-system: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--sl-font-system:
Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto, Cantarell,
Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--sl-color-white: var(--color-contrast); /* “white” */
--sl-color-gray-1: var(--color-base);

View File

@@ -331,7 +331,19 @@ import {
VersionIcon,
XIcon,
} from '@modrinth/assets'
import { injectNotificationManager, useRelativeTime } from '@modrinth/ui'
import {
Avatar,
Categories,
CopyCode,
DoubleIcon,
injectNotificationManager,
ProjectStatusBadge,
useRelativeTime,
} from '@modrinth/ui'
import { getProjectLink, getVersionLink } from '~/helpers/projects'
import ThreadSummary from './thread/ThreadSummary.vue'
const { addNotification } = injectNotificationManager()
const emit = defineEmits(['update:notifications'])

View File

@@ -9,7 +9,7 @@
<div class="flex gap-2">
<ButtonStyled>
<button size="sm" @click="$emit('refetch')">
<UiServersIconsLoadingIcon class="h-5 w-5" />
<LoadingIcon class="h-5 w-5" />
Try again
</button>
</ButtonStyled>
@@ -28,6 +28,8 @@
import { FileIcon, HomeIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import LoadingIcon from './icons/LoadingIcon.vue'
defineProps<{
title: string
message: string

View File

@@ -18,7 +18,7 @@
}"
data-pyro-files-virtual-list
>
<UiServersFileItem
<FileItem
v-for="item in visibleItems"
:key="item.path"
:count="item.count"
@@ -45,6 +45,8 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import FileItem from './FileItem.vue'
const props = defineProps<{
items: any[]
}>()

View File

@@ -14,7 +14,7 @@
v-if="state.hasError"
class="flex h-full w-full flex-col items-center justify-center gap-8"
>
<UiServersIconsPanelErrorIcon />
<PanelErrorIcon />
<p class="m-0">{{ state.errorMessage || 'Invalid or empty image file.' }}</p>
</div>
<img
@@ -57,6 +57,8 @@ import { ZoomInIcon, ZoomOutIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import PanelErrorIcon from './icons/PanelErrorIcon.vue'
const ZOOM_MIN = 0.1
const ZOOM_MAX = 5
const ZOOM_IN_FACTOR = 1.2

View File

@@ -27,7 +27,7 @@
>
<div class="flex flex-1 items-center gap-2 truncate">
<transition-group name="status-icon" mode="out-in">
<UiServersPanelSpinner
<PanelSpinner
v-show="item.status === 'uploading'"
key="spinner"
class="absolute !size-4"
@@ -107,6 +107,8 @@ import { computed, nextTick, ref, watch } from 'vue'
import type { FSModule } from '~/composables/servers/modules/fs.ts'
import PanelSpinner from './PanelSpinner.vue'
const { addNotification } = injectNotificationManager()
interface UploadItem {

View File

@@ -5,7 +5,7 @@
:key="loader.name"
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
>
<UiServersLoaderSelectorCard
<LoaderSelectorCard
:loader="loader"
:is-current="isCurrentLoader(loader.name)"
:loader-version="data.loader_version"
@@ -24,7 +24,7 @@
:key="loader.name"
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
>
<UiServersLoaderSelectorCard
<LoaderSelectorCard
:loader="loader"
:is-current="isCurrentLoader(loader.name)"
:loader-version="data.loader_version"
@@ -44,7 +44,7 @@
:key="loader.name"
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
>
<UiServersLoaderSelectorCard
<LoaderSelectorCard
:loader="loader"
:is-current="isCurrentLoader(loader.name)"
:loader-version="data.loader_version"
@@ -58,6 +58,7 @@
</template>
<script setup lang="ts">
import LoaderSelectorCard from './LoaderSelectorCard.vue'
const props = defineProps<{
data: {
loader: string | null

View File

@@ -5,7 +5,7 @@
class="grid size-10 place-content-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg shadow-sm"
:class="isCurrentLoader ? '[&&]:bg-bg-green' : ''"
>
<UiServersIconsLoaderIcon
<LoaderIcon
:loader="loader.name"
class="[&&]:size-6"
:class="isCurrentLoader ? 'text-brand' : ''"
@@ -43,6 +43,8 @@
import { CheckIcon, DownloadIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import LoaderIcon from './icons/LoaderIcon.vue'
interface LoaderInfo {
name: 'Vanilla' | 'Fabric' | 'Forge' | 'Quilt' | 'Paper' | 'NeoForge' | 'Purpur'
displayName: string

View File

@@ -6,7 +6,7 @@
Are you sure you want to
<span class="lowercase">{{ confirmActionText }}</span> the server?
</p>
<UiCheckbox
<Checkbox
v-model="dontAskAgain"
label="Don't ask me again"
class="text-sm"
@@ -34,7 +34,7 @@
:header="`All of ${serverName || 'Server'} info`"
@close="closeDetailsModal"
>
<UiServersServerInfoLabels
<ServerInfoLabels
:server-data="serverData"
:show-game-label="true"
:show-loader-label="true"
@@ -53,7 +53,7 @@
<div class="flex flex-row items-center gap-2 rounded-lg">
<ButtonStyled v-if="isInstalling" type="standard" color="brand">
<button disabled class="flex-shrink-0">
<UiServersPanelSpinner class="size-5" /> Installing...
<PanelSpinner class="size-5" /> Installing...
</button>
</ButtonStyled>
@@ -70,7 +70,7 @@
<ButtonStyled type="standard" color="brand">
<button :disabled="!canTakeAction" @click="handlePrimaryAction">
<div v-if="isTransitionState" class="grid place-content-center">
<UiServersIconsLoadingIcon />
<LoadingIcon />
</div>
<component :is="isRunning ? UpdatedIcon : PlayIcon" v-else />
<span>{{ primaryActionText }}</span>
@@ -116,12 +116,16 @@ import {
UpdatedIcon,
XIcon,
} from '@modrinth/assets'
import { ButtonStyled, NewModal } from '@modrinth/ui'
import { ButtonStyled, Checkbox, NewModal } from '@modrinth/ui'
import type { PowerAction as ServerPowerAction, ServerState } from '@modrinth/utils'
import { useStorage } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import LoadingIcon from './icons/LoadingIcon.vue'
import PanelSpinner from './PanelSpinner.vue'
import ServerInfoLabels from './ServerInfoLabels.vue'
const flags = useFeatureFlags()
interface PowerAction {

View File

@@ -151,7 +151,7 @@
class="group"
>
<div class="flex items-center gap-2">
<UiServersLogLine :log="item" @show-full-log="showFullLogMessage" />
<LogLine :log="item" @show-full-log="showFullLogMessage" />
<div @mousedown.stop @click.stop>
<button
v-if="searchInput"
@@ -223,8 +223,8 @@
:class="{ hidden: searchInput || hasSelection || isSingleLineSelected }"
@click="toggleFullscreen"
>
<LazyUiServersIconsMinimizeIconVue v-if="isFullScreen" />
<LazyUiServersIconsFullscreenIcon v-else />
<MinimizeIconVue v-if="isFullScreen" />
<FullscreenIcon v-else />
</button>
<Transition name="fade">
@@ -306,6 +306,10 @@ import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useModrinthServersConsole } from '~/store/console.ts'
import FullscreenIcon from './icons/FullscreenIcon.vue'
import MinimizeIconVue from './icons/MinimizeIcon.vue.vue'
import LogLine from './LogLine.vue'
const { $cosmetics } = useNuxtApp()
const cosmetics = $cosmetics

View File

@@ -28,7 +28,7 @@
<div
class="grid size-16 place-content-center rounded-2xl border-[2px] border-solid border-button-border bg-button-bg shadow-sm"
>
<UiServersIconsLoaderIcon class="size-10" :loader="selectedLoader" />
<LoaderIcon class="size-10" :loader="selectedLoader" />
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
@@ -54,7 +54,7 @@
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
<div class="text-lg font-bold text-contrast">Minecraft version</div>
<UiServersTeleportDropdownMenu
<TeleportDropdownMenu
v-model="selectedMCVersion"
name="mcVersion"
:options="mcVersions"
@@ -102,13 +102,13 @@
<div
class="relative flex h-9 w-full items-center rounded-xl bg-button-bg px-4 opacity-50"
>
<UiServersIconsLoadingIcon class="mr-2 animate-spin" />
<LoadingIcon class="mr-2 animate-spin" />
Loading versions...
<DropdownIcon class="absolute right-4" />
</div>
</template>
<template v-else-if="selectedLoaderVersions.length > 0">
<UiServersTeleportDropdownMenu
<TeleportDropdownMenu
v-model="selectedLoaderVersion"
name="loaderVersion"
:options="selectedLoaderVersions"
@@ -211,6 +211,9 @@ import { $fetch } from 'ofetch'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import type { BackupInProgressReason } from '~/pages/servers/manage/[id].vue'
import LoaderIcon from './icons/LoaderIcon.vue'
import LoadingIcon from './icons/LoadingIcon.vue'
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()

View File

@@ -1,24 +1,24 @@
<template>
<div>
<UiServersServerGameLabel
<ServerGameLabel
v-if="showGameLabel"
:game="serverData.game"
:mc-version="serverData.mc_version ?? ''"
:is-link="linked"
/>
<UiServersServerLoaderLabel
<ServerLoaderLabel
:loader="serverData.loader"
:loader-version="serverData.loader_version ?? ''"
:no-separator="column"
:is-link="linked"
/>
<UiServersServerSubdomainLabel
<ServerSubdomainLabel
v-if="serverData.net?.domain"
:subdomain="serverData.net.domain"
:no-separator="column"
:is-link="linked"
/>
<UiServersServerUptimeLabel
<ServerUptimeLabel
v-if="uptimeSeconds"
:uptime-seconds="uptimeSeconds"
:no-separator="column"
@@ -27,6 +27,11 @@
</template>
<script setup lang="ts">
import ServerGameLabel from './ServerGameLabel.vue'
import ServerLoaderLabel from './ServerLoaderLabel.vue'
import ServerSubdomainLabel from './ServerSubdomainLabel.vue'
import ServerUptimeLabel from './ServerUptimeLabel.vue'
interface ServerInfoLabelsProps {
serverData: Record<string, any>
showGameLabel: boolean

View File

@@ -1,5 +1,5 @@
<template>
<LazyUiServersPlatformVersionSelectModal
<PlatformVersionSelectModal
ref="versionSelectModal"
:server="props.server"
:current-loader="ignoreCurrentInstallation ? undefined : (data?.loader as Loaders)"
@@ -8,13 +8,13 @@
@reinstall="emit('reinstall', $event)"
/>
<LazyUiServersPlatformMrpackModal
<PlatformMrpackModal
ref="mrpackModal"
:server="props.server"
@reinstall="emit('reinstall', $event)"
/>
<LazyUiServersPlatformChangeModpackVersionModal
<PlatformChangeModpackVersionModal
ref="modpackVersionModal"
:server="props.server"
:project="data?.project"
@@ -137,7 +137,7 @@
}"
:tabindex="props.server.general?.status === 'installing' ? -1 : 0"
>
<UiServersLoaderSelector
<LoaderSelector
:data="
ignoreCurrentInstallation
? {
@@ -165,6 +165,11 @@ import type { Loaders } from '@modrinth/utils'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import type { BackupInProgressReason } from '~/pages/servers/manage/[id].vue'
import LoaderSelector from './LoaderSelector.vue'
import PlatformChangeModpackVersionModal from './PlatformChangeModpackVersionModal.vue'
import PlatformMrpackModal from './PlatformMrpackModal.vue'
import PlatformVersionSelectModal from './PlatformVersionSelectModal.vue'
const { formatMessage } = useVIntl()
const props = defineProps<{

View File

@@ -16,7 +16,7 @@
data-pyro-server-listing
:data-pyro-server-listing-id="server_id"
>
<UiServersServerIcon v-if="status !== 'suspended'" :image="image" />
<ServerIcon v-if="status !== 'suspended'" :image="image" />
<div
v-else
class="bg-bg-secondary flex size-24 items-center justify-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg shadow-sm"
@@ -49,7 +49,7 @@
>
<SparklesIcon class="size-5 shrink-0" /> New server
</div>
<UiServersServerInfoLabels
<ServerInfoLabels
v-else
:server-data="{ game, mc_version, loader, loader_version, net }"
:show-game-label="showGameLabel"
@@ -63,7 +63,7 @@
v-if="status === 'suspended' && suspension_reason === 'upgrading'"
class="relative -mt-4 flex w-full flex-row items-center gap-2 rounded-b-3xl bg-bg-blue p-4 text-sm font-bold text-contrast"
>
<UiServersPanelSpinner />
<PanelSpinner />
Your server's hardware is currently being upgraded and will be back online shortly.
</div>
<div
@@ -71,8 +71,8 @@
class="relative -mt-4 flex w-full flex-col gap-2 rounded-b-3xl bg-bg-red p-4 text-sm font-bold text-contrast"
>
<div class="flex flex-row gap-2">
<UiServersIconsPanelErrorIcon class="!size-5" /> Your server has been cancelled. Please
update your billing information or contact Modrinth Support for more information.
<PanelErrorIcon class="!size-5" /> Your server has been cancelled. Please update your
billing information or contact Modrinth Support for more information.
</div>
<CopyCode :text="`${props.server_id}`" class="ml-auto" />
</div>
@@ -81,9 +81,8 @@
class="relative -mt-4 flex w-full flex-col gap-2 rounded-b-3xl bg-bg-red p-4 text-sm font-bold text-contrast"
>
<div class="flex flex-row gap-2">
<UiServersIconsPanelErrorIcon class="!size-5" /> Your server has been suspended:
{{ suspension_reason }}. Please update your billing information or contact Modrinth Support
for more information.
<PanelErrorIcon class="!size-5" /> Your server has been suspended: {{ suspension_reason }}.
Please update your billing information or contact Modrinth Support for more information.
</div>
<CopyCode :text="`${props.server_id}`" class="ml-auto" />
</div>
@@ -92,8 +91,8 @@
class="relative -mt-4 flex w-full flex-col gap-2 rounded-b-3xl bg-bg-red p-4 text-sm font-bold text-contrast"
>
<div class="flex flex-row gap-2">
<UiServersIconsPanelErrorIcon class="!size-5" /> Your server has been suspended. Please
update your billing information or contact Modrinth Support for more information.
<PanelErrorIcon class="!size-5" /> Your server has been suspended. Please update your
billing information or contact Modrinth Support for more information.
</div>
<CopyCode :text="`${props.server_id}`" class="ml-auto" />
</div>
@@ -107,6 +106,11 @@ import type { Project, Server } from '@modrinth/utils'
import { useModrinthServers } from '~/composables/servers/modrinth-servers.ts'
import PanelErrorIcon from './icons/PanelErrorIcon.vue'
import PanelSpinner from './PanelSpinner.vue'
import ServerIcon from './ServerIcon.vue'
import ServerInfoLabels from './ServerInfoLabels.vue'
const props = defineProps<Partial<Server>>()
if (props.server_id && props.status === 'available') {

View File

@@ -9,7 +9,7 @@
src="~/assets/images/servers/minecraft_server_icon.png"
/>
<div class="absolute inset-0 grid place-content-center">
<UiServersIconsLoadingIcon class="size-8 animate-spin text-contrast" />
<LoadingIcon class="size-8 animate-spin text-contrast" />
</div>
</div>
<div class="flex flex-col gap-4">
@@ -18,3 +18,7 @@
</div>
</div>
</template>
<script lang="ts" setup>
import LoadingIcon from './icons/LoadingIcon.vue'
</script>

View File

@@ -2,7 +2,7 @@
<div v-tooltip="'Change server loader'" class="flex min-w-0 flex-row items-center gap-4 truncate">
<div v-if="!noSeparator" class="experimental-styles-within h-6 w-0.5 bg-button-border"></div>
<div class="flex flex-row items-center gap-2">
<UiServersIconsLoaderIcon v-if="loader" :loader="loader" class="flex shrink-0 [&&]:size-5" />
<LoaderIcon v-if="loader" :loader="loader" class="flex shrink-0 [&&]:size-5" />
<div v-else class="size-5 shrink-0 animate-pulse rounded-full bg-button-border"></div>
<NuxtLink
v-if="isLink"
@@ -34,6 +34,7 @@
</template>
<script setup lang="ts">
import LoaderIcon from './icons/LoaderIcon.vue'
defineProps<{
noSeparator?: boolean
loader?: 'Fabric' | 'Quilt' | 'Forge' | 'NeoForge' | 'Paper' | 'Spigot' | 'Bukkit' | 'Vanilla'

View File

@@ -8,7 +8,7 @@
<div v-if="!noSeparator" class="experimental-styles-within h-6 w-0.5 bg-button-border"></div>
<div class="flex gap-2">
<UiServersIconsTimer class="flex size-5 shrink-0" />
<Timer class="flex size-5 shrink-0" />
<time class="truncate text-sm font-semibold" :aria-label="verboseUptime">
{{ formattedUptime }}
</time>
@@ -19,6 +19,8 @@
<script setup lang="ts">
import { computed } from 'vue'
import Timer from './icons/Timer.vue'
const props = defineProps<{
uptimeSeconds: number
noSeparator?: boolean

View File

@@ -1,5 +1,7 @@
<template>
<NuxtLayout>
<ModrinthLoadingIndicator />
<NotificationPanel />
<div class="main experimental-styles-within">
<div v-if="is404" class="error-graphic">
<Logo404 />
@@ -50,10 +52,16 @@
<script setup>
import { SadRinthbot } from '@modrinth/assets'
import { NotificationPanel, provideNotificationManager } from '@modrinth/ui'
import { defineMessage, useVIntl } from '@vintl/vintl'
import { IntlFormatted } from '@vintl/vintl/components'
import Logo404 from '~/assets/images/404.svg'
import ModrinthLoadingIndicator from './components/ui/modrinth-loading-indicator.ts'
import { FrontendNotificationManager } from './providers/frontend-notifications.ts'
provideNotificationManager(new FrontendNotificationManager())
const { formatMessage } = useVIntl()
const props = defineProps({

View File

@@ -116,7 +116,7 @@
>
<div>
<NuxtLink to="/" aria-label="Modrinth home page">
<BrandTextLogo aria-hidden="true" class="h-7 w-auto text-contrast" />
<TextLogo aria-hidden="true" class="h-7 w-auto text-contrast" />
</NuxtLink>
</div>
<div
@@ -450,8 +450,7 @@
</div>
</NuxtLink>
<nuxt-link v-else class="iconified-button brand-button" to="/auth/sign-in">
<LogInIcon aria-hidden="true" />
{{ formatMessage(commonMessages.signInButton) }}
<LogInIcon aria-hidden="true" /> {{ formatMessage(commonMessages.signInButton) }}
</nuxt-link>
</div>
<div class="links">
@@ -590,7 +589,7 @@
role="region"
aria-label="Modrinth information"
>
<BrandTextLogo
<TextLogo
aria-hidden="true"
class="text-logo button-base h-6 w-auto text-contrast lg:h-8"
@click="developerModeIncrement()"
@@ -716,7 +715,9 @@ import {
PagewideBanner,
} from '@modrinth/ui'
import { isAdmin, isStaff } from '@modrinth/utils'
import { IntlFormatted } from '@vintl/vintl/components'
import TextLogo from '~/components/brand/TextLogo.vue'
import CollectionCreateModal from '~/components/ui/CollectionCreateModal.vue'
import ModalCreation from '~/components/ui/ModalCreation.vue'
import OrganizationCreateModal from '~/components/ui/OrganizationCreateModal.vue'
@@ -724,12 +725,13 @@ import TeleportOverflowMenu from '~/components/ui/servers/TeleportOverflowMenu.v
import { errors as generatedStateErrors } from '~/generated/state.json'
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()
const auth = await useAuth()
const user = await useUser()
const { addNotification } = injectNotificationManager()
const cosmetics = useCosmetics()
const flags = useFeatureFlags()
@@ -739,6 +741,22 @@ const router = useNativeRouter()
const link = config.public.siteUrl + route.path.replace(/\/+$/, '')
const basePopoutId = useId()
async function handleResendEmailVerification() {
try {
await resendVerifyEmail()
addNotification({
title: 'Verification email sent',
text: 'Please check your inbox for the verification email.',
type: 'success',
})
} catch (err) {
addNotification({
title: 'An error occurred',
text: err.data.description,
type: 'error',
})
}
}
const verifyEmailBannerMessages = defineMessages({
title: {
@@ -854,23 +872,6 @@ const footerMessages = defineMessages({
},
})
async function handleResendEmailVerification() {
try {
await resendVerifyEmail()
addNotification({
title: 'Email sent',
text: `An email with a link to verify your account has been sent to ${auth.value.user.email}.`,
type: 'success',
})
} catch (err) {
addNotification({
title: 'An error occurred',
text: err.data.description,
type: 'error',
})
}
}
useHead({
link: [
{
@@ -1211,10 +1212,7 @@ const footerLinks = [
{
href: '/news/changelog',
label: formatMessage(
defineMessage({
id: 'layout.footer.about.changelog',
defaultMessage: 'Changelog',
}),
defineMessage({ id: 'layout.footer.about.changelog', defaultMessage: 'Changelog' }),
),
},
{
@@ -1248,19 +1246,13 @@ const footerLinks = [
{
href: '/plus',
label: formatMessage(
defineMessage({
id: 'layout.footer.products.plus',
defaultMessage: 'Modrinth+',
}),
defineMessage({ id: 'layout.footer.products.plus', defaultMessage: 'Modrinth+' }),
),
},
{
href: '/app',
label: formatMessage(
defineMessage({
id: 'layout.footer.products.app',
defaultMessage: 'Modrinth App',
}),
defineMessage({ id: 'layout.footer.products.app', defaultMessage: 'Modrinth App' }),
),
},
{
@@ -1291,10 +1283,7 @@ const footerLinks = [
{
href: 'https://crowdin.com/project/modrinth',
label: formatMessage(
defineMessage({
id: 'layout.footer.resources.translate',
defaultMessage: 'Translate',
}),
defineMessage({ id: 'layout.footer.resources.translate', defaultMessage: 'Translate' }),
),
},
{
@@ -1323,19 +1312,13 @@ const footerLinks = [
{
href: '/legal/rules',
label: formatMessage(
defineMessage({
id: 'layout.footer.legal.rules',
defaultMessage: 'Content Rules',
}),
defineMessage({ id: 'layout.footer.legal.rules', defaultMessage: 'Content Rules' }),
),
},
{
href: '/legal/terms',
label: formatMessage(
defineMessage({
id: 'layout.footer.legal.terms-of-use',
defaultMessage: 'Terms of Use',
}),
defineMessage({ id: 'layout.footer.legal.terms-of-use', defaultMessage: 'Terms of Use' }),
),
},
{

File diff suppressed because it is too large Load Diff

View File

@@ -82,6 +82,7 @@
<script setup>
import { CheckIcon, XIcon } from '@modrinth/assets'
import { Avatar, Button, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import { useAuth } from '@/composables/auth.js'
import { useScopes } from '@/composables/auth/scopes.ts'

View File

@@ -141,6 +141,7 @@ import {
SSOSteamIcon,
} from '@modrinth/assets'
import { commonMessages, injectNotificationManager } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import HCaptcha from '@/components/ui/HCaptcha.vue'

View File

@@ -146,6 +146,7 @@ import {
UserIcon,
} from '@modrinth/assets'
import { Checkbox, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import HCaptcha from '@/components/ui/HCaptcha.vue'

View File

@@ -50,6 +50,7 @@
<script setup>
import { RightArrowIcon, WavingRinthbot } from '@modrinth/assets'
import { Checkbox, commonMessages } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
const route = useRoute()

View File

@@ -407,6 +407,7 @@ import {
useRelativeTime,
} from '@modrinth/ui'
import { isAdmin } from '@modrinth/utils'
import { IntlFormatted } from '@vintl/vintl/components'
import UpToDate from 'assets/images/illustrations/up_to_date.svg'
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'

View File

@@ -292,6 +292,7 @@ import {
} from '@modrinth/ui'
import type { Project, Report, User, Version } from '@modrinth/utils'
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
import { IntlFormatted } from '@vintl/vintl/components'
import { useImageUpload } from '~/composables/image-upload.ts'

View File

@@ -116,7 +116,7 @@
}"
>
<div class="flex w-full min-w-0 select-none flex-col items-center gap-6 pt-4 sm:flex-row">
<UiServersServerIcon :image="serverData.image" class="drop-shadow-lg sm:drop-shadow-none" />
<ServerIcon :image="serverData.image" class="drop-shadow-lg sm:drop-shadow-none" />
<div
class="flex min-w-0 flex-1 flex-col-reverse items-center gap-2 sm:flex-col sm:items-start"
>
@@ -137,7 +137,7 @@
data-pyro-server-action-buttons
class="server-action-buttons-anim flex w-fit flex-shrink-0"
>
<UiServersPanelServerActionButton
<PanelServerActionButton
v-if="!serverData.flows?.intro"
class="flex-shrink-0"
:is-online="isServerRunning"
@@ -158,7 +158,7 @@
>
<SettingsIcon /> Configuring server...
</div>
<UiServersServerInfoLabels
<ServerInfoLabels
v-else
:server-data="serverData"
:show-game-label="showGameLabel"
@@ -175,7 +175,7 @@
v-if="serverData?.status === 'installing'"
class="w-50 h-50 flex items-center justify-center gap-2 text-center text-lg font-bold"
>
<LazyUiServersPanelSpinner class="size-10 animate-spin" /> Setting up your server...
<PanelSpinner class="size-10 animate-spin" /> Setting up your server...
</div>
<div v-else>
<h2 class="my-4 text-xl font-extrabold">
@@ -196,7 +196,7 @@
data-pyro-navigation
class="isolate flex w-full select-none flex-col justify-between gap-4 overflow-auto md:flex-row md:items-center"
>
<UiNavTabs :links="navLinks" />
<NavTabs :links="navLinks" />
</div>
<div data-pyro-mount class="h-full w-full flex-1">
@@ -304,7 +304,7 @@
data-pyro-server-ws-reconnecting
class="mb-4 flex w-full flex-row items-center gap-4 rounded-2xl bg-bg-orange p-4 text-sm text-contrast"
>
<UiServersPanelSpinner />
<PanelSpinner />
Hang on, we're reconnecting to your server.
</div>
@@ -313,13 +313,13 @@
data-pyro-server-installing
class="mb-4 flex w-full flex-row items-center gap-4 rounded-2xl bg-bg-blue p-4 text-sm text-contrast"
>
<UiServersServerIcon :image="serverData.image" class="!h-10 !w-10" />
<ServerIcon :image="serverData.image" class="!h-10 !w-10" />
<div class="flex flex-col gap-1">
<span class="text-lg font-bold"> We're preparing your server! </span>
<div class="flex flex-row items-center gap-2">
<UiServersPanelSpinner class="!h-3 !w-3" />
<LazyUiServersInstallingTicker />
<PanelSpinner class="!h-3 !w-3" />
<InstallingTicker />
</div>
</div>
</div>
@@ -382,7 +382,13 @@ import DOMPurify from 'dompurify'
import { computed, onMounted, onUnmounted, type Reactive, ref } from 'vue'
import { reloadNuxtApp } from '#app'
import NavTabs from '~/components/ui/NavTabs.vue'
import PanelErrorIcon from '~/components/ui/servers/icons/PanelErrorIcon.vue'
import InstallingTicker from '~/components/ui/servers/InstallingTicker.vue'
import PanelServerActionButton from '~/components/ui/servers/PanelServerActionButton.vue'
import PanelSpinner from '~/components/ui/servers/PanelSpinner.vue'
import ServerIcon from '~/components/ui/servers/ServerIcon.vue'
import ServerInfoLabels from '~/components/ui/servers/ServerInfoLabels.vue'
import ServerInstallation from '~/components/ui/servers/ServerInstallation.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { useModrinthServers } from '~/composables/servers/modrinth-servers.ts'

View File

@@ -1,5 +1,5 @@
<template>
<UiServersContentVersionEditModal
<ContentVersionEditModal
v-if="!invalidModal"
ref="versionEditModal"
:type="type"
@@ -59,7 +59,7 @@
/>
</div>
<ButtonStyled>
<UiServersTeleportOverflowMenu
<TeleportOverflowMenu
position="bottom"
direction="left"
:aria-label="`Filter ${type}s`"
@@ -77,8 +77,8 @@
<template #all> All {{ type.toLocaleLowerCase() }}s </template>
<template #enabled> Only enabled </template>
<template #disabled> Only disabled </template>
</UiServersTeleportOverflowMenu>
</ButtonStyled>
</TeleportOverflowMenu></ButtonStyled
>
</div>
<div v-if="hasMods" class="flex w-full items-center gap-2 sm:w-fit">
<ButtonStyled>
@@ -202,7 +202,7 @@
@click="showVersionModal(mod)"
>
<template v-if="mod.changing">
<UiServersIconsLoadingIcon class="animate-spin" />
<LoadingIcon class="animate-spin" />
</template>
<template v-else>
<EditIcon />
@@ -212,13 +212,13 @@
<!-- Dropdown for mobile -->
<div class="mr-2 flex items-center sm:hidden">
<UiServersIconsLoadingIcon
<LoadingIcon
v-if="mod.changing"
class="mr-2 h-5 w-5 animate-spin"
style="color: var(--color-base)"
/>
<ButtonStyled v-else circular type="transparent">
<UiServersTeleportOverflowMenu
<TeleportOverflowMenu
:options="[
{
id: 'edit',
@@ -240,8 +240,8 @@
<TrashIcon class="h-5 w-5" />
<span>Delete</span>
</template>
</UiServersTeleportOverflowMenu>
</ButtonStyled>
</TeleportOverflowMenu></ButtonStyled
>
</div>
<input
@@ -312,7 +312,7 @@
</div>
</div>
<div v-else class="mt-4 flex h-full flex-col items-center justify-center gap-4 text-center">
<UiServersIconsLoaderIcon loader="Vanilla" class="size-24" />
<LoaderIcon loader="Vanilla" class="size-24" />
<p class="m-0 pt-3 font-bold text-contrast">Your server is running Vanilla Minecraft</p>
<p class="m-0">
Add content to your server by installing a modpack or choosing a different platform that
@@ -359,8 +359,11 @@ import { Avatar, ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import type { Mod } from '@modrinth/utils'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import ContentVersionEditModal from '~/components/ui/servers/ContentVersionEditModal.vue'
import FilesUploadDragAndDrop from '~/components/ui/servers/FilesUploadDragAndDrop.vue'
import FilesUploadDropdown from '~/components/ui/servers/FilesUploadDropdown.vue'
import LoaderIcon from '~/components/ui/servers/icons/LoaderIcon.vue'
import LoadingIcon from '~/components/ui/servers/icons/LoadingIcon.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { acceptFileFromProjectType } from '~/helpers/fileUtils.js'

View File

@@ -1,31 +1,19 @@
<template>
<div data-pyro-file-manager-root class="contents">
<LazyUiServersFilesCreateItemModal
ref="createItemModal"
:type="newItemType"
@create="handleCreateNewItem"
/>
<FilesCreateItemModal ref="createItemModal" :type="newItemType" @create="handleCreateNewItem" />
<FilesUploadZipUrlModal ref="uploadZipModal" :server="server" />
<FilesUploadConflictModal ref="uploadConflictModal" @proceed="extractItem" />
<LazyUiServersFilesRenameItemModal
ref="renameItemModal"
:item="selectedItem"
@rename="handleRenameItem"
/>
<FilesRenameItemModal ref="renameItemModal" :item="selectedItem" @rename="handleRenameItem" />
<LazyUiServersFilesMoveItemModal
<FilesMoveItemModal
ref="moveItemModal"
:item="selectedItem"
:current-path="currentPath"
@move="handleMoveItem"
/>
<LazyUiServersFilesDeleteItemModal
ref="deleteItemModal"
:item="selectedItem"
@delete="handleDeleteItem"
/>
<FilesDeleteItemModal ref="deleteItemModal" :item="selectedItem" @delete="handleDeleteItem" />
<FilesUploadDragAndDrop
class="relative flex w-full flex-col rounded-2xl border border-solid border-bg-raised"
@@ -33,7 +21,7 @@
>
<div ref="mainContent" class="relative isolate flex w-full flex-col">
<div v-if="!isEditing" class="contents">
<UiServersFilesBrowseNavbar
<FilesBrowseNavbar
:breadcrumb-segments="breadcrumbSegments"
:search-query="searchQuery"
:current-filter="viewFilter"
@@ -46,11 +34,7 @@
@filter="handleFilter"
@update:search-query="searchQuery = $event"
/>
<UiServersFilesLabelBar
:sort-field="sortMethod"
:sort-desc="sortDesc"
@sort="handleSort"
/>
<FilesLabelBar :sort-field="sortMethod" :sort-desc="sortDesc" @sort="handleSort" />
<div
v-for="op in ops"
:key="`fs-op-${op.op}-${op.src}`"
@@ -172,7 +156,7 @@
@upload-complete="refreshList()"
/>
</div>
<UiServersFilesEditingNavbar
<FilesEditingNavbar
v-else
:file-name="editingFile?.name"
:is-image="isEditingImage"
@@ -211,10 +195,10 @@
class="ace_editor ace_hidpi ace-one-dark ace_dark rounded-b-lg"
@init="onInit"
/>
<UiServersFilesImageViewer v-else :image-blob="imagePreview" />
<FilesImageViewer v-else :image-blob="imagePreview" />
</div>
<div v-else-if="items.length > 0" class="h-full w-full overflow-hidden rounded-b-2xl">
<UiServersFileVirtualList
<FileVirtualList
:items="filteredItems"
@extract="handleExtractItem"
@delete="showDeleteModal"
@@ -239,7 +223,7 @@
</div>
</div>
<LazyUiServersFileManagerError
<FileManagerError
v-else-if="loadError"
title="Unable to load files"
message="The folder may not exist."
@@ -259,7 +243,7 @@
</div>
</FilesUploadDragAndDrop>
<UiServersFilesContextMenu
<FilesContextMenu
ref="contextMenu"
:item="contextMenuInfo.item"
:x="contextMenuInfo.x"
@@ -289,10 +273,21 @@ import { formatBytes, ModrinthServersFetchError } from '@modrinth/utils'
import { useInfiniteScroll } from '@vueuse/core'
import { computed } from 'vue'
import FileManagerError from '~/components/ui/servers/FileManagerError.vue'
import FilesBrowseNavbar from '~/components/ui/servers/FilesBrowseNavbar.vue'
import FilesContextMenu from '~/components/ui/servers/FilesContextMenu.vue'
import FilesCreateItemModal from '~/components/ui/servers/FilesCreateItemModal.vue'
import FilesDeleteItemModal from '~/components/ui/servers/FilesDeleteItemModal.vue'
import FilesEditingNavbar from '~/components/ui/servers/FilesEditingNavbar.vue'
import FilesImageViewer from '~/components/ui/servers/FilesImageViewer.vue'
import FilesLabelBar from '~/components/ui/servers/FilesLabelBar.vue'
import FilesMoveItemModal from '~/components/ui/servers/FilesMoveItemModal.vue'
import FilesRenameItemModal from '~/components/ui/servers/FilesRenameItemModal.vue'
import FilesUploadConflictModal from '~/components/ui/servers/FilesUploadConflictModal.vue'
import FilesUploadDragAndDrop from '~/components/ui/servers/FilesUploadDragAndDrop.vue'
import FilesUploadDropdown from '~/components/ui/servers/FilesUploadDropdown.vue'
import FilesUploadZipUrlModal from '~/components/ui/servers/FilesUploadZipUrlModal.vue'
import FileVirtualList from '~/components/ui/servers/FileVirtualList.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import { handleServersError } from '~/composables/servers/modrinth-servers.ts'

View File

@@ -75,7 +75,7 @@
</div>
<div class="flex flex-col-reverse gap-6 md:flex-col">
<UiServersServerStats
<ServerStats
:data="isConnected && !isWsAuthIncorrect ? stats : undefined"
:loading="!isConnected || isWsAuthIncorrect"
/>
@@ -87,17 +87,11 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<h2 class="m-0 text-3xl font-extrabold text-contrast">Console</h2>
<UiServersPanelServerStatus
v-if="isConnected && !isWsAuthIncorrect"
:state="serverPowerState"
/>
<PanelServerStatus v-if="isConnected && !isWsAuthIncorrect" :state="serverPowerState" />
</div>
</div>
<UiServersPanelTerminal
:full-screen="fullScreen"
:loading="!isConnected || isWsAuthIncorrect"
>
<PanelTerminal :full-screen="fullScreen" :loading="!isConnected || isWsAuthIncorrect">
<div class="relative w-full px-4 pt-4">
<ul
v-if="suggestions.length && isConnected && !isWsAuthIncorrect"
@@ -169,7 +163,7 @@
/>
</div>
</div>
</UiServersPanelTerminal>
</PanelTerminal>
</div>
</div>
@@ -191,6 +185,9 @@ import { IssuesIcon, TerminalSquareIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled } from '@modrinth/ui'
import type { ServerState, Stats } from '@modrinth/utils'
import PanelServerStatus from '~/components/ui/servers/PanelServerStatus.vue'
import PanelTerminal from '~/components/ui/servers/PanelTerminal.vue'
import ServerStats from '~/components/ui/servers/ServerStats.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
type ServerProps = {

View File

@@ -1,5 +1,5 @@
<template>
<UiServersServerSidebar
<ServerSidebar
:route="route"
:nav-links="navLinks"
:server="server"
@@ -20,6 +20,7 @@ import {
} from '@modrinth/assets'
import { isAdmin as isUserAdmin, type User } from '@modrinth/utils'
import ServerSidebar from '~/components/ui/servers/ServerSidebar.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
import type { BackupInProgressReason } from '~/pages/servers/manage/[id].vue'

View File

@@ -88,7 +88,7 @@
>
<EditIcon class="h-8 w-8 text-contrast" />
</div>
<UiServersServerIcon :image="icon" />
<ServerIcon :image="icon" />
</div>
<ButtonStyled>
<button v-tooltip="'Synchronize icon with installed modpack'" @click="resetIcon">
@@ -101,7 +101,7 @@
</div>
</div>
<div v-else />
<UiServersSaveBanner
<SaveBanner
:is-visible="!!hasUnsavedChanges && !!isValidServerName"
:server="props.server"
:is-updating="isUpdating"
@@ -116,6 +116,8 @@ import { EditIcon, TransferIcon } from '@modrinth/assets'
import { injectNotificationManager } from '@modrinth/ui'
import ButtonStyled from '@modrinth/ui/src/components/base/ButtonStyled.vue'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import ServerIcon from '~/components/ui/servers/ServerIcon.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -251,7 +251,7 @@
</div>
</div>
</div>
<UiServersSaveBanner
<SaveBanner
:is-visible="!!hasUnsavedChanges && !!isValidSubdomain"
:server="props.server"
:is-updating="isUpdating"
@@ -282,6 +282,7 @@ import {
} from '@modrinth/ui'
import { computed, nextTick, ref } from 'vue'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -31,7 +31,7 @@
</div>
</div>
</div>
<UiServersSaveBanner
<SaveBanner
:is-visible="hasUnsavedChanges"
:server="props.server"
:is-updating="false"
@@ -45,6 +45,7 @@
import { injectNotificationManager } from '@modrinth/ui'
import { useStorage } from '@vueuse/core'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -131,7 +131,7 @@
</p>
</div>
<UiServersSaveBanner
<SaveBanner
:is-visible="hasUnsavedChanges"
:server="props.server"
:is-updating="isUpdating"
@@ -144,10 +144,11 @@
<script setup lang="ts">
import { EyeIcon, IssuesIcon, SearchIcon } from '@modrinth/assets'
import { injectNotificationManager } from '@modrinth/ui'
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import Fuse from 'fuse.js'
import { computed, inject, ref, watch } from 'vue'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -101,7 +101,7 @@
</div>
</div>
</div>
<UiServersSaveBanner
<SaveBanner
:is-visible="!!hasUnsavedChanges"
:server="props.server"
:is-updating="isUpdating"
@@ -115,6 +115,7 @@
import { IssuesIcon, UpdatedIcon } from '@modrinth/assets'
import { ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import SaveBanner from '~/components/ui/servers/SaveBanner.vue'
import type { ModrinthServer } from '~/composables/servers/modrinth-servers.ts'
const { addNotification } = injectNotificationManager()

View File

@@ -53,7 +53,7 @@
</div>
</div>
<LazyUiServersServerManageEmptyState
<ServerManageEmptyState
v-else-if="serverList.length === 0 && !isPollingForNewServers && !hasError"
/>
@@ -93,12 +93,8 @@
v-if="filteredData.length > 0 || isPollingForNewServers"
class="m-0 flex flex-col gap-4 p-0"
>
<UiServersServerListing
v-for="server in filteredData"
:key="server.server_id"
v-bind="server"
/>
<LazyUiServersServerListingSkeleton v-if="isPollingForNewServers" />
<ServerListing v-for="server in filteredData" :key="server.server_id" v-bind="server" />
<ServerListingSkeleton v-if="isPollingForNewServers" />
</ul>
<div v-else class="flex h-full items-center justify-center">
<p class="text-contrast">No servers found.</p>
@@ -115,6 +111,9 @@ import Fuse from 'fuse.js'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { reloadNuxtApp } from '#app'
import ServerListing from '~/components/ui/servers/ServerListing.vue'
import ServerListingSkeleton from '~/components/ui/servers/ServerListingSkeleton.vue'
import ServerManageEmptyState from '~/components/ui/servers/ServerManageEmptyState.vue'
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
definePageMeta({

View File

@@ -205,12 +205,9 @@
>
<div class="flex flex-col justify-between gap-4">
<div class="flex flex-col gap-4">
<LazyUiServersModrinthServersIcon class="flex h-8 w-fit" />
<ModrinthServersIcon class="flex h-8 w-fit" />
<div class="flex flex-col gap-2">
<UiServersServerListing
v-if="subscription.serverInfo"
v-bind="subscription.serverInfo"
/>
<ServerListing v-if="subscription.serverInfo" v-bind="subscription.serverInfo" />
<div v-else class="w-fit">
<p>
A linked server couldn't be found for this subscription. There are a few possible
@@ -588,6 +585,8 @@ import {
import { calculateSavings, formatPrice, getCurrency } from '@modrinth/utils'
import { computed, ref } from 'vue'
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
import ServerListing from '~/components/ui/servers/ServerListing.vue'
import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
import { products } from '~/generated/state.json'

View File

@@ -207,6 +207,8 @@
import { CodeIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
import { Button, injectNotificationManager, ThemeSelector } from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { IntlFormatted } from '@vintl/vintl/components'
import MessageBanner from '~/components/ui/MessageBanner.vue'
import type { DisplayLocation } from '~/plugins/cosmetics'

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { IssuesIcon, RadioButtonCheckedIcon, RadioButtonIcon } from '@modrinth/assets'
import { commonSettingsMessages } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import Fuse from 'fuse.js/dist/fuse.basic'
import { isModifierKeyDown } from '~/helpers/events.ts'

View File

@@ -212,6 +212,7 @@ import {
injectNotificationManager,
useRelativeTime,
} from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import Modal from '~/components/ui/Modal.vue'
import {

View File

@@ -92,6 +92,7 @@
<script setup>
import { SaveIcon, TrashIcon, UndoIcon, UploadIcon, UserIcon, XIcon } from '@modrinth/assets'
import { Avatar, Button, commonMessages, FileInput, injectNotificationManager } from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
const { addNotification } = injectNotificationManager()
const { formatMessage } = useVIntl()

View File

@@ -366,6 +366,7 @@ import {
OverflowMenu,
useRelativeTime,
} from '@modrinth/ui'
import { IntlFormatted } from '@vintl/vintl/components'
import TenMClubBadge from '~/assets/images/badges/10m-club.svg?component'
import AlphaTesterBadge from '~/assets/images/badges/alpha-tester.svg?component'

View File

@@ -0,0 +1,5 @@
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:error', (error: any) => {
console.error('An error occurred:', error)
})
})

View File

@@ -1,186 +1,186 @@
{
"articles": [
{
"title": "Skins — Now in Modrinth App!",
"summary": "Customize your look, save your favorite skins, and swap them out in a flash, all within Modrinth App.",
"thumbnail": "https://modrinth.com/news/article/skins-now-in-modrinth-app/thumbnail.webp",
"date": "2025-07-06T23:45:00.000Z",
"link": "https://modrinth.com/news/article/skins-now-in-modrinth-app"
},
{
"title": "Creator Updates, July 2025",
"summary": "Addressing recent growth and growing pains that have been affecting creators.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2025-07-02T04:20:00.000Z",
"link": "https://modrinth.com/news/article/creator-updates-july-2025"
},
{
"title": "A Pride Month Success: Over $8,400 Raised for The Trevor Project!",
"summary": "Reflecting on our Pride Month fundraiser campaign for LGBTQ+ youth.",
"thumbnail": "https://modrinth.com/news/article/pride-campaign-2025/thumbnail.webp",
"date": "2025-07-01T18:00:00.000Z",
"link": "https://modrinth.com/news/article/pride-campaign-2025"
},
{
"title": "A New Chapter for Modrinth Servers",
"summary": "Modrinth Servers is now fully operated in-house by the Modrinth Team.",
"thumbnail": "https://modrinth.com/news/article/a-new-chapter-for-modrinth-servers/thumbnail.webp",
"date": "2025-03-13T00:00:00.000Z",
"link": "https://modrinth.com/news/article/a-new-chapter-for-modrinth-servers"
},
{
"title": "Host your own server with Modrinth Servers — now in beta",
"summary": "Fast, simple, reliable servers directly integrated into Modrinth.",
"thumbnail": "https://modrinth.com/news/article/modrinth-servers-beta/thumbnail.webp",
"date": "2024-11-03T06:00:00.000Z",
"link": "https://modrinth.com/news/article/modrinth-servers-beta"
},
{
"title": "Quintupling Creator Revenue and Becoming Sustainable",
"summary": "Announcing an update to our monetization program, creator split, and more!",
"thumbnail": "https://modrinth.com/news/article/becoming-sustainable/thumbnail.webp",
"date": "2024-09-13T20:00:00.000Z",
"link": "https://modrinth.com/news/article/becoming-sustainable"
},
{
"title": "Introducing Modrinth+, a refreshed site look, and a new advertising system!",
"summary": "Learn about this major update to Modrinth.",
"thumbnail": "https://modrinth.com/news/article/design-refresh/thumbnail.webp",
"date": "2024-08-21T20:00:00.000Z",
"link": "https://modrinth.com/news/article/design-refresh"
},
{
"title": "Malware Discovery Disclosure: \"Windows Borderless\" mod",
"summary": "Threat Analysis and Plan of Action",
"thumbnail": "https://modrinth.com/news/article/windows-borderless-malware-disclosure/thumbnail.webp",
"date": "2024-05-07T20:00:00.000Z",
"link": "https://modrinth.com/news/article/windows-borderless-malware-disclosure"
},
{
"title": "A Sustainable Path Forward for Modrinth",
"summary": "Our capital return and whats next.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2024-04-04T20:00:00.000Z",
"link": "https://modrinth.com/news/article/capital-return"
},
{
"title": "Creator Update: Analytics, Organizations, Collections, and more",
"summary": "December may be over, but were not done giving gifts.",
"thumbnail": "https://modrinth.com/news/article/creator-update/thumbnail.webp",
"date": "2024-01-06T20:00:00.000Z",
"link": "https://modrinth.com/news/article/creator-update"
},
{
"title": "Correcting Inflated Download Counts due to Rate Limiting Issue",
"summary": "A rate limiting issue caused inflated download counts in certain countries.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2023-11-10T20:00:00.000Z",
"link": "https://modrinth.com/news/article/download-adjustment"
},
{
"title": "Introducing Modrinth App Beta",
"summary": "Changing the modded Minecraft landscape with the new Modrinth App, alongside several other major features.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2023-08-05T20:00:00.000Z",
"link": "https://modrinth.com/news/article/modrinth-app-beta"
},
{
"title": "(April Fools 2023) Powering up your experience: Modrinth Technologies™ beta launch!",
"summary": "Welcome to the new era of Modrinth. We can't wait to hear your feedback.",
"thumbnail": "https://modrinth.com/news/article/new-site-beta/thumbnail.webp",
"date": "2023-04-01T08:00:00.000Z",
"link": "https://modrinth.com/news/article/new-site-beta"
},
{
"title": "Accelerating Modrinth's Development",
"summary": "Our fundraiser and the future of Modrinth!",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2023-02-01T20:00:00.000Z",
"link": "https://modrinth.com/news/article/accelerating-development"
},
{
"title": "Modrinth's Anniversary Update",
"summary": "Marking two years of Modrinth and discussing our New Year's Resolutions for 2023.",
"thumbnail": "https://modrinth.com/news/article/two-years-of-modrinth/thumbnail.webp",
"date": "2023-01-07T00:00:00.000Z",
"link": "https://modrinth.com/news/article/two-years-of-modrinth"
},
{
"title": "Two years of Modrinth: a retrospective",
"summary": "The history of Modrinth as we know it from December 2020 to December 2022.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2023-01-07T00:00:00.000Z",
"link": "https://modrinth.com/news/article/two-years-of-modrinth-history"
},
{
"title": "Creators can now make money on Modrinth!",
"summary": "Introducing the Creator Monetization Program allowing creators to earn revenue from their projects.",
"thumbnail": "https://modrinth.com/news/article/creator-monetization/thumbnail.webp",
"date": "2022-11-12T00:00:00.000Z",
"link": "https://modrinth.com/news/article/creator-monetization"
},
{
"title": "Modrinth's Carbon Ads experiment",
"summary": "Experimenting with a different ad providers to find one which one works for us.",
"thumbnail": "https://modrinth.com/news/article/carbon-ads/thumbnail.webp",
"date": "2022-09-08T00:00:00.000Z",
"link": "https://modrinth.com/news/article/carbon-ads"
},
{
"title": "Plugins and Resource Packs now have a home on Modrinth",
"summary": "A small update with a big impact: plugins and resource packs are now available on Modrinth!",
"thumbnail": "https://modrinth.com/news/article/plugins-resource-packs/thumbnail.webp",
"date": "2022-08-27T00:00:00.000Z",
"link": "https://modrinth.com/news/article/plugins-resource-packs"
},
{
"title": "Changes to Modrinth Modpacks",
"summary": "CurseForge CDN links requested to be removed by the end of the month",
"thumbnail": "https://modrinth.com/news/article/modpack-changes/thumbnail.webp",
"date": "2022-05-28T00:00:00.000Z",
"link": "https://modrinth.com/news/article/modpack-changes"
},
{
"title": "Modrinth Modpacks: Now in alpha testing",
"summary": "After over a year of development, we're happy to announce that modpack support is now in alpha testing.",
"thumbnail": "https://modrinth.com/news/article/modpacks-alpha/thumbnail.webp",
"date": "2022-05-15T00:00:00.000Z",
"link": "https://modrinth.com/news/article/modpacks-alpha"
},
{
"title": "This week in Modrinth development: Filters and Fixes",
"summary": "Continuing to improve the user interface after a great first week since Modrinth launched out of beta.",
"thumbnail": "https://modrinth.com/news/article/knossos-v2.1.0/thumbnail.webp",
"date": "2022-03-09T00:00:00.000Z",
"link": "https://modrinth.com/news/article/knossos-v2.1.0"
},
{
"title": "Now showing on Modrinth: A new look!",
"summary": "Releasing many new features and improvements, including a redesign!",
"thumbnail": "https://modrinth.com/news/article/redesign/thumbnail.webp",
"date": "2022-02-27T00:00:00.000Z",
"link": "https://modrinth.com/news/article/redesign"
},
{
"title": "Beginner's Guide to Licensing your Mods",
"summary": "Software licenses; the nitty-gritty legal aspect of software development. They're more important than you think.",
"thumbnail": "https://modrinth.com/news/article/licensing-guide/thumbnail.webp",
"date": "2021-05-16T00:00:00.000Z",
"link": "https://modrinth.com/news/article/licensing-guide"
},
{
"title": "Welcome to Modrinth Beta",
"summary": "After six months of work, Modrinth enters Beta, helping modders host their mods with ease!",
"thumbnail": "https://modrinth.com/news/article/modrinth-beta/thumbnail.webp",
"date": "2020-12-01T00:00:00.000Z",
"link": "https://modrinth.com/news/article/modrinth-beta"
},
{
"title": "What is Modrinth?",
"summary": "Hello, we are Modrinth an open source mods hosting platform. Sounds dry, doesn't it? So let me tell you our story and I promise, it won't be boring!",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2020-11-27T00:00:00.000Z",
"link": "https://modrinth.com/news/article/whats-modrinth"
}
]
"articles": [
{
"title": "Skins — Now in Modrinth App!",
"summary": "Customize your look, save your favorite skins, and swap them out in a flash, all within Modrinth App.",
"thumbnail": "https://modrinth.com/news/article/skins-now-in-modrinth-app/thumbnail.webp",
"date": "2025-07-06T23:45:00.000Z",
"link": "https://modrinth.com/news/article/skins-now-in-modrinth-app"
},
{
"title": "Creator Updates, July 2025",
"summary": "Addressing recent growth and growing pains that have been affecting creators.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2025-07-02T04:20:00.000Z",
"link": "https://modrinth.com/news/article/creator-updates-july-2025"
},
{
"title": "A Pride Month Success: Over $8,400 Raised for The Trevor Project!",
"summary": "Reflecting on our Pride Month fundraiser campaign for LGBTQ+ youth.",
"thumbnail": "https://modrinth.com/news/article/pride-campaign-2025/thumbnail.webp",
"date": "2025-07-01T18:00:00.000Z",
"link": "https://modrinth.com/news/article/pride-campaign-2025"
},
{
"title": "A New Chapter for Modrinth Servers",
"summary": "Modrinth Servers is now fully operated in-house by the Modrinth Team.",
"thumbnail": "https://modrinth.com/news/article/a-new-chapter-for-modrinth-servers/thumbnail.webp",
"date": "2025-03-13T00:00:00.000Z",
"link": "https://modrinth.com/news/article/a-new-chapter-for-modrinth-servers"
},
{
"title": "Host your own server with Modrinth Servers — now in beta",
"summary": "Fast, simple, reliable servers directly integrated into Modrinth.",
"thumbnail": "https://modrinth.com/news/article/modrinth-servers-beta/thumbnail.webp",
"date": "2024-11-03T06:00:00.000Z",
"link": "https://modrinth.com/news/article/modrinth-servers-beta"
},
{
"title": "Quintupling Creator Revenue and Becoming Sustainable",
"summary": "Announcing an update to our monetization program, creator split, and more!",
"thumbnail": "https://modrinth.com/news/article/becoming-sustainable/thumbnail.webp",
"date": "2024-09-13T20:00:00.000Z",
"link": "https://modrinth.com/news/article/becoming-sustainable"
},
{
"title": "Introducing Modrinth+, a refreshed site look, and a new advertising system!",
"summary": "Learn about this major update to Modrinth.",
"thumbnail": "https://modrinth.com/news/article/design-refresh/thumbnail.webp",
"date": "2024-08-21T20:00:00.000Z",
"link": "https://modrinth.com/news/article/design-refresh"
},
{
"title": "Malware Discovery Disclosure: \"Windows Borderless\" mod",
"summary": "Threat Analysis and Plan of Action",
"thumbnail": "https://modrinth.com/news/article/windows-borderless-malware-disclosure/thumbnail.webp",
"date": "2024-05-07T20:00:00.000Z",
"link": "https://modrinth.com/news/article/windows-borderless-malware-disclosure"
},
{
"title": "A Sustainable Path Forward for Modrinth",
"summary": "Our capital return and whats next.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2024-04-04T20:00:00.000Z",
"link": "https://modrinth.com/news/article/capital-return"
},
{
"title": "Creator Update: Analytics, Organizations, Collections, and more",
"summary": "December may be over, but were not done giving gifts.",
"thumbnail": "https://modrinth.com/news/article/creator-update/thumbnail.webp",
"date": "2024-01-06T20:00:00.000Z",
"link": "https://modrinth.com/news/article/creator-update"
},
{
"title": "Correcting Inflated Download Counts due to Rate Limiting Issue",
"summary": "A rate limiting issue caused inflated download counts in certain countries.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2023-11-10T20:00:00.000Z",
"link": "https://modrinth.com/news/article/download-adjustment"
},
{
"title": "Introducing Modrinth App Beta",
"summary": "Changing the modded Minecraft landscape with the new Modrinth App, alongside several other major features.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2023-08-05T20:00:00.000Z",
"link": "https://modrinth.com/news/article/modrinth-app-beta"
},
{
"title": "(April Fools 2023) Powering up your experience: Modrinth Technologies™ beta launch!",
"summary": "Welcome to the new era of Modrinth. We can't wait to hear your feedback.",
"thumbnail": "https://modrinth.com/news/article/new-site-beta/thumbnail.webp",
"date": "2023-04-01T08:00:00.000Z",
"link": "https://modrinth.com/news/article/new-site-beta"
},
{
"title": "Accelerating Modrinth's Development",
"summary": "Our fundraiser and the future of Modrinth!",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2023-02-01T20:00:00.000Z",
"link": "https://modrinth.com/news/article/accelerating-development"
},
{
"title": "Modrinth's Anniversary Update",
"summary": "Marking two years of Modrinth and discussing our New Year's Resolutions for 2023.",
"thumbnail": "https://modrinth.com/news/article/two-years-of-modrinth/thumbnail.webp",
"date": "2023-01-07T00:00:00.000Z",
"link": "https://modrinth.com/news/article/two-years-of-modrinth"
},
{
"title": "Two years of Modrinth: a retrospective",
"summary": "The history of Modrinth as we know it from December 2020 to December 2022.",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2023-01-07T00:00:00.000Z",
"link": "https://modrinth.com/news/article/two-years-of-modrinth-history"
},
{
"title": "Creators can now make money on Modrinth!",
"summary": "Introducing the Creator Monetization Program allowing creators to earn revenue from their projects.",
"thumbnail": "https://modrinth.com/news/article/creator-monetization/thumbnail.webp",
"date": "2022-11-12T00:00:00.000Z",
"link": "https://modrinth.com/news/article/creator-monetization"
},
{
"title": "Modrinth's Carbon Ads experiment",
"summary": "Experimenting with a different ad providers to find one which one works for us.",
"thumbnail": "https://modrinth.com/news/article/carbon-ads/thumbnail.webp",
"date": "2022-09-08T00:00:00.000Z",
"link": "https://modrinth.com/news/article/carbon-ads"
},
{
"title": "Plugins and Resource Packs now have a home on Modrinth",
"summary": "A small update with a big impact: plugins and resource packs are now available on Modrinth!",
"thumbnail": "https://modrinth.com/news/article/plugins-resource-packs/thumbnail.webp",
"date": "2022-08-27T00:00:00.000Z",
"link": "https://modrinth.com/news/article/plugins-resource-packs"
},
{
"title": "Changes to Modrinth Modpacks",
"summary": "CurseForge CDN links requested to be removed by the end of the month",
"thumbnail": "https://modrinth.com/news/article/modpack-changes/thumbnail.webp",
"date": "2022-05-28T00:00:00.000Z",
"link": "https://modrinth.com/news/article/modpack-changes"
},
{
"title": "Modrinth Modpacks: Now in alpha testing",
"summary": "After over a year of development, we're happy to announce that modpack support is now in alpha testing.",
"thumbnail": "https://modrinth.com/news/article/modpacks-alpha/thumbnail.webp",
"date": "2022-05-15T00:00:00.000Z",
"link": "https://modrinth.com/news/article/modpacks-alpha"
},
{
"title": "This week in Modrinth development: Filters and Fixes",
"summary": "Continuing to improve the user interface after a great first week since Modrinth launched out of beta.",
"thumbnail": "https://modrinth.com/news/article/knossos-v2.1.0/thumbnail.webp",
"date": "2022-03-09T00:00:00.000Z",
"link": "https://modrinth.com/news/article/knossos-v2.1.0"
},
{
"title": "Now showing on Modrinth: A new look!",
"summary": "Releasing many new features and improvements, including a redesign!",
"thumbnail": "https://modrinth.com/news/article/redesign/thumbnail.webp",
"date": "2022-02-27T00:00:00.000Z",
"link": "https://modrinth.com/news/article/redesign"
},
{
"title": "Beginner's Guide to Licensing your Mods",
"summary": "Software licenses; the nitty-gritty legal aspect of software development. They're more important than you think.",
"thumbnail": "https://modrinth.com/news/article/licensing-guide/thumbnail.webp",
"date": "2021-05-16T00:00:00.000Z",
"link": "https://modrinth.com/news/article/licensing-guide"
},
{
"title": "Welcome to Modrinth Beta",
"summary": "After six months of work, Modrinth enters Beta, helping modders host their mods with ease!",
"thumbnail": "https://modrinth.com/news/article/modrinth-beta/thumbnail.webp",
"date": "2020-12-01T00:00:00.000Z",
"link": "https://modrinth.com/news/article/modrinth-beta"
},
{
"title": "What is Modrinth?",
"summary": "Hello, we are Modrinth an open source mods hosting platform. Sounds dry, doesn't it? So let me tell you our story and I promise, it won't be boring!",
"thumbnail": "https://modrinth.com/news/default.webp",
"date": "2020-11-27T00:00:00.000Z",
"link": "https://modrinth.com/news/article/whats-modrinth"
}
]
}

View File

@@ -8,26 +8,26 @@ import _ArrowBigUpDashIcon from './icons/arrow-big-up-dash.svg?component'
import _AsteriskIcon from './icons/asterisk.svg?component'
import _BadgeCheckIcon from './icons/badge-check.svg?component'
import _BanIcon from './icons/ban.svg?component'
import _BellRingIcon from './icons/bell-ring.svg?component'
import _BellIcon from './icons/bell.svg?component'
import _BellRingIcon from './icons/bell-ring.svg?component'
import _BlocksIcon from './icons/blocks.svg?component'
import _BoldIcon from './icons/bold.svg?component'
import _BookIcon from './icons/book.svg?component'
import _BookOpenIcon from './icons/book-open.svg?component'
import _BookTextIcon from './icons/book-text.svg?component'
import _BookIcon from './icons/book.svg?component'
import _BookmarkIcon from './icons/bookmark.svg?component'
import _BotIcon from './icons/bot.svg?component'
import _BoxImportIcon from './icons/box-import.svg?component'
import _BoxIcon from './icons/box.svg?component'
import _BoxImportIcon from './icons/box-import.svg?component'
import _BracesIcon from './icons/braces.svg?component'
import _BrushCleaningIcon from './icons/brush-cleaning.svg?component'
import _CalendarIcon from './icons/calendar.svg?component'
import _CardIcon from './icons/card.svg?component'
import _ChangeSkinIcon from './icons/change-skin.svg?component'
import _ChartIcon from './icons/chart.svg?component'
import _CheckIcon from './icons/check.svg?component'
import _CheckCheckIcon from './icons/check-check.svg?component'
import _CheckCircleIcon from './icons/check-circle.svg?component'
import _CheckIcon from './icons/check.svg?component'
import _ChevronLeftIcon from './icons/chevron-left.svg?component'
import _ChevronRightIcon from './icons/chevron-right.svg?component'
import _ClearIcon from './icons/clear.svg?component'
@@ -56,13 +56,13 @@ import _EditIcon from './icons/edit.svg?component'
import _EllipsisVerticalIcon from './icons/ellipsis-vertical.svg?component'
import _ExpandIcon from './icons/expand.svg?component'
import _ExternalIcon from './icons/external.svg?component'
import _EyeOffIcon from './icons/eye-off.svg?component'
import _EyeIcon from './icons/eye.svg?component'
import _EyeOffIcon from './icons/eye-off.svg?component'
import _FileIcon from './icons/file.svg?component'
import _FileArchiveIcon from './icons/file-archive.svg?component'
import _FileTextIcon from './icons/file-text.svg?component'
import _FileIcon from './icons/file.svg?component'
import _FilterXIcon from './icons/filter-x.svg?component'
import _FilterIcon from './icons/filter.svg?component'
import _FilterXIcon from './icons/filter-x.svg?component'
import _FolderArchiveIcon from './icons/folder-archive.svg?component'
import _FolderOpenIcon from './icons/folder-open.svg?component'
import _FolderSearchIcon from './icons/folder-search.svg?component'
@@ -79,8 +79,8 @@ import _HashIcon from './icons/hash.svg?component'
import _Heading1Icon from './icons/heading-1.svg?component'
import _Heading2Icon from './icons/heading-2.svg?component'
import _Heading3Icon from './icons/heading-3.svg?component'
import _HeartHandshakeIcon from './icons/heart-handshake.svg?component'
import _HeartIcon from './icons/heart.svg?component'
import _HeartHandshakeIcon from './icons/heart-handshake.svg?component'
import _HistoryIcon from './icons/history.svg?component'
import _HomeIcon from './icons/home.svg?component'
import _ImageIcon from './icons/image.svg?component'
@@ -96,13 +96,13 @@ import _LeftArrowIcon from './icons/left-arrow.svg?component'
import _LibraryIcon from './icons/library.svg?component'
import _LightBulbIcon from './icons/light-bulb.svg?component'
import _LinkIcon from './icons/link.svg?component'
import _ListIcon from './icons/list.svg?component'
import _ListBulletedIcon from './icons/list-bulleted.svg?component'
import _ListEndIcon from './icons/list-end.svg?component'
import _ListOrderedIcon from './icons/list-ordered.svg?component'
import _ListIcon from './icons/list.svg?component'
import _LoaderIcon from './icons/loader.svg?component'
import _LockOpenIcon from './icons/lock-open.svg?component'
import _LockIcon from './icons/lock.svg?component'
import _LockOpenIcon from './icons/lock-open.svg?component'
import _LogInIcon from './icons/log-in.svg?component'
import _LogOutIcon from './icons/log-out.svg?component'
import _MailIcon from './icons/mail.svg?component'
@@ -113,8 +113,8 @@ import _MessageIcon from './icons/message.svg?component'
import _MicrophoneIcon from './icons/microphone.svg?component'
import _MinimizeIcon from './icons/minimize.svg?component'
import _MinusIcon from './icons/minus.svg?component'
import _MonitorSmartphoneIcon from './icons/monitor-smartphone.svg?component'
import _MonitorIcon from './icons/monitor.svg?component'
import _MonitorSmartphoneIcon from './icons/monitor-smartphone.svg?component'
import _MoonIcon from './icons/moon.svg?component'
import _MoreHorizontalIcon from './icons/more-horizontal.svg?component'
import _MoreVerticalIcon from './icons/more-vertical.svg?component'
@@ -123,16 +123,16 @@ import _NoSignalIcon from './icons/no-signal.svg?component'
import _NotepadTextIcon from './icons/notepad-text.svg?component'
import _OmorphiaIcon from './icons/omorphia.svg?component'
import _OrganizationIcon from './icons/organization.svg?component'
import _PackageIcon from './icons/package.svg?component'
import _PackageClosedIcon from './icons/package-closed.svg?component'
import _PackageOpenIcon from './icons/package-open.svg?component'
import _PackageIcon from './icons/package.svg?component'
import _PaintbrushIcon from './icons/paintbrush.svg?component'
import _PickaxeIcon from './icons/pickaxe.svg?component'
import _PlayIcon from './icons/play.svg?component'
import _PlugIcon from './icons/plug.svg?component'
import _PlusIcon from './icons/plus.svg?component'
import _RadioButtonCheckedIcon from './icons/radio-button-checked.svg?component'
import _RadioButtonIcon from './icons/radio-button.svg?component'
import _RadioButtonCheckedIcon from './icons/radio-button-checked.svg?component'
import _ReceiptTextIcon from './icons/receipt-text.svg?component'
import _RedoIcon from './icons/redo.svg?component'
import _ReplyIcon from './icons/reply.svg?component'
@@ -147,8 +147,8 @@ import _ScaleIcon from './icons/scale.svg?component'
import _ScanEyeIcon from './icons/scan-eye.svg?component'
import _SearchIcon from './icons/search.svg?component'
import _SendIcon from './icons/send.svg?component'
import _ServerPlusIcon from './icons/server-plus.svg?component'
import _ServerIcon from './icons/server.svg?component'
import _ServerPlusIcon from './icons/server-plus.svg?component'
import _SettingsIcon from './icons/settings.svg?component'
import _ShareIcon from './icons/share.svg?component'
import _ShieldIcon from './icons/shield.svg?component'
@@ -177,23 +177,23 @@ import _TrashIcon from './icons/trash.svg?component'
import _TriangleAlertIcon from './icons/triangle-alert.svg?component'
import _UnderlineIcon from './icons/underline.svg?component'
import _UndoIcon from './icons/undo.svg?component'
import _UnknownDonationIcon from './icons/unknown-donation.svg?component'
import _UnknownIcon from './icons/unknown.svg?component'
import _UnknownDonationIcon from './icons/unknown-donation.svg?component'
import _UnlinkIcon from './icons/unlink.svg?component'
import _UnplugIcon from './icons/unplug.svg?component'
import _UpdatedIcon from './icons/updated.svg?component'
import _UploadIcon from './icons/upload.svg?component'
import _UserIcon from './icons/user.svg?component'
import _UserPlusIcon from './icons/user-plus.svg?component'
import _UserXIcon from './icons/user-x.svg?component'
import _UserIcon from './icons/user.svg?component'
import _UsersIcon from './icons/users.svg?component'
import _VersionIcon from './icons/version.svg?component'
import _WikiIcon from './icons/wiki.svg?component'
import _WindowIcon from './icons/window.svg?component'
import _WorldIcon from './icons/world.svg?component'
import _WrenchIcon from './icons/wrench.svg?component'
import _XCircleIcon from './icons/x-circle.svg?component'
import _XIcon from './icons/x.svg?component'
import _XCircleIcon from './icons/x-circle.svg?component'
import _ZoomInIcon from './icons/zoom-in.svg?component'
import _ZoomOutIcon from './icons/zoom-out.svg?component'

View File

@@ -212,7 +212,7 @@ async function generateJsonFile(articles): Promise<void> {
)
const json = { articles: sorted }
await fs.mkdir(path.dirname(JSON_PATH), { recursive: true })
await fs.writeFile(JSON_PATH, JSON.stringify(json, null, 2) + '\n', 'utf8')
await fs.writeFile(JSON_PATH, JSON.stringify(json, null, '\t') + '\n', 'utf8')
console.log(`📝 Wrote JSON articles to ${JSON_PATH}`)
}

View File

@@ -1,30 +1,30 @@
// AUTO-GENERATED FILE - DO NOT EDIT
import { article as windows_borderless_malware_disclosure } from "./windows_borderless_malware_disclosure";
import { article as whats_modrinth } from "./whats_modrinth";
import { article as a_new_chapter_for_modrinth_servers } from "./a_new_chapter_for_modrinth_servers";
import { article as accelerating_development } from "./accelerating_development";
import { article as becoming_sustainable } from "./becoming_sustainable";
import { article as capital_return } from "./capital_return";
import { article as carbon_ads } from "./carbon_ads";
import { article as creator_monetization } from "./creator_monetization";
import { article as creator_update } from "./creator_update";
import { article as creator_updates_july_2025 } from "./creator_updates_july_2025";
import { article as design_refresh } from "./design_refresh";
import { article as download_adjustment } from "./download_adjustment";
import { article as knossos_v2_1_0 } from "./knossos_v2_1_0";
import { article as licensing_guide } from "./licensing_guide";
import { article as modpack_changes } from "./modpack_changes";
import { article as modpacks_alpha } from "./modpacks_alpha";
import { article as modrinth_app_beta } from "./modrinth_app_beta";
import { article as modrinth_beta } from "./modrinth_beta";
import { article as modrinth_servers_beta } from "./modrinth_servers_beta";
import { article as new_site_beta } from "./new_site_beta";
import { article as plugins_resource_packs } from "./plugins_resource_packs";
import { article as pride_campaign_2025 } from "./pride_campaign_2025";
import { article as redesign } from "./redesign";
import { article as skins_now_in_modrinth_app } from "./skins_now_in_modrinth_app";
import { article as two_years_of_modrinth } from "./two_years_of_modrinth";
import { article as two_years_of_modrinth_history } from "./two_years_of_modrinth_history";
import { article as skins_now_in_modrinth_app } from "./skins_now_in_modrinth_app";
import { article as redesign } from "./redesign";
import { article as pride_campaign_2025 } from "./pride_campaign_2025";
import { article as plugins_resource_packs } from "./plugins_resource_packs";
import { article as new_site_beta } from "./new_site_beta";
import { article as modrinth_servers_beta } from "./modrinth_servers_beta";
import { article as modrinth_beta } from "./modrinth_beta";
import { article as modrinth_app_beta } from "./modrinth_app_beta";
import { article as modpacks_alpha } from "./modpacks_alpha";
import { article as modpack_changes } from "./modpack_changes";
import { article as licensing_guide } from "./licensing_guide";
import { article as knossos_v2_1_0 } from "./knossos_v2_1_0";
import { article as download_adjustment } from "./download_adjustment";
import { article as design_refresh } from "./design_refresh";
import { article as creator_updates_july_2025 } from "./creator_updates_july_2025";
import { article as creator_update } from "./creator_update";
import { article as creator_monetization } from "./creator_monetization";
import { article as carbon_ads } from "./carbon_ads";
import { article as capital_return } from "./capital_return";
import { article as becoming_sustainable } from "./becoming_sustainable";
import { article as accelerating_development } from "./accelerating_development";
import { article as a_new_chapter_for_modrinth_servers } from "./a_new_chapter_for_modrinth_servers";
import { article as whats_modrinth } from "./whats_modrinth";
import { article as windows_borderless_malware_disclosure } from "./windows_borderless_malware_disclosure";
export const articles = [
windows_borderless_malware_disclosure,

View File

@@ -19,6 +19,26 @@ export const configurationNuxtToAppend = [
rules: {
'vue/html-self-closing': 'off',
'vue/multi-word-component-names': 'off',
'vue/no-undef-components': [
'error',
{
ignorePatterns: [
'NuxtPage',
'NuxtLayout',
'NuxtLink',
'ClientOnly',
'Teleport',
'Transition',
'TransitionGroup',
'Head',
'Title',
'router-link',
'RouterView',
'RouterLink',
'nuxt-link',
],
},
],
},
languageOptions: {
parserOptions: {

View File

@@ -86,7 +86,7 @@
}"
@click="serverLoader = loader"
>
<UiServersIconsLoaderIcon :loader="loader" class="!h-12 !w-12" />
<LoaderIcon :loader="loader" class="!h-12 !w-12" />
<p class="text-lg font-bold m-0">{{ loader }}</p>
</button>
</div>
@@ -552,6 +552,7 @@ import Checkbox from '../base/Checkbox.vue'
import Slider from '../base/Slider.vue'
import AnimatedLogo from '../brand/AnimatedLogo.vue'
import NewModal from '../modal/NewModal.vue'
import LoaderIcon from '../servers/LoaderIcon.vue'
const { locale, formatMessage } = useVIntl()

View File

@@ -24,6 +24,7 @@ import { regionOverrides } from '../../utils/regions'
import ButtonStyled from '../base/ButtonStyled.vue'
import Checkbox from '../base/Checkbox.vue'
import TagItem from '../base/TagItem.vue'
import ModrinthServersIcon from '../servers/ModrinthServersIcon.vue'
import ExpandableInvoiceTotal from './ExpandableInvoiceTotal.vue'
import FormattedPaymentMethod from './FormattedPaymentMethod.vue'
import ServersSpecs from './ServersSpecs.vue'
@@ -93,7 +94,7 @@ function setInterval(newInterval: ServerBillingInterval) {
<div class="grid sm:grid-cols-[3fr_2fr] gap-4">
<div class="bg-table-alternateRow p-4 rounded-2xl">
<div class="flex items-center gap-2 mb-3">
<LazyUiServersModrinthServersIcon class="flex h-5 w-fit" />
<ModrinthServersIcon class="flex h-5 w-fit" />
<TagItem>{{ planName }}</TagItem>
</div>
<div>

View File

@@ -0,0 +1,232 @@
<template>
<svg
v-if="loader === 'Fabric'"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
clip-rule="evenodd"
viewBox="0 0 24 24"
>
<path fill="none" d="M0 0h24v24H0z" />
<path
fill="none"
stroke="currentColor"
stroke-width="23"
d="m820 761-85.6-87.6c-4.6-4.7-10.4-9.6-25.9 1-19.9 13.6-8.4 21.9-5.2 25.4 8.2 9 84.1 89 97.2 104 2.5 2.8-20.3-22.5-6.5-39.7 5.4-7 18-12 26-3 6.5 7.3 10.7 18-3.4 29.7-24.7 20.4-102 82.4-127 103-12.5 10.3-28.5 2.3-35.8-6-7.5-8.9-30.6-34.6-51.3-58.2-5.5-6.3-4.1-19.6 2.3-25 35-30.3 91.9-73.8 111.9-90.8"
transform="matrix(.08671 0 0 .0867 -49.8 -56)"
/>
</svg>
<svg
v-else-if="loader === 'Quilt'"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
stroke-miterlimit="2"
clip-rule="evenodd"
viewBox="0 0 24 24"
>
<defs>
<path
id="quilt"
fill="none"
stroke="currentColor"
stroke-width="65.6"
d="M442.5 233.9c0-6.4-5.2-11.6-11.6-11.6h-197c-6.4 0-11.6 5.2-11.6 11.6v197c0 6.4 5.2 11.6 11.6 11.6h197c6.4 0 11.6-5.2 11.6-11.7v-197Z"
></path>
</defs>
<path fill="none" d="M0 0h24v24H0z"></path>
<use
xlink:href="#quilt"
stroke-width="65.6"
transform="matrix(.03053 0 0 .03046 -3.2 -3.2)"
></use>
<use xlink:href="#quilt" stroke-width="65.6" transform="matrix(.03053 0 0 .03046 -3.2 7)"></use>
<use
xlink:href="#quilt"
stroke-width="65.6"
transform="matrix(.03053 0 0 .03046 6.9 -3.2)"
></use>
<path
fill="none"
stroke="currentColor"
stroke-width="70.4"
d="M442.5 234.8c0-7-5.6-12.5-12.5-12.5H234.7c-6.8 0-12.4 5.6-12.4 12.5V430c0 6.9 5.6 12.5 12.4 12.5H430c6.9 0 12.5-5.6 12.5-12.5V234.8Z"
transform="rotate(45 3.5 24) scale(.02843 .02835)"
></path>
</svg>
<svg
v-else-if="loader === 'Forge'"
ml:space="preserve"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
stroke-miterlimit="1.5"
clip-rule="evenodd"
viewBox="0 0 24 24"
>
<path fill="none" d="M0 0h24v24H0z"></path>
<path
fill="none"
stroke="currentColor"
stroke-width="2"
d="M2 7.5h8v-2h12v2s-7 3.4-7 6 3.1 3.1 3.1 3.1l.9 3.9H5l1-4.1s3.8.1 4-2.9c.2-2.7-6.5-.7-8-6Z"
></path>
</svg>
<svg
v-else-if="loader === 'NeoForge'"
enable-background="new 0 0 24 24"
version="1.1"
viewBox="0 0 24 24"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
>
<g
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
>
<path d="m12 19.2v2m0-2v2" />
<path
d="m8.4 1.3c0.5 1.5 0.7 3 0.1 4.6-0.2 0.5-0.9 1.5-1.6 1.5m8.7-6.1c-0.5 1.5-0.7 3-0.1 4.6 0.2 0.6 0.9 1.5 1.6 1.5"
/>
<path d="m3.6 15.8h-1.7m18.5 0h1.7" />
<path d="m3.2 12.1h-1.7m19.3 0h1.8" />
<path d="m8.1 12.7v1.6m7.8-1.6v1.6" />
<path d="m10.8 18h1.2m0 1.2-1.2-1.2m2.4 0h-1.2m0 1.2 1.2-1.2" />
<path
d="m4 9.7c-0.5 1.2-0.8 2.4-0.8 3.7 0 3.1 2.9 6.3 5.3 8.2 0.9 0.7 2.2 1.1 3.4 1.1m0.1-17.8c-1.1 0-2.1 0.2-3.2 0.7m11.2 4.1c0.5 1.2 0.8 2.4 0.8 3.7 0 3.1-2.9 6.3-5.3 8.2-0.9 0.7-2.2 1.1-3.4 1.1m-0.1-17.8c1.1 0 2.1 0.2 3.2 0.7"
/>
<path
d="m4 9.7c-0.2-1.8-0.3-3.7 0.5-5.5s2.2-2.6 3.9-3m11.6 8.5c0.2-1.9 0.3-3.7-0.5-5.5s-2.2-2.6-3.9-3"
/>
<path d="m12 21.2-2.4 0.4m2.4-0.4 2.4 0.4" />
</g>
</svg>
<svg
v-else-if="loader === 'Paper'"
xml:space="preserve"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
stroke-miterlimit="1.5"
clip-rule="evenodd"
viewBox="0 0 24 24"
>
<path fill="none" d="M0 0h24v24H0z" />
<path fill="none" stroke="currentColor" stroke-width="2" d="m12 18 6 2 3-17L2 14l6 2" />
<path stroke="currentColor" stroke-width="2" d="m9 21-1-5 4 2-3 3Z" />
<path fill="currentColor" d="m12 18-4-2 10-9-6 11Z" />
</svg>
<svg
v-else-if="loader === 'Spigot'"
viewBox="0 0 332 284"
style="
fill-rule: evenodd;
clip-rule: evenodd;
stroke-linejoin: round;
fill: none;
fill-rule: nonzero;
stroke-width: 24px;
"
stroke="currentColor"
>
<path
d="M147.5,27l27,-15l27.5,15l66.5,0l0,33.5l-73,-0.912l0,45.5l26,-0.088l0,31.5l-12.5,0l0,15.5l16,21.5l35,0l0,-21.5l35.5,0l0,21.5l24.5,0l0,55.5l-24.5,0l0,17l-35.5,0l0,-27l-35,0l-55.5,14.5l-67.5,-14.5l-15,14.5l18,12.5l-3,24.5l-41.5,1.5l-48.5,-19.5l6,-19l24.5,-4.5l16,-41l79,-36l-7,-15.5l0,-31.5l23.5,0l0,-45.5l-73.5,0l0,-32.5l67,0Z"
/>
</svg>
<svg
v-else-if="loader === 'Bukkit'"
viewBox="0 0 292 319"
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linecap: round; stroke-linejoin: round"
stroke="currentColor"
>
<g transform="matrix(1,0,0,1,0,-5)">
<path
d="M12,109.5L12,155L34.5,224L57.5,224L57.5,271L81,294L160,294L160,172L259.087,172L265,155L265,109.5M12,109.5L12,64L34.5,64L34.5,41L81,17L195.5,17L241,41L241,64L265,64L265,109.5M12,109.5L81,109.5L81,132L195.5,132L195.5,109.5L265,109.5M264.087,204L264.087,244M207.5,272L207.5,312M250,272L250,312L280,312L280,272L250,272ZM192.5,204L192.5,244L222.5,244L222.5,204L192.5,204Z"
style="fill: none; fill-rule: nonzero; stroke-width: 24px"
/>
</g>
</svg>
<svg
v-else-if="loader === 'Purpur'"
xml:space="preserve"
fill-rule="evenodd"
stroke-linecap="round"
stroke-linejoin="round"
stroke-miterlimit="1.5"
clip-rule="evenodd"
viewBox="0 0 24 24"
>
<defs>
<path
id="purpur"
fill="none"
stroke="currentColor"
stroke-width="1.68"
d="m264 41.95 8-4v8l-8 4v-8Z"
></path>
</defs>
<path fill="none" d="M0 0h24v24H0z"></path>
<path
fill="none"
stroke="currentColor"
stroke-width="1.77"
d="m264 29.95-8 4 8 4.42 8-4.42-8-4Z"
transform="matrix(1.125 0 0 1.1372 -285 -31.69)"
></path>
<path
fill="none"
stroke="currentColor"
stroke-width="1.77"
d="m272 38.37-8 4.42-8-4.42"
transform="matrix(1.125 0 0 1.1372 -285 -31.69)"
></path>
<path
fill="none"
stroke="currentColor"
stroke-width="1.77"
d="m260 31.95 8 4.21V45"
transform="matrix(1.125 0 0 1.1372 -285 -31.69)"
></path>
<path
fill="none"
stroke="currentColor"
stroke-width="1.77"
d="M260 45v-8.84l8-4.21"
transform="matrix(1.125 0 0 1.1372 -285 -31.69)"
></path>
<use
xlink:href="#purpur"
stroke-width="1.68"
transform="matrix(1.125 0 0 1.2569 -285 -40.78)"
></use>
<use
xlink:href="#purpur"
stroke-width="1.68"
transform="matrix(-1.125 0 0 1.2569 309 -40.78)"
></use>
</svg>
<svg v-else-if="loader === 'Vanilla'" viewBox="0 0 20 20" fill="currentColor">
<path
fill-rule="evenodd"
d="M9.504 1.132a1 1 0 01.992 0l1.75 1a1 1 0 11-.992 1.736L10 3.152l-1.254.716a1 1 0 11-.992-1.736l1.75-1zM5.618 4.504a1 1 0 01-.372 1.364L5.016 6l.23.132a1 1 0 11-.992 1.736L4 7.723V8a1 1 0 01-2 0V6a.996.996 0 01.52-.878l1.734-.99a1 1 0 011.364.372zm8.764 0a1 1 0 011.364-.372l1.733.99A1.002 1.002 0 0118 6v2a1 1 0 11-2 0v-.277l-.254.145a1 1 0 11-.992-1.736l.23-.132-.23-.132a1 1 0 01-.372-1.364zm-7 4a1 1 0 011.364-.372L10 8.848l1.254-.716a1 1 0 11.992 1.736L11 10.58V12a1 1 0 11-2 0v-1.42l-1.246-.712a1 1 0 01-.372-1.364zM3 11a1 1 0 011 1v1.42l1.246.712a1 1 0 11-.992 1.736l-1.75-1A1 1 0 012 14v-2a1 1 0 011-1zm14 0a1 1 0 011 1v2a1 1 0 01-.504.868l-1.75 1a1 1 0 11-.992-1.736L16 13.42V12a1 1 0 011-1zm-9.618 5.504a1 1 0 011.364-.372l.254.145V16a1 1 0 112 0v.277l.254-.145a1 1 0 11.992 1.736l-1.735.992a.995.995 0 01-1.022 0l-1.735-.992a1 1 0 01-.372-1.364z"
clip-rule="evenodd"
></path>
</svg>
<LoaderIcon v-else />
</template>
<script setup lang="ts">
import { LoaderIcon } from '@modrinth/assets'
import type { Loaders } from '@modrinth/utils'
defineProps<{
loader: Loaders
}>()
</script>

View File

@@ -0,0 +1,53 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:serif="http://www.serif.com/"
version="1.1"
viewBox="0 0 1793 199"
>
<g>
<g id="Layer_1">
<g id="green" fill="var(--color-brand)">
<path
d="M1184.1,166.6c-8,0-15.6-1-22.9-3.1s-13.1-4.6-17.4-7.6l8.5-16.9c4.3,2.7,9.4,5,15.3,6.8,5.9,1.8,11.9,2.7,17.8,2.7s12.1-.9,15.2-2.8c3.1-1.9,4.7-4.5,4.7-7.7s-1.1-4.6-3.2-6c-2.1-1.4-4.9-2.4-8.4-3.1-3.4-.7-7.3-1.4-11.5-2-4.2-.6-8.4-1.4-12.6-2.4-4.2-1-8-2.5-11.5-4.5-3.4-2-6.2-4.6-8.4-7.9-2.1-3.3-3.2-7.7-3.2-13.2s1.7-11.3,5.2-15.8c3.4-4.5,8.3-7.9,14.5-10.3,6.2-2.4,13.6-3.7,22.2-3.7s12.9.7,19.4,2.1c6.5,1.4,11.9,3.4,16.2,6.1l-8.5,16.9c-4.5-2.7-9.1-4.6-13.6-5.6-4.6-1-9.1-1.5-13.6-1.5-6.8,0-11.8,1-15,3-3.3,2-4.9,4.6-4.9,7.7s1.1,5,3.2,6.4c2.1,1.4,4.9,2.6,8.4,3.4,3.4.8,7.3,1.5,11.5,2,4.2.5,8.4,1.3,12.6,2.4,4.2,1.1,8,2.5,11.5,4.4,3.5,1.8,6.3,4.4,8.5,7.7,2.1,3.3,3.2,7.7,3.2,13s-1.8,11.1-5.3,15.5c-3.5,4.4-8.5,7.8-14.9,10.2-6.4,2.4-14.1,3.7-23,3.7Z"
/>
<path
d="M1291.1,166.6c-10.6,0-19.8-2.1-27.7-6.3-7.9-4.2-14-10-18.3-17.4-4.3-7.4-6.5-15.7-6.5-25.1s2.1-17.9,6.3-25.2c4.2-7.3,10-13,17.5-17.2,7.4-4.2,15.9-6.2,25.4-6.2s17.5,2,24.8,6.1c7.2,4,12.9,9.7,17.1,17.1,4.2,7.4,6.2,16,6.2,26s0,2,0,3.2c0,1.2-.2,2.3-.3,3.4h-79.3v-14.8h67.5l-8.7,4.6c.1-5.5-1-10.3-3.4-14.4-2.4-4.2-5.6-7.4-9.7-9.8-4.1-2.4-8.8-3.6-14.2-3.6s-10.2,1.2-14.3,3.6c-4.1,2.4-7.3,5.7-9.6,9.9-2.3,4.2-3.5,9.2-3.5,14.9v3.6c0,5.7,1.3,10.7,3.9,15.1,2.6,4.4,6.3,7.8,11,10.2,4.7,2.4,10.2,3.6,16.4,3.6s10.2-.8,14.4-2.5c4.3-1.7,8.1-4.3,11.4-7.8l11.9,13.7c-4.3,5-9.6,8.8-16.1,11.5-6.5,2.7-13.9,4-22.2,4Z"
/>
<path
d="M1357.2,165.3v-95.1h21.2v26.2l-2.5-7.7c2.8-6.4,7.3-11.3,13.4-14.6,6.1-3.3,13.7-5,22.9-5v21.2c-1-.2-1.8-.4-2.7-.4-.8,0-1.7,0-2.5,0-8.4,0-15.1,2.5-20.1,7.4-5,4.9-7.5,12.3-7.5,22v46.1h-22.3Z"
/>
<path d="M1460,165.3l-40.8-95.1h23.2l35.1,83.9h-11.4l36.3-83.9h21.4l-40.8,95.1h-23Z" />
<path
d="M1579.6,166.6c-10.6,0-19.8-2.1-27.7-6.3-7.9-4.2-14-10-18.3-17.4-4.3-7.4-6.5-15.7-6.5-25.1s2.1-17.9,6.3-25.2c4.2-7.3,10-13,17.5-17.2,7.4-4.2,15.9-6.2,25.4-6.2s17.5,2,24.8,6.1c7.2,4,12.9,9.7,17.1,17.1,4.2,7.4,6.2,16,6.2,26s0,2,0,3.2c0,1.2-.2,2.3-.3,3.4h-79.3v-14.8h67.5l-8.7,4.6c.1-5.5-1-10.3-3.4-14.4-2.4-4.2-5.6-7.4-9.7-9.8-4.1-2.4-8.8-3.6-14.2-3.6s-10.2,1.2-14.3,3.6c-4.1,2.4-7.3,5.7-9.6,9.9-2.3,4.2-3.5,9.2-3.5,14.9v3.6c0,5.7,1.3,10.7,3.9,15.1,2.6,4.4,6.3,7.8,11,10.2,4.7,2.4,10.2,3.6,16.4,3.6s10.2-.8,14.4-2.5c4.3-1.7,8.1-4.3,11.4-7.8l11.9,13.7c-4.3,5-9.6,8.8-16.1,11.5-6.5,2.7-13.9,4-22.2,4Z"
/>
<path
d="M1645.7,165.3v-95.1h21.2v26.2l-2.5-7.7c2.8-6.4,7.3-11.3,13.4-14.6,6.1-3.3,13.7-5,22.9-5v21.2c-1-.2-1.8-.4-2.7-.4-.8,0-1.7,0-2.5,0-8.4,0-15.1,2.5-20.1,7.4-5,4.9-7.5,12.3-7.5,22v46.1h-22.3Z"
/>
<path
d="M1749.9,166.6c-8,0-15.6-1-22.9-3.1s-13.1-4.6-17.4-7.6l8.5-16.9c4.3,2.7,9.4,5,15.3,6.8,5.9,1.8,11.9,2.7,17.8,2.7s12.1-.9,15.2-2.8c3.1-1.9,4.7-4.5,4.7-7.7s-1.1-4.6-3.2-6c-2.1-1.4-4.9-2.4-8.4-3.1-3.4-.7-7.3-1.4-11.5-2-4.2-.6-8.4-1.4-12.6-2.4-4.2-1-8-2.5-11.5-4.5-3.4-2-6.2-4.6-8.4-7.9-2.1-3.3-3.2-7.7-3.2-13.2s1.7-11.3,5.2-15.8c3.4-4.5,8.3-7.9,14.5-10.3,6.2-2.4,13.6-3.7,22.2-3.7s12.9.7,19.4,2.1c6.5,1.4,11.9,3.4,16.2,6.1l-8.5,16.9c-4.5-2.7-9.1-4.6-13.6-5.6-4.6-1-9.1-1.5-13.6-1.5-6.8,0-11.8,1-15,3-3.3,2-4.9,4.6-4.9,7.7s1.1,5,3.2,6.4c2.1,1.4,4.9,2.6,8.4,3.4,3.4.8,7.3,1.5,11.5,2,4.2.5,8.4,1.3,12.6,2.4,4.2,1.1,8,2.5,11.5,4.4,3.5,1.8,6.3,4.4,8.5,7.7,2.1,3.3,3.2,7.7,3.2,13s-1.8,11.1-5.3,15.5c-3.5,4.4-8.5,7.8-14.9,10.2-6.4,2.4-14.1,3.7-23,3.7Z"
/>
<g>
<path
d="M9.8,143l63.4-38.1-5.8-15.3,18.1-18.6,22.9-4.9,6.6,8.2-10.6,10.7-9.2,2.9-6.6,6.8,3.2,9,6.5,6.9,9.2-2.5,6.6-7.2,14.3-4.5,4.3,9.6-14.8,18.1-24.8,7.8-11.1-12.4-63.6,38.2c-3-3.9-6.5-9.4-8.8-14.7ZM192.8,65.4l-50.4,13.6c2.8,7.4,3.7,11.7,4.5,16.5l50.3-13.6c-.8-5.4-2.2-10.8-4.4-16.5Z"
fill-rule="evenodd"
/>
<path
d="M17.3,106.5c3.6,42.1,38.9,75.2,82,75.2s60.7-18.9,74-46.3l16.4,5.7c-15.8,34.1-50.3,57.9-90.4,57.9S3.6,158.2,0,106.5h17.3ZM.3,89.4C5.3,39.2,47.8,0,99.3,0s99.5,44.6,99.5,99.5-1.1,17.4-3.3,25.5l-16.3-5.7c1.6-6.5,2.4-13.1,2.4-19.8,0-45.4-36.9-82.3-82.3-82.3S22.6,48.7,17.6,89.4H.3Z"
fill-rule="evenodd"
/>
<path
d="M99,51.6c-26.4,0-47.9,21.5-47.9,48s21.5,48,48,48,2.7,0,4-.2l4.8,16.8c-2.9.4-5.8.6-8.8.6-36,0-65.2-29.2-65.2-65.2S63.1,34.4,99,34.4s1.8,0,2.7,0l-2.7,17.1ZM118.6,37.4c26.4,8.3,45.6,33,45.6,62.2s-16.4,50.2-39.8,60l-4.8-16.7c16.2-7.7,27.4-24.2,27.4-43.3s-13-38.1-31.1-44.9l2.7-17.2Z"
fill-rule="evenodd"
/>
</g>
</g>
<g id="black" fill="currentColor">
<path
d="M354.8,69.2c12,0,21.7,3.4,28.6,10.4,7,7.2,10.6,17.5,10.6,31.5v54.8h-22.4v-51.9c0-8.4-1.8-14.7-5.5-19-3.8-4.1-8.9-6.3-15.9-6.3s-13.6,2.5-18.1,7.3c-4.5,5-6.8,12.2-6.8,21.3v48.5h-22.4v-51.9c0-8.4-1.8-14.7-5.5-19-3.8-4.1-8.9-6.3-15.9-6.3s-13.6,2.5-18.1,7.3c-4.5,4.8-6.8,12-6.8,21.3v48.5h-22.4v-95.6h21.3v12.2c3.6-4.3,8.1-7.5,13.4-9.8,5.4-2.3,11.3-3.4,17.9-3.4s13.6,1.3,19.2,3.9c5.5,2.9,9.8,6.8,13.1,12,3.9-5,8.9-8.9,15.2-11.8,6.3-2.7,13.1-4.1,20.6-4.1ZM466,167.2c-9.7,0-18.4-2.1-26.1-6.3-7.6-4-13.8-10.1-18.1-17.5-4.5-7.3-6.6-15.7-6.6-25.2s2.1-17.9,6.6-25.2c4.3-7.4,10.6-13.4,18.1-17.4,7.7-4.1,16.5-6.3,26.1-6.3s18.6,2.1,26.3,6.3c7.7,4.1,13.8,10,18.3,17.4,4.3,7.3,6.4,15.7,6.4,25.2s-2.1,17.9-6.4,25.2c-4.5,7.5-10.6,13.4-18.3,17.5-7.7,4.1-16.5,6.3-26.3,6.3h0ZM466,148c8.2,0,15-2.7,20.4-8.2,5.4-5.5,8.1-12.7,8.1-21.7s-2.7-16.1-8.1-21.7c-5.4-5.5-12.2-8.2-20.4-8.2s-15,2.7-20.2,8.2c-5.4,5.5-8.1,12.7-8.1,21.7s2.7,16.1,8.1,21.7c5.2,5.5,12,8.2,20.2,8.2ZM631.5,33.1v132.8h-21.5v-12.3c-3.7,4.4-8.3,7.9-13.6,10.2-5.5,2.3-11.5,3.4-18.1,3.4s-17.4-2-24.7-6.1c-7.3-4.1-13.2-9.8-17.4-17.4-4.1-7.3-6.3-15.9-6.3-25.6s2.1-18.3,6.3-25.6c4.1-7.3,10-13.1,17.4-17.2,7.3-4.1,15.6-6.1,24.7-6.1s12.2,1.1,17.4,3.2c5.2,2.1,9.8,5.4,13.4,9.7v-49h22.4ZM581.1,148c5.4,0,10.2-1.3,14.5-3.8,4.3-2.3,7.7-5.9,10.2-10.4,2.5-4.5,3.8-9.8,3.8-15.7s-1.3-11.3-3.8-15.7c-2.5-4.5-5.9-8.1-10.2-10.6-4.3-2.3-9.1-3.6-14.5-3.6s-10.2,1.3-14.5,3.6c-4.3,2.5-7.7,6.1-10.2,10.6-2.5,4.5-3.8,9.8-3.8,15.7s1.3,11.3,3.8,15.7c2.5,4.5,5.9,8.1,10.2,10.4,4.3,2.5,9.1,3.8,14.5,3.8ZM681.6,84.3c6.4-10,17.7-15,34-15v21.3c-1.7-.3-3.4-.5-5.2-.5-8.8,0-15.6,2.5-20.4,7.5-4.8,5.2-7.3,12.5-7.3,22v46.4h-22.4v-95.6h21.3v14h0ZM734.1,70.3h22.4v95.6h-22.4v-95.6ZM745.4,54.6c-4.1,0-7.5-1.3-10.2-3.9-2.7-2.4-4.2-5.9-4.1-9.5,0-3.8,1.4-7,4.1-9.7,2.7-2.5,6.1-3.8,10.2-3.8s7.5,1.3,10.2,3.6c2.7,2.5,4.1,5.5,4.1,9.3s-1.3,7.2-3.9,9.8c-2.7,2.7-6.3,4.1-10.4,4.1ZM839.5,69.2c12,0,21.7,3.6,29,10.6,7.3,7,10.9,17.5,10.9,31.3v54.8h-22.4v-51.9c0-8.4-2-14.7-5.9-19-3.9-4.1-9.5-6.3-16.8-6.3s-14.7,2.5-19.5,7.3c-4.8,5-7.2,12.2-7.2,21.5v48.3h-22.4v-95.6h21.3v12.3c3.8-4.5,8.4-7.7,14-10,5.5-2.3,12-3.4,19-3.4ZM964.8,160.7c-2.8,2.2-6,3.9-9.5,4.8-3.9,1.1-7.9,1.6-12,1.6-10.6,0-18.6-2.7-24.3-8.2-5.7-5.5-8.6-13.4-8.6-24v-46h-15.7v-17.9h15.7v-21.8h22.4v21.8h25.6v17.9h-25.6v45.5c0,4.7,1.1,8.2,3.4,10.6,2.3,2.5,5.5,3.8,9.8,3.8s9.1-1.3,12.5-3.9l6.3,15.9ZM1036.9,69.2c12,0,21.7,3.6,29,10.6,7.3,7,10.9,17.5,10.9,31.3v54.8h-22.4v-51.9c0-8.4-2-14.7-5.9-19-3.9-4.1-9.5-6.3-16.8-6.3s-14.7,2.5-19.5,7.3c-4.8,5-7.2,12.2-7.2,21.5v48.3h-22.4V33.1h22.4v48.3c3.8-3.9,8.2-7,13.8-9.1,5.4-2,11.5-3,18.1-3Z"
/>
</g>
</g>
</g>
</svg>
</template>

View File

@@ -1,4 +1,5 @@
<template>
<!-- eslint-disable vue/no-undef-components -->
<div ref="skinPreviewContainer" class="relative w-full h-full cursor-grab" @click="onCanvasClick">
<div
class="absolute bottom-[18%] left-0 right-0 flex flex-col justify-center items-center mb-2 pointer-events-none z-10 gap-2"
@@ -217,23 +218,23 @@ const radialSpotlightShader = computed(() => ({
uniform float outerOpacity;
uniform float falloffPower;
uniform float shadowRadius;
varying vec2 vUv;
void main() {
vec2 center = vec2(0.5, 0.5);
float dist = distance(vUv, center) * 2.0;
// Create shadow in the center
float shadowFalloff = 1.0 - smoothstep(0.0, shadowRadius, dist);
// Create overall spotlight falloff
float spotlightFalloff = 1.0 - smoothstep(0.0, 1.0, pow(dist, falloffPower));
// Combine both effects
vec3 color = mix(outerColor, innerColor, shadowFalloff);
float opacity = mix(outerOpacity, innerOpacity * shadowFalloff, spotlightFalloff);
gl_FragColor = vec4(color, opacity);
}
`,