feat: new user badges, ui consistency pass (#6262)
* feat: new user badges, ui consistency pass * prepr * fix: align with backend * fix: lint --------- Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
@@ -1,5 +1,5 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { type AuthProvider, provideAuth } from '@modrinth/ui'
|
||||
import { type AuthProvider, type AuthUser, provideAuth } from '@modrinth/ui'
|
||||
import { computed, type Ref, ref, watchEffect } from 'vue'
|
||||
|
||||
type AppCredentials = {
|
||||
@@ -12,7 +12,7 @@ export function setupAuthProvider(
|
||||
requestSignIn: (redirectPath: string) => void | Promise<void>,
|
||||
) {
|
||||
const sessionToken = ref<string | null>(null)
|
||||
const user = ref<Labrinth.Users.v2.User | null>(null)
|
||||
const user = ref<AuthUser | null>(null)
|
||||
const isReady = computed(() => credentials.value !== undefined)
|
||||
|
||||
const authProvider: AuthProvider = {
|
||||
|
||||
@@ -58,6 +58,9 @@ export default defineConfig({
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false,
|
||||
cleanupIds: {
|
||||
minify: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -104,6 +104,9 @@ export default defineNuxtConfig({
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false,
|
||||
cleanupIds: {
|
||||
minify: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
Cards and body styling
|
||||
*/
|
||||
// CARDS
|
||||
.base-card {
|
||||
padding: var(--spacing-card-lg);
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
min-height: var(--font-size-2xl);
|
||||
|
||||
background-color: var(--color-raised-bg);
|
||||
border-radius: var(--size-rounded-card);
|
||||
background-color: var(--surface-3);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--surface-4);
|
||||
|
||||
margin-bottom: var(--spacing-card-md);
|
||||
margin-bottom: var(--gap-md);
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: -2px;
|
||||
|
||||
box-shadow: var(--shadow-card);
|
||||
|
||||
.card__overlay {
|
||||
position: absolute;
|
||||
@@ -25,6 +25,17 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:where(&.warning, &.information) {
|
||||
padding: 1.5rem;
|
||||
line-height: 1.5;
|
||||
min-height: 0;
|
||||
|
||||
a {
|
||||
color: var(--color-blue);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&.moderation-card {
|
||||
background-color: var(--color-warning-banner-bg);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<AutoLink
|
||||
:to="currentAd.link"
|
||||
:aria-label="currentAd.description"
|
||||
class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 rounded-[inherit] bg-bg-raised"
|
||||
class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 rounded-[inherit] border border-solid border-surface-4 bg-surface-3"
|
||||
>
|
||||
<img
|
||||
:src="currentAd.light"
|
||||
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
</AutoLink>
|
||||
<div
|
||||
class="absolute top-0 flex items-center justify-center overflow-hidden rounded-2xl bg-bg-raised"
|
||||
class="absolute top-0 flex items-center justify-center overflow-hidden rounded-2xl border border-solid border-surface-4 bg-surface-3"
|
||||
>
|
||||
<div id="modrinth-rail-1" />
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<nav :aria-label="ariaLabel" class="w-full">
|
||||
<ul
|
||||
class="card-shadow m-0 flex list-none flex-col items-start gap-1.5 rounded-2xl bg-bg-raised p-4"
|
||||
class="card-shadow m-0 flex list-none flex-col items-start gap-1.5 rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4"
|
||||
:class="{ 'pt-3': filteredItems?.[0]?.type === 'heading' }"
|
||||
>
|
||||
<slot v-if="hasSlotContent" />
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ export const initAuth = async (oldToken = null) => {
|
||||
auth.user = await useBaseFetch(
|
||||
'user',
|
||||
{
|
||||
apiVersion: 3,
|
||||
headers: {
|
||||
Authorization: auth.token,
|
||||
},
|
||||
@@ -105,6 +106,7 @@ export const initAuth = async (oldToken = null) => {
|
||||
auth.user = await useBaseFetch(
|
||||
'user',
|
||||
{
|
||||
apiVersion: 3,
|
||||
headers: {
|
||||
Authorization: auth.token,
|
||||
},
|
||||
|
||||
@@ -793,6 +793,7 @@ import ModrinthFooter from '~/components/ui/ModrinthFooter.vue'
|
||||
import { getSignInRouteObj } from '~/composables/auth.js'
|
||||
import { errors as generatedStateErrors } from '~/generated/state.json'
|
||||
import { getProjectTypeMessage } from '~/utils/i18n-project-type.ts'
|
||||
import { hasActiveMidas } from '~/utils/user-membership.ts'
|
||||
|
||||
const generatedState = useGeneratedState()
|
||||
|
||||
@@ -1096,7 +1097,7 @@ const userMenuOptions = computed(() => {
|
||||
id: 'plus',
|
||||
link: '/plus',
|
||||
color: 'purple',
|
||||
shown: !flags.value.hidePlusPromoInUserMenu && !isPermission(user.badges, 1 << 0),
|
||||
shown: !flags.value.hidePlusPromoInUserMenu && !hasActiveMidas(user),
|
||||
},
|
||||
{
|
||||
id: 'servers',
|
||||
|
||||
@@ -2,19 +2,21 @@ import { useAppQueryClient } from '~/composables/query-client'
|
||||
import { useServerModrinthClient } from '~/server/utils/api-client'
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
if (!to.path.startsWith('/user/') || !to.params.id) {
|
||||
const userParam = to.params.user ?? to.params.id
|
||||
const userId = Array.isArray(userParam) ? userParam[0] : userParam
|
||||
|
||||
if (!to.path.startsWith('/user/') || !userId) {
|
||||
return
|
||||
}
|
||||
|
||||
const queryClient = useAppQueryClient()
|
||||
const authToken = useCookie('auth-token')
|
||||
const client = useServerModrinthClient({ authToken: authToken.value || undefined })
|
||||
const userId = to.params.id as string
|
||||
|
||||
try {
|
||||
const user = await queryClient.fetchQuery({
|
||||
queryKey: ['user', userId],
|
||||
queryFn: () => client.labrinth.users_v2.get(userId),
|
||||
queryFn: () => client.labrinth.users_v3.get(userId),
|
||||
})
|
||||
|
||||
if (!user) return
|
||||
|
||||
@@ -38,11 +38,7 @@
|
||||
{{ calculateSavings(price.prices.intervals.monthly, price.prices.intervals.yearly) }}% with
|
||||
annual billing!
|
||||
</p>
|
||||
<ButtonStyled
|
||||
v-if="auth.user && isPermission(auth.user.badges, 1 << 0)"
|
||||
color="purple"
|
||||
size="large"
|
||||
>
|
||||
<ButtonStyled v-if="auth.user && hasActiveMidas(auth.user)" color="purple" size="large">
|
||||
<nuxt-link to="/settings/billing">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
Manage subscription
|
||||
@@ -95,8 +91,8 @@ import {
|
||||
import { calculateSavings, getCurrency } from '@modrinth/utils'
|
||||
|
||||
import { useBaseFetch } from '@/composables/fetch.js'
|
||||
import { isPermission } from '@/utils/permissions.ts'
|
||||
import { products } from '~/generated/state.json'
|
||||
import { hasActiveMidas } from '~/utils/user-membership.ts'
|
||||
|
||||
const { addNotification } = injectNotificationManager()
|
||||
const formatPrice = useFormatPrice()
|
||||
|
||||
@@ -460,8 +460,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="normal-page__sidebar">
|
||||
<div v-if="organizations?.length > 0" class="card flex-card">
|
||||
<h2 class="text-lg text-contrast">
|
||||
<div
|
||||
v-if="organizations?.length > 0"
|
||||
class="mb-4 rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4 pt-3"
|
||||
>
|
||||
<h2 class="m-0 mb-2 text-lg text-contrast">
|
||||
{{ formatMessage(messages.profileOrganizations) }}
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@@ -476,24 +479,16 @@
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="badges.length > 0" class="card flex-card">
|
||||
<h2 class="text-lg text-contrast">
|
||||
{{ formatMessage(messages.profileBadges) }}
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div v-for="badge in badges" :key="badge">
|
||||
<StaffBadge v-if="badge === 'staff'" class="h-14 w-14" />
|
||||
<ModBadge v-else-if="badge === 'mod'" class="h-14 w-14" />
|
||||
<nuxt-link v-else-if="badge === 'plus'" to="/plus">
|
||||
<PlusBadge class="h-14 w-14" />
|
||||
</nuxt-link>
|
||||
<TenMClubBadge v-else-if="badge === '10m-club'" class="h-14 w-14" />
|
||||
<EarlyAdopterBadge v-else-if="badge === 'early-adopter'" class="h-14 w-14" />
|
||||
<AlphaTesterBadge v-else-if="badge === 'alpha-tester'" class="h-14 w-14" />
|
||||
<BetaTesterBadge v-else-if="badge === 'beta-tester'" class="h-14 w-14" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<UserBadges
|
||||
:downloads="sumDownloads"
|
||||
:join-date="joinDate"
|
||||
:role="user.role"
|
||||
:badges="user.badges"
|
||||
:has-midas="hasActiveMidas(user)"
|
||||
:has-pride="hasPride26Badge(user)"
|
||||
:earliest-project-by-type="earliestProjectByType"
|
||||
class="mb-4 rounded-2xl border border-solid border-surface-4 bg-surface-3 p-4 pt-3"
|
||||
/>
|
||||
<AdPlaceholder v-if="!auth.user" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -540,6 +535,7 @@ import {
|
||||
useCompactNumber,
|
||||
useFormatDateTime,
|
||||
useFormatNumber,
|
||||
UserBadges,
|
||||
useRelativeTime,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
@@ -547,19 +543,13 @@ import { isAdmin, isStaff, UserBadge } from '@modrinth/utils'
|
||||
import { useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { onServerPrefetch } from 'vue'
|
||||
|
||||
import TenMClubBadge from '~/assets/images/badges/10m-club.svg?component'
|
||||
import AlphaTesterBadge from '~/assets/images/badges/alpha-tester.svg?component'
|
||||
import BetaTesterBadge from '~/assets/images/badges/beta-tester.svg?component'
|
||||
import EarlyAdopterBadge from '~/assets/images/badges/early-adopter.svg?component'
|
||||
import ModBadge from '~/assets/images/badges/mod.svg?component'
|
||||
import PlusBadge from '~/assets/images/badges/plus.svg?component'
|
||||
import StaffBadge from '~/assets/images/badges/staff.svg?component'
|
||||
import UpToDate from '~/assets/images/illustrations/up_to_date.svg?component'
|
||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
||||
import ModalCreation from '~/components/ui/create/ProjectCreateModal.vue'
|
||||
import { getSignInRouteObj } from '~/composables/auth.js'
|
||||
import { reportUser } from '~/utils/report-helpers.ts'
|
||||
import { hasActiveMidas, hasPride26Badge } from '~/utils/user-membership.ts'
|
||||
|
||||
const data = useNuxtApp()
|
||||
const route = useNativeRoute()
|
||||
@@ -743,7 +733,7 @@ const {
|
||||
suspense: userSuspense,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => ['user', userId]),
|
||||
queryFn: () => client.labrinth.users_v2.get(userId),
|
||||
queryFn: () => client.labrinth.users_v3.get(userId),
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -859,57 +849,21 @@ const sumDownloads = computed(() => {
|
||||
})
|
||||
|
||||
const joinDate = computed(() => new Date(user.value.created))
|
||||
const MODRINTH_BETA_END_DATE = new Date('2022-02-27T08:00:00.000Z')
|
||||
const MODRINTH_ALPHA_END_DATE = new Date('2020-11-30T08:00:00.000Z')
|
||||
|
||||
const badges = computed(() => {
|
||||
const badges = []
|
||||
|
||||
if (user.value.role === 'admin') {
|
||||
badges.push('staff')
|
||||
}
|
||||
|
||||
if (user.value.role === 'moderator') {
|
||||
badges.push('mod')
|
||||
}
|
||||
|
||||
if (isPermission(user.value.badges, 1 << 0)) {
|
||||
badges.push('plus')
|
||||
}
|
||||
|
||||
if (sumDownloads.value > 10000000) {
|
||||
badges.push('10m-club')
|
||||
}
|
||||
|
||||
if (
|
||||
isPermission(user.value.badges, 1 << 1) ||
|
||||
isPermission(user.value.badges, 1 << 2) ||
|
||||
isPermission(user.value.badges, 1 << 3)
|
||||
) {
|
||||
badges.push('early-adopter')
|
||||
}
|
||||
|
||||
if (isPermission(user.value.badges, 1 << 4) || joinDate.value < MODRINTH_ALPHA_END_DATE) {
|
||||
badges.push('alpha-tester')
|
||||
} else if (isPermission(user.value.badges, 1 << 4) || joinDate.value < MODRINTH_BETA_END_DATE) {
|
||||
badges.push('beta-tester')
|
||||
}
|
||||
|
||||
if (isPermission(user.value.badges, 1 << 5)) {
|
||||
badges.push('contributor')
|
||||
}
|
||||
|
||||
if (isPermission(user.value.badges, 1 << 6)) {
|
||||
badges.push('translator')
|
||||
}
|
||||
|
||||
return badges
|
||||
})
|
||||
|
||||
async function copyId() {
|
||||
await navigator.clipboard.writeText(user.value.id)
|
||||
}
|
||||
|
||||
const earliestProjectByType = computed(() => {
|
||||
const obj = {}
|
||||
|
||||
for (const project of projects.value ?? []) {
|
||||
obj[project.project_type] = new Date(project.published)
|
||||
}
|
||||
|
||||
return obj
|
||||
})
|
||||
|
||||
async function copyPermalink() {
|
||||
await navigator.clipboard.writeText(`${config.public.siteUrl}/user/${user.value.id}`)
|
||||
}
|
||||
@@ -1012,7 +966,6 @@ export default defineNuxtComponent({
|
||||
}
|
||||
|
||||
.description {
|
||||
// Grow to take up remaining space
|
||||
flex-grow: 1;
|
||||
|
||||
color: var(--color-text);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { type AuthProvider, provideAuth } from '@modrinth/ui'
|
||||
import { type AuthProvider, type AuthUser, provideAuth } from '@modrinth/ui'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
|
||||
import { getSignInRedirectPath } from '~/composables/auth.js'
|
||||
@@ -8,7 +8,7 @@ export function setupAuthProvider(auth: Awaited<ReturnType<typeof useAuth>>) {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const sessionToken = ref<string | null>(null)
|
||||
const user = ref<Labrinth.Users.v2.User | null>(null)
|
||||
const user = ref<AuthUser | null>(null)
|
||||
|
||||
const authProvider: AuthProvider = {
|
||||
session_token: sessionToken,
|
||||
@@ -26,7 +26,7 @@ export function setupAuthProvider(auth: Awaited<ReturnType<typeof useAuth>>) {
|
||||
|
||||
watchEffect(() => {
|
||||
sessionToken.value = auth.value.token || null
|
||||
user.value = (auth.value.user as Labrinth.Users.v2.User | null) ?? null
|
||||
user.value = (auth.value.user as Labrinth.Users.v3.User | null) ?? null
|
||||
})
|
||||
|
||||
provideAuth(authProvider)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { UserBadge } from '@modrinth/utils'
|
||||
|
||||
const PRIDE_26_MIDAS_DURATION_MS = 30 * 24 * 60 * 60 * 1000
|
||||
|
||||
type Pride26Campaign = {
|
||||
last_donated_at?: string | null
|
||||
has_badge?: boolean | null
|
||||
has_midas?: boolean | null
|
||||
}
|
||||
|
||||
type UserWithMembership = {
|
||||
badges?: number | null
|
||||
campaigns?: {
|
||||
pride_26?: Pride26Campaign | null
|
||||
} | null
|
||||
}
|
||||
|
||||
export function hasPride26Badge(user?: UserWithMembership | null) {
|
||||
return user?.campaigns?.pride_26?.has_badge === true
|
||||
}
|
||||
|
||||
export function hasActivePride26Midas(user?: UserWithMembership | null, now = Date.now()) {
|
||||
const pride26Campaign = user?.campaigns?.pride_26
|
||||
|
||||
if (!pride26Campaign?.has_midas || !pride26Campaign.last_donated_at) {
|
||||
return false
|
||||
}
|
||||
|
||||
const lastDonatedAt = Date.parse(pride26Campaign.last_donated_at)
|
||||
|
||||
if (!Number.isFinite(lastDonatedAt)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return lastDonatedAt + PRIDE_26_MIDAS_DURATION_MS > now
|
||||
}
|
||||
|
||||
export function hasActiveMidas(user?: UserWithMembership | null, now = Date.now()) {
|
||||
return Boolean((user?.badges ?? 0) & UserBadge.MIDAS) || hasActivePride26Midas(user, now)
|
||||
}
|
||||
@@ -1298,6 +1298,16 @@ export namespace Labrinth {
|
||||
export type AuthProvider = Common.AuthProvider
|
||||
export type UserPayoutData = Common.UserPayoutData
|
||||
|
||||
export type Pride26CampaignDonation = {
|
||||
last_donated_at: string
|
||||
has_badge: boolean
|
||||
has_midas: boolean
|
||||
}
|
||||
|
||||
export type UserCampaigns = {
|
||||
pride_26: Pride26CampaignDonation | null
|
||||
}
|
||||
|
||||
export type User = {
|
||||
id: string
|
||||
username: string
|
||||
@@ -1314,6 +1324,7 @@ export namespace Labrinth {
|
||||
payout_data?: UserPayoutData
|
||||
stripe_customer_id?: string
|
||||
allow_friend_requests?: boolean
|
||||
campaigns: UserCampaigns
|
||||
github_id?: number
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,25 @@ export class LabrinthUsersV3Module extends AbstractModule {
|
||||
return 'labrinth_users_v3'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user by ID or username
|
||||
*
|
||||
* @param idOrUsername - The user's ID or username
|
||||
* @returns Promise resolving to the user data
|
||||
*
|
||||
* GET /v3/user/{id}
|
||||
*/
|
||||
public async get(idOrUsername: string): Promise<Labrinth.Users.v3.User> {
|
||||
return this.client.request<Labrinth.Users.v3.User>(
|
||||
`/user/${encodeURIComponent(idOrUsername)}`,
|
||||
{
|
||||
api: 'labrinth',
|
||||
version: 3,
|
||||
method: 'GET',
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all projects the authenticated user can access directly or through
|
||||
* their organizations.
|
||||
|
||||
@@ -121,6 +121,30 @@ function generateIconExports(): {
|
||||
})
|
||||
}
|
||||
|
||||
// Process badge icons from icons/badges/
|
||||
const badgesDir = path.join(iconsDir, 'badges')
|
||||
if (fs.existsSync(badgesDir)) {
|
||||
const badgeFiles = fs.readdirSync(badgesDir).filter((file) => file.endsWith('.svg'))
|
||||
badgeFiles.forEach((file) => {
|
||||
const baseName = path.basename(file, '.svg')
|
||||
let pascalName = toPascalCase(baseName)
|
||||
|
||||
if (pascalName === '') {
|
||||
pascalName = 'Unknown'
|
||||
}
|
||||
|
||||
if (!pascalName.endsWith('Badge')) {
|
||||
pascalName += 'Badge'
|
||||
}
|
||||
|
||||
icons.push({
|
||||
importPath: `./icons/badges/${file}?component`,
|
||||
pascalName,
|
||||
privateName: `_${pascalName}`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Sort by import path using simple-import-sort's algorithm
|
||||
icons.sort((a, b) => compareImportSources(a.importPath, b.importPath))
|
||||
|
||||
@@ -156,7 +180,7 @@ function generateIconExports(): {
|
||||
function runTests(): void {
|
||||
console.log('🧪 Running conversion tests...\n')
|
||||
|
||||
const testCases: Array<{ input: string; expected: string }> = [
|
||||
const testCases: Array<{ input: string; expected: string; suffix?: string }> = [
|
||||
{ input: 'align-left', expected: 'AlignLeftIcon' },
|
||||
{ input: 'arrow-big-up-dash', expected: 'ArrowBigUpDashIcon' },
|
||||
{ input: 'check-check', expected: 'CheckCheckIcon' },
|
||||
@@ -171,13 +195,17 @@ function runTests(): void {
|
||||
{ input: 'list_bulleted', expected: 'ListBulletedIcon' },
|
||||
{ input: 'test.name', expected: 'TestNameIcon' },
|
||||
{ input: 'test-name_final.icon', expected: 'TestNameFinalIcon' },
|
||||
{ input: 'downloads-500m', expected: 'Downloads500mBadge', suffix: 'Badge' },
|
||||
{ input: 'early-modpack', expected: 'EarlyModpackBadge', suffix: 'Badge' },
|
||||
{ input: 'plus', expected: 'PlusBadge', suffix: 'Badge' },
|
||||
]
|
||||
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
|
||||
testCases.forEach(({ input, expected }) => {
|
||||
const result = toPascalCase(input) + (toPascalCase(input).endsWith('Icon') ? '' : 'Icon')
|
||||
testCases.forEach(({ input, expected, suffix = 'Icon' }) => {
|
||||
const base = toPascalCase(input)
|
||||
const result = base.endsWith(suffix) ? base : base + suffix
|
||||
const success = result === expected
|
||||
|
||||
if (success) {
|
||||
@@ -312,6 +340,26 @@ function getExpectedIconExports(iconsDir: string): string[] {
|
||||
})
|
||||
}
|
||||
|
||||
// Process badge icons from icons/badges/
|
||||
const badgesDir = path.join(iconsDir, 'badges')
|
||||
if (fs.existsSync(badgesDir)) {
|
||||
const badgeFiles = fs.readdirSync(badgesDir).filter((file) => file.endsWith('.svg'))
|
||||
badgeFiles.forEach((file) => {
|
||||
const baseName = path.basename(file, '.svg')
|
||||
let pascalName = toPascalCase(baseName)
|
||||
|
||||
if (pascalName === '') {
|
||||
pascalName = 'Unknown'
|
||||
}
|
||||
|
||||
if (!pascalName.endsWith('Badge')) {
|
||||
pascalName += 'Badge'
|
||||
}
|
||||
|
||||
exports.push(pascalName)
|
||||
})
|
||||
}
|
||||
|
||||
return exports.sort()
|
||||
}
|
||||
|
||||
@@ -321,14 +369,15 @@ function getActualIconExports(indexFile: string): string[] {
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(indexFile, 'utf8')
|
||||
const exportMatches = content.match(/export const (\w+Icon) = _\w+Icon/g) || []
|
||||
const exportMatches =
|
||||
content.match(/export const (\w+(?:Icon|Badge)) = _\w+(?:Icon|Badge)/g) || []
|
||||
|
||||
return exportMatches
|
||||
.map((match) => {
|
||||
const result = match.match(/export const (\w+Icon)/)
|
||||
const result = match.match(/export const (\w+(?:Icon|Badge))/)
|
||||
return result ? result[1] : ''
|
||||
})
|
||||
.filter((name) => name.endsWith('Icon'))
|
||||
.filter((name) => name.endsWith('Icon') || name.endsWith('Badge'))
|
||||
.sort()
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,26 @@ import _ArrowUpZAIcon from './icons/arrow-up-z-a.svg?component'
|
||||
import _AsteriskIcon from './icons/asterisk.svg?component'
|
||||
import _BadgeCheckIcon from './icons/badge-check.svg?component'
|
||||
import _BadgeDollarSignIcon from './icons/badge-dollar-sign.svg?component'
|
||||
import _AlphaBadge from './icons/badges/alpha.svg?component'
|
||||
import _BetaBadge from './icons/badges/beta.svg?component'
|
||||
import _Downloads1mBadge from './icons/badges/downloads-1m.svg?component'
|
||||
import _Downloads10mBadge from './icons/badges/downloads-10m.svg?component'
|
||||
import _Downloads25mBadge from './icons/badges/downloads-25m.svg?component'
|
||||
import _Downloads50mBadge from './icons/badges/downloads-50m.svg?component'
|
||||
import _Downloads100mBadge from './icons/badges/downloads-100m.svg?component'
|
||||
import _Downloads250mBadge from './icons/badges/downloads-250m.svg?component'
|
||||
import _Downloads500mBadge from './icons/badges/downloads-500m.svg?component'
|
||||
import _EarlyDatapackBadge from './icons/badges/early-datapack.svg?component'
|
||||
import _EarlyHostingBadge from './icons/badges/early-hosting.svg?component'
|
||||
import _EarlyModpackBadge from './icons/badges/early-modpack.svg?component'
|
||||
import _EarlyPluginBadge from './icons/badges/early-plugin.svg?component'
|
||||
import _EarlyResourcepackBadge from './icons/badges/early-resourcepack.svg?component'
|
||||
import _EarlyServersBadge from './icons/badges/early-servers.svg?component'
|
||||
import _EarlyShadersBadge from './icons/badges/early-shaders.svg?component'
|
||||
import _ModeratorBadge from './icons/badges/moderator.svg?component'
|
||||
import _PlusBadge from './icons/badges/plus.svg?component'
|
||||
import _PrideBadge from './icons/badges/pride.svg?component'
|
||||
import _StaffBadge from './icons/badges/staff.svg?component'
|
||||
import _BanIcon from './icons/ban.svg?component'
|
||||
import _BellIcon from './icons/bell.svg?component'
|
||||
import _BellRingIcon from './icons/bell-ring.svg?component'
|
||||
@@ -423,6 +443,26 @@ export const ArrowUpZAIcon = _ArrowUpZAIcon
|
||||
export const AsteriskIcon = _AsteriskIcon
|
||||
export const BadgeCheckIcon = _BadgeCheckIcon
|
||||
export const BadgeDollarSignIcon = _BadgeDollarSignIcon
|
||||
export const AlphaBadge = _AlphaBadge
|
||||
export const BetaBadge = _BetaBadge
|
||||
export const Downloads1mBadge = _Downloads1mBadge
|
||||
export const Downloads10mBadge = _Downloads10mBadge
|
||||
export const Downloads25mBadge = _Downloads25mBadge
|
||||
export const Downloads50mBadge = _Downloads50mBadge
|
||||
export const Downloads100mBadge = _Downloads100mBadge
|
||||
export const Downloads250mBadge = _Downloads250mBadge
|
||||
export const Downloads500mBadge = _Downloads500mBadge
|
||||
export const EarlyDatapackBadge = _EarlyDatapackBadge
|
||||
export const EarlyHostingBadge = _EarlyHostingBadge
|
||||
export const EarlyModpackBadge = _EarlyModpackBadge
|
||||
export const EarlyPluginBadge = _EarlyPluginBadge
|
||||
export const EarlyResourcepackBadge = _EarlyResourcepackBadge
|
||||
export const EarlyServersBadge = _EarlyServersBadge
|
||||
export const EarlyShadersBadge = _EarlyShadersBadge
|
||||
export const ModeratorBadge = _ModeratorBadge
|
||||
export const PlusBadge = _PlusBadge
|
||||
export const PrideBadge = _PrideBadge
|
||||
export const StaffBadge = _StaffBadge
|
||||
export const BanIcon = _BanIcon
|
||||
export const BellIcon = _BellIcon
|
||||
export const BellRingIcon = _BellRingIcon
|
||||
|
||||
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="none" viewBox="0 0 90 90"><path fill="#33363d" d="M11.041 30.917a16.666 16.666 0 0 1 19.917-19.875 16.666 16.666 0 0 1 28.083 0 16.666 16.666 0 0 1 19.917 19.917 16.666 16.666 0 0 1 0 28.083 16.666 16.666 0 0 1-19.875 19.917 16.666 16.666 0 0 1-28.125 0A16.666 16.666 0 0 1 11.04 59.084a16.667 16.667 0 0 1 0-28.167"/><path fill="url(#downloads-10m-a)" fill-opacity=".7" d="M11.041 30.917a16.666 16.666 0 0 1 19.917-19.875 16.666 16.666 0 0 1 28.083 0 16.666 16.666 0 0 1 19.917 19.917 16.666 16.666 0 0 1 0 28.083 16.666 16.666 0 0 1-19.875 19.917 16.666 16.666 0 0 1-28.125 0A16.666 16.666 0 0 1 11.04 59.084a16.667 16.667 0 0 1 0-28.167"/><path fill="url(#downloads-10m-b)" fill-opacity=".7" d="M83.647 45a13.67 13.67 0 0 0-6.305-11.514 3 3 0 0 1-1.312-3.185A13.667 13.667 0 0 0 59.7 13.969a3 3 0 0 1-3.186-1.31 13.67 13.67 0 0 0-23.029 0 3 3 0 0 1-3.183 1.31A13.668 13.668 0 0 0 13.97 30.268a3 3 0 0 1-1.325 3.185 13.67 13.67 0 0 0-4.664 18.14 13.67 13.67 0 0 0 4.665 4.957 3 3 0 0 1 1.324 3.184 13.67 13.67 0 0 0 9.54 16.09c2.21.64 4.546.712 6.792.208l.231-.043c1.16-.166 2.32.36 2.956 1.36a13.67 13.67 0 0 0 11.532 6.332v3l-.526-.01a16.7 16.7 0 0 1-7.032-1.802l-.464-.246a16.67 16.67 0 0 1-6.041-5.663 16.67 16.67 0 0 1-15.041-4.1l-.378-.366a16.7 16.7 0 0 1-4.071-6.625l-.155-.503a16.67 16.67 0 0 1-.272-8.281 16.7 16.7 0 0 1-5.426-5.588l-.261-.457a16.67 16.67 0 0 1-.247-15.612l.247-.465a16.67 16.67 0 0 1 5.687-6.045 16.7 16.7 0 0 1 .132-7.775l.14-.507a16.7 16.7 0 0 1 3.86-6.75l.366-.377a16.67 16.67 0 0 1 14.904-4.573l.515.107a16.7 16.7 0 0 1 5.581-5.382l.456-.259a16.667 16.667 0 0 1 21.757 5.204l.289.437a16.667 16.667 0 0 1 19.917 19.917A16.67 16.67 0 0 1 86.647 45l-.01.524a16.7 16.7 0 0 1-2.04 7.48l-.258.456a16.7 16.7 0 0 1-5.381 5.582 16.67 16.67 0 0 1-4.466 15.419l-.377.366a16.7 16.7 0 0 1-6.75 3.86l-.508.14a16.7 16.7 0 0 1-7.774.132 16.67 16.67 0 0 1-6.041 5.663l-.465.246a16.7 16.7 0 0 1-7.556 1.812v-3a13.67 13.67 0 0 0 11.53-6.331l.134-.194a3 3 0 0 1 3.047-1.125 13.67 13.67 0 0 0 16.09-9.54c.64-2.21.712-4.546.208-6.792a3 3 0 0 1 1.312-3.183A13.67 13.67 0 0 0 83.647 45"/><path fill="#000" fill-opacity=".5" fill-rule="evenodd" d="M46.709 6.46a13.66 13.66 0 0 0-8.274 1.573 13.67 13.67 0 0 0-4.95 4.625 3 3 0 0 1-3.183 1.312A13.668 13.668 0 0 0 13.97 30.267a3 3 0 0 1-.489 2.395c-.22.309-.502.579-.835.79a13.68 13.68 0 0 0-6.25 13.267 13.67 13.67 0 0 0 6.25 9.83 3 3 0 0 1 1.307 1.812c.11.44.12.91.017 1.372a13.6 13.6 0 0 0-.155 5.116 13.66 13.66 0 0 0 3.843 7.52 13.67 13.67 0 0 0 12.644 3.662l.231-.043c1.16-.166 2.32.36 2.956 1.36q.116.183.238.36a13.64 13.64 0 0 0 4.715 4.285 13.67 13.67 0 0 0 18.11-4.644q.064-.1.133-.194a3 3 0 0 1 3.047-1.125 13.67 13.67 0 0 0 16.63-13.35c-.001-1-.111-2-.332-2.982a3.01 3.01 0 0 1 1.312-3.183 13.668 13.668 0 0 0 0-23.029 3 3 0 0 1-.54-.441 2.998 2.998 0 0 1-.772-2.744 13.674 13.674 0 0 0-10.785-16.425 13.7 13.7 0 0 0-5.546.093 3 3 0 0 1-3.185-1.31 13.67 13.67 0 0 0-9.805-6.198m-36.1 43.685a10.667 10.667 0 0 1 3.641-14.158 6 6 0 0 0 2.648-6.37 10.666 10.666 0 0 1 12.747-12.72l.232.047a6 6 0 0 0 6.136-2.67 10.668 10.668 0 0 1 17.973 0l.131.197a6 6 0 0 0 6.24 2.424 10.67 10.67 0 0 1 12.746 12.748 6 6 0 0 0 2.623 6.37 10.667 10.667 0 0 1 3.766 13.813l-.157.297a10.7 10.7 0 0 1-3.61 3.864 6 6 0 0 0-2.622 6.368 10.667 10.667 0 0 1-12.721 12.747 6 6 0 0 0-6.095 2.25l-.068.096-.134.194-.065.096a10.67 10.67 0 0 1-8.664 4.937l-.336.005a10.67 10.67 0 0 1-8.815-4.66l-.184-.282a6 6 0 0 0-5.914-2.72l-.122.021-.231.043-.109.022a10.668 10.668 0 0 1-12.747-12.721 6 6 0 0 0-2.45-6.239l-.198-.13a10.7 10.7 0 0 1-3.64-3.87" clip-rule="evenodd"/><path fill="#fff" d="M30.826 51.364V63h-2.46v-9.301h-.068l-2.665 1.67v-2.181l2.88-1.824zm7.328 11.892q-1.465-.006-2.523-.722-1.05-.716-1.619-2.074-.562-1.358-.557-3.267 0-1.903.563-3.244.568-1.341 1.62-2.04 1.056-.705 2.516-.704 1.46 0 2.511.704 1.057.705 1.626 2.046.568 1.336.562 3.238 0 1.916-.568 3.273-.562 1.357-1.614 2.074-1.05.716-2.517.716m0-2.04q1 0 1.597-1.006.596-1.005.59-3.017 0-1.323-.272-2.204-.267-.881-.762-1.324a1.66 1.66 0 0 0-1.153-.443q-.994 0-1.59.994-.598.994-.603 2.977 0 1.34.267 2.239.273.891.767 1.34.495.444 1.16.444m6.521-9.852h3.035l3.204 7.818h.136l3.205-7.818h3.034V63h-2.386v-7.574h-.097l-3.011 7.517H50.17l-3.012-7.545h-.096V63h-2.387zm18.05 9.863v-8.045h2.034v8.045zm-3.006-3.005v-2.035h8.046v2.035z"/><path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M45 35V23m5 7-5 5-5-5m14 5v4a2 2 0 0 1-2 2H38a2 2 0 0 1-2-2v-4"/><defs><linearGradient id="downloads-10m-a" x1="7.5" x2="80" y1="-3" y2="91" gradientUnits="userSpaceOnUse"><stop stop-color="#42ffdf"/><stop offset="1" stop-color="#170fff"/></linearGradient><linearGradient id="downloads-10m-b" x1="7.5" x2="80" y1="-3" y2="91" gradientUnits="userSpaceOnUse"><stop stop-color="#5d46f2"/><stop offset="1" stop-color="#b2ebf8"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="none" viewBox="0 0 90 90"><path fill="#33363d" d="M11.041 30.917a16.666 16.666 0 0 1 19.917-19.875 16.666 16.666 0 0 1 28.083 0 16.666 16.666 0 0 1 19.917 19.917 16.666 16.666 0 0 1 0 28.083 16.666 16.666 0 0 1-19.875 19.917 16.666 16.666 0 0 1-28.125 0A16.666 16.666 0 0 1 11.04 59.084a16.667 16.667 0 0 1 0-28.167"/><path fill="url(#downloads-1m-a)" fill-opacity=".7" d="M11.041 30.917a16.666 16.666 0 0 1 19.917-19.875 16.666 16.666 0 0 1 28.083 0 16.666 16.666 0 0 1 19.917 19.917 16.666 16.666 0 0 1 0 28.083 16.666 16.666 0 0 1-19.875 19.917 16.666 16.666 0 0 1-28.125 0A16.666 16.666 0 0 1 11.04 59.084a16.667 16.667 0 0 1 0-28.167"/><path fill="url(#downloads-1m-b)" fill-opacity=".7" d="M83.647 45a13.67 13.67 0 0 0-6.305-11.514 3 3 0 0 1-1.312-3.185A13.667 13.667 0 0 0 59.7 13.969a3 3 0 0 1-3.186-1.31 13.67 13.67 0 0 0-23.029 0 3 3 0 0 1-3.183 1.31A13.668 13.668 0 0 0 13.97 30.268a3 3 0 0 1-1.325 3.185 13.67 13.67 0 0 0-4.664 18.14 13.67 13.67 0 0 0 4.665 4.957 3 3 0 0 1 1.324 3.184 13.67 13.67 0 0 0 9.54 16.09c2.21.64 4.546.712 6.792.208l.231-.043c1.16-.166 2.32.36 2.956 1.36a13.67 13.67 0 0 0 11.532 6.332v3l-.526-.01a16.7 16.7 0 0 1-7.032-1.802l-.464-.246a16.67 16.67 0 0 1-6.041-5.663 16.67 16.67 0 0 1-15.041-4.1l-.378-.366a16.7 16.7 0 0 1-4.071-6.625l-.155-.503a16.67 16.67 0 0 1-.272-8.281 16.7 16.7 0 0 1-5.426-5.588l-.261-.457a16.67 16.67 0 0 1-.247-15.612l.247-.465a16.67 16.67 0 0 1 5.687-6.045 16.7 16.7 0 0 1 .132-7.775l.14-.507a16.7 16.7 0 0 1 3.86-6.75l.366-.377a16.67 16.67 0 0 1 14.904-4.573l.515.107a16.7 16.7 0 0 1 5.581-5.382l.456-.259a16.667 16.667 0 0 1 21.757 5.204l.289.437a16.667 16.667 0 0 1 19.917 19.917A16.67 16.67 0 0 1 86.647 45l-.01.524a16.7 16.7 0 0 1-2.04 7.48l-.258.456a16.7 16.7 0 0 1-5.381 5.582 16.67 16.67 0 0 1-4.466 15.419l-.377.366a16.7 16.7 0 0 1-6.75 3.86l-.508.14a16.7 16.7 0 0 1-7.774.132 16.67 16.67 0 0 1-6.041 5.663l-.465.246a16.7 16.7 0 0 1-7.556 1.812v-3a13.67 13.67 0 0 0 11.53-6.331l.134-.194a3 3 0 0 1 3.047-1.125 13.67 13.67 0 0 0 16.09-9.54c.64-2.21.712-4.546.208-6.792a3 3 0 0 1 1.312-3.183A13.67 13.67 0 0 0 83.647 45"/><path fill="#000" fill-opacity=".5" fill-rule="evenodd" d="M46.709 6.46a13.66 13.66 0 0 0-8.274 1.573 13.67 13.67 0 0 0-4.95 4.625 3 3 0 0 1-3.183 1.312A13.668 13.668 0 0 0 13.97 30.267a3 3 0 0 1-.489 2.395c-.22.309-.502.579-.835.79a13.68 13.68 0 0 0-6.25 13.267 13.67 13.67 0 0 0 6.25 9.83 3 3 0 0 1 1.307 1.812c.11.44.12.91.017 1.372a13.6 13.6 0 0 0-.155 5.116 13.66 13.66 0 0 0 3.843 7.52 13.67 13.67 0 0 0 12.644 3.662l.231-.043c1.16-.166 2.32.36 2.956 1.36q.116.183.238.36a13.64 13.64 0 0 0 4.715 4.285 13.67 13.67 0 0 0 18.11-4.644q.064-.1.133-.194a3 3 0 0 1 3.047-1.125 13.67 13.67 0 0 0 16.63-13.35c-.001-1-.111-2-.332-2.982a3.01 3.01 0 0 1 1.312-3.183 13.668 13.668 0 0 0 0-23.029 3 3 0 0 1-.54-.441 2.998 2.998 0 0 1-.772-2.744 13.674 13.674 0 0 0-10.785-16.425 13.7 13.7 0 0 0-5.546.093 3 3 0 0 1-3.185-1.31 13.67 13.67 0 0 0-9.805-6.198m-36.1 43.685a10.667 10.667 0 0 1 3.641-14.158 6 6 0 0 0 2.648-6.37 10.666 10.666 0 0 1 12.747-12.72l.232.047a6 6 0 0 0 6.136-2.67 10.668 10.668 0 0 1 17.973 0l.131.197a6 6 0 0 0 6.24 2.424 10.67 10.67 0 0 1 12.746 12.748 6 6 0 0 0 2.623 6.37 10.667 10.667 0 0 1 3.766 13.813l-.157.297a10.7 10.7 0 0 1-3.61 3.864 6 6 0 0 0-2.622 6.368 10.667 10.667 0 0 1-12.721 12.747 6 6 0 0 0-6.095 2.25l-.068.096-.134.194-.065.096a10.67 10.67 0 0 1-8.664 4.937l-.336.005a10.67 10.67 0 0 1-8.815-4.66l-.184-.282a6 6 0 0 0-5.914-2.72l-.122.021-.231.043-.109.022a10.668 10.668 0 0 1-12.747-12.721 6 6 0 0 0-2.45-6.239l-.198-.13a10.7 10.7 0 0 1-3.64-3.87" clip-rule="evenodd"/><path fill="#fff" d="M36.334 51.364V63h-2.46v-9.301h-.069l-2.664 1.67v-2.181l2.88-1.824zm2.834 0h3.034l3.204 7.818h.137l3.204-7.818h3.034V63h-2.386v-7.574h-.097l-3.011 7.517h-1.625l-3.011-7.545h-.097V63h-2.386zm18.05 9.863v-8.045h2.033v8.045zm-3.006-3.005v-2.035h8.045v2.035z"/><path stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M45 35V23m5 7-5 5-5-5m14 5v4a2 2 0 0 1-2 2H38a2 2 0 0 1-2-2v-4"/><defs><linearGradient id="downloads-1m-a" x1="7.5" x2="80" y1="-3" y2="91" gradientUnits="userSpaceOnUse"><stop stop-color="#adff42"/><stop offset="1" stop-color="#0fe7ff"/></linearGradient><linearGradient id="downloads-1m-b" x1="7.5" x2="80" y1="-3" y2="91" gradientUnits="userSpaceOnUse"><stop stop-color="#46f291"/><stop offset="1" stop-color="#b2f8c4"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="none" viewBox="0 0 90 90"><path fill="#71b1f5" fill-rule="evenodd" d="M65 10c7.49 0 13.607 5.881 13.982 13.281l.003.075.015.567.001.077v10c0 3.918-1.61 7.459-4.204 10a13.96 13.96 0 0 1 4.188 9.356l.015.567.001.077v10c0 7.732-6.269 14.001-14.001 14.001H24.999c-7.733 0-14-6.27-14-14.001V54c0-3.918 1.61-7.459 4.203-10A13.96 13.96 0 0 1 11 34V24c0-7.732 6.268-14 14-14z" clip-rule="evenodd"/><path fill="url(#early-hosting-a)" fill-rule="evenodd" d="M65 10c7.49 0 13.607 5.881 13.982 13.281l.003.075.015.567.001.077v10c0 3.918-1.61 7.459-4.204 10a13.96 13.96 0 0 1 4.188 9.356l.015.567.001.077v10c0 7.732-6.269 14.001-14.001 14.001H24.999c-7.733 0-14-6.27-14-14.001V54c0-3.918 1.61-7.459 4.203-10A13.96 13.96 0 0 1 11 34V24c0-7.732 6.268-14 14-14z" clip-rule="evenodd"/><path fill="#23258c" fill-rule="evenodd" d="M65 13c5.885 0 10.691 4.621 10.986 10.434l.015.566v10c0 4.438-2.63 8.26-6.416 9.999 3.624 1.664 6.188 5.239 6.401 9.435l.015.566v10c0 6.075-4.926 11.001-11.001 11.001H24.999c-6.075 0-11-4.926-11-11.001V54c0-4.44 2.63-8.262 6.417-10-3.786-1.739-6.417-5.561-6.417-10V24c0-6.075 4.925-11 11-11zM34.496 55a5.98 5.98 0 0 1 1.53 4c0 1.538-.58 2.94-1.53 4.001H64V55zM30 56a3 3 0 1 0 0 6h.026a3 3 0 0 0 0-6zm4.496-31a5.98 5.98 0 0 1 1.53 4c0 1.538-.58 2.94-1.53 4.001H64V25zM30 26a3 3 0 1 0 0 6h.026a3 3 0 0 0 0-6z" clip-rule="evenodd"/><path fill="url(#early-hosting-b)" fill-rule="evenodd" d="M65 13c5.885 0 10.691 4.621 10.986 10.434l.015.566v10c0 4.438-2.63 8.26-6.416 9.999 3.624 1.664 6.188 5.239 6.401 9.435l.015.566v10c0 6.075-4.926 11.001-11.001 11.001H24.999c-6.075 0-11-4.926-11-11.001V54c0-4.44 2.63-8.262 6.417-10-3.786-1.739-6.417-5.561-6.417-10V24c0-6.075 4.925-11 11-11zM34.496 55a5.98 5.98 0 0 1 1.53 4c0 1.538-.58 2.94-1.53 4.001H64V55zM30 56a3 3 0 1 0 0 6h.026a3 3 0 0 0 0-6zm4.496-31a5.98 5.98 0 0 1 1.53 4c0 1.538-.58 2.94-1.53 4.001H64V25zM30 26a3 3 0 1 0 0 6h.026a3 3 0 0 0 0-6z" clip-rule="evenodd"/><path fill="#3b89f4" d="M67.001 24A2 2 0 0 0 65 22H24.999a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2.001H65A2 2 0 0 0 67.001 34zm6 10A8 8 0 0 1 65 42.001H24.999a8 8 0 0 1-8-8.001V24a8 8 0 0 1 8-8H65a8 8 0 0 1 8.001 8z"/><path fill="url(#early-hosting-c)" d="M67.001 24A2 2 0 0 0 65 22H24.999a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2.001H65A2 2 0 0 0 67.001 34zm6 10A8 8 0 0 1 65 42.001H24.999a8 8 0 0 1-8-8.001V24a8 8 0 0 1 8-8H65a8 8 0 0 1 8.001 8z"/><path fill="#3b89f4" d="M67.001 54A2 2 0 0 0 65 52H24.999a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2.001H65A2 2 0 0 0 67.001 64zm6 10A8 8 0 0 1 65 72.001H24.999a8 8 0 0 1-8-8.001V54a8 8 0 0 1 8-8H65a8 8 0 0 1 8.001 8z"/><path fill="url(#early-hosting-d)" d="M67.001 54A2 2 0 0 0 65 52H24.999a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2.001H65A2 2 0 0 0 67.001 64zm6 10A8 8 0 0 1 65 72.001H24.999a8 8 0 0 1-8-8.001V54a8 8 0 0 1 8-8H65a8 8 0 0 1 8.001 8z"/><path fill="#3b89f4" d="M30.026 26a3 3 0 0 1 0 6H30a3 3 0 0 1 0-6z"/><path fill="url(#early-hosting-e)" d="M30.026 26a3 3 0 0 1 0 6H30a3 3 0 0 1 0-6z"/><path fill="#3b89f4" d="M30.026 56a3 3 0 0 1 0 6H30a3 3 0 0 1 0-6z"/><path fill="url(#early-hosting-f)" d="M30.026 56a3 3 0 0 1 0 6H30a3 3 0 0 1 0-6z"/><defs><linearGradient id="early-hosting-a" x1="24.943" x2="79.403" y1="-.014" y2="82.407" gradientUnits="userSpaceOnUse"><stop stop-color="#bddcf3"/><stop offset="1" stop-color="#64aaf4"/></linearGradient><linearGradient id="early-hosting-b" x1="26.712" x2="76.367" y1="3.87" y2="79.018" gradientUnits="userSpaceOnUse"><stop stop-color="#2f137c"/><stop offset="1" stop-color="#19359a"/></linearGradient><linearGradient id="early-hosting-c" x1="28.482" x2="73.332" y1="7.754" y2="75.629" gradientUnits="userSpaceOnUse"><stop stop-color="#2aa3f3"/><stop offset="1" stop-color="#6d3bf7"/></linearGradient><linearGradient id="early-hosting-d" x1="28.482" x2="73.332" y1="7.754" y2="75.629" gradientUnits="userSpaceOnUse"><stop stop-color="#2aa3f3"/><stop offset="1" stop-color="#6d3bf7"/></linearGradient><linearGradient id="early-hosting-e" x1="28.482" x2="73.332" y1="7.754" y2="75.629" gradientUnits="userSpaceOnUse"><stop stop-color="#2aa3f3"/><stop offset="1" stop-color="#6d3bf7"/></linearGradient><linearGradient id="early-hosting-f" x1="28.482" x2="73.332" y1="7.754" y2="75.629" gradientUnits="userSpaceOnUse"><stop stop-color="#2aa3f3"/><stop offset="1" stop-color="#6d3bf7"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="none" viewBox="0 0 90 90"><path fill="url(#early-servers-a)" d="M77.157 44.15a6 6 0 0 0-.625-2.337l-9.195-18.365a11.34 11.34 0 0 0-9.744-6.275l-.056-.002-.29-.005H31.803a11.34 11.34 0 0 0-10.135 6.274l-9.2 18.373a6 6 0 0 0-.635 2.686v16a11.33 11.33 0 0 0 5.514 9.726 11.34 11.34 0 0 0 5.819 1.608h42.667l.563-.014a11.334 11.334 0 0 0 10.77-11.32v-16q0-.175-.01-.349M20.834 47.5v13a2.334 2.334 0 0 0 2.333 2.334h42.667a2.335 2.335 0 0 0 2.333-2.334v-13zM19.395 64.27a5 5 0 0 1-.181-.19zM65.167 50.5v9.334H42.962a5.99 5.99 0 0 0 2.23-4.667c0-1.885-.87-3.567-2.23-4.667zM28.5 58.166a3 3 0 1 1 0-6h.026a3 3 0 0 1 0 6zm10.666 0a3 3 0 0 1 0-6h.026a3 3 0 0 1 0 6zm-7.357-32-.163.006a2.33 2.33 0 0 0-1.925 1.289l-7.03 14.038H66.31l-7.025-14.03-.005-.008a2.33 2.33 0 0 0-1.925-1.29l-.163-.005zm24.971 3 4.673 9.333H27.55l4.673-9.333zm3.53-4.992a5.3 5.3 0 0 1 1.628 1.901l.03.051a5.33 5.33 0 0 0-1.966-2.16zm-31.619-.001a5 5 0 0 0 .308-.205zm2.787-.996q-.102.008-.202.018.08-.009.16-.015zm25.962-.004q-.02-.002-.04-.003l-.065-.001zM22.69 44.499a3 3 0 0 1-2.682-4.343l7.025-14.03-9.2 18.373v3a3 3 0 0 1 3-3zm45.476 0a3 3 0 0 1 3 3v-3l-9.195-18.365 7.02 14.022A3 3 0 0 1 66.31 44.5zm6 16a8.35 8.35 0 0 1-2.44 5.893 8.33 8.33 0 0 1-5.893 2.441H23.167a8.32 8.32 0 0 1-4.958-1.636 8.34 8.34 0 0 1-3.214-5.065 8.3 8.3 0 0 1-.161-1.633v-16a3 3 0 0 1 .317-1.343l9.2-18.373a8.33 8.33 0 0 1 7.455-4.617h25.39l.29.005a8.3 8.3 0 0 1 3.625.974q.24.128.472.272a8.33 8.33 0 0 1 3.071 3.374l9.196 18.365a3 3 0 0 1 .317 1.343zm6 0a14.333 14.333 0 0 1-13.697 14.32l-.563.013-.073.001H23.167A14.335 14.335 0 0 1 8.834 60.499v-16c0-1.398.326-2.778.952-4.028l9.2-18.373a14.33 14.33 0 0 1 5.27-5.78l.044-.027.339-.202q.022-.015.046-.027a14.34 14.34 0 0 1 7.117-1.896h25.495l.29.005.111.004a14.34 14.34 0 0 1 12.321 7.93l9.196 18.366a9 9 0 0 1 .952 4.028z"/><path fill="url(#early-servers-b)" d="M74.167 44.499a3 3 0 0 0-.317-1.343l-9.196-18.365a8.33 8.33 0 0 0-7.168-4.62l-.29-.005h-25.39a8.33 8.33 0 0 0-7.454 4.617l-9.2 18.373a3 3 0 0 0-.318 1.343v16a8.334 8.334 0 0 0 8.333 8.334h42.667a8.334 8.334 0 0 0 8.333-8.334zm-51 18.334a2.334 2.334 0 0 1-2.333-2.334v-13h47.333v13a2.334 2.334 0 0 1-2.333 2.334zM28.5 52.166a3 3 0 0 0 0 6h.026a3 3 0 0 0 0-6zm28.692-26 .163.006a2.33 2.33 0 0 1 1.925 1.289l.005.008 7.025 14.03H22.69l7.03-14.038a2.33 2.33 0 0 1 1.925-1.29l.163-.005zM27.55 38.499h33.904l-4.673-9.333H32.222zm8.617 16.667a3 3 0 0 0 3 3h.026a3 3 0 0 0 0-6h-.026a3 3 0 0 0-3 3m9.026 0c0 1.886-.87 3.567-2.23 4.667h22.205v-9.334H42.962a5.99 5.99 0 0 1 2.23 4.667m31.975 5.333a11.334 11.334 0 0 1-10.77 11.32l-.563.014H23.167a11.335 11.335 0 0 1-11.333-11.334v-16c0-.932.217-1.852.635-2.686l9.2-18.373a11.34 11.34 0 0 1 4.168-4.573l.339-.202a11.34 11.34 0 0 1 5.628-1.499h25.442l.291.005.056.002a11.33 11.33 0 0 1 9.744 6.275l9.195 18.365a6 6 0 0 1 .635 2.686z"/><path fill="url(#early-servers-c)" d="M68.167 47.499H20.834v13a2.334 2.334 0 0 0 2.333 2.334h42.667a2.335 2.335 0 0 0 2.333-2.334zm-39.64 4.667a3 3 0 0 1 0 6H28.5a3 3 0 1 1 0-6zm10.665 0a3 3 0 0 1 0 6h-.026a3 3 0 0 1 0-6zm-7.546-25.994a2.33 2.33 0 0 0-1.925 1.289l-7.03 14.038H66.31l-7.025-14.03-.005-.008a2.33 2.33 0 0 0-1.925-1.29l-.163-.005H31.81zm42.521 34.327a8.334 8.334 0 0 1-8.333 8.334H23.167a8.335 8.335 0 0 1-8.333-8.334v-16a3 3 0 0 1 .317-1.343l9.2-18.373a8.33 8.33 0 0 1 7.455-4.617h25.39l.29.005a8.33 8.33 0 0 1 7.168 4.62l9.196 18.365a3 3 0 0 1 .317 1.343z"/><defs><linearGradient id="early-servers-a" x1="23.461" x2="68.576" y1="5.232" y2="85.514" gradientUnits="userSpaceOnUse"><stop stop-color="#bddcf3"/><stop offset="1" stop-color="#64aaf4"/></linearGradient><linearGradient id="early-servers-b" x1="25.23" x2="65.537" y1="9.116" y2="82.018" gradientUnits="userSpaceOnUse"><stop stop-color="#2f137c"/><stop offset="1" stop-color="#19359a"/></linearGradient><linearGradient id="early-servers-c" x1="27" x2="62.5" y1="12.999" y2="78.499" gradientUnits="userSpaceOnUse"><stop stop-color="#2aa3f3"/><stop offset="1" stop-color="#6d3bf7"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="none" viewBox="0 0 90 90"><path fill="#d9d9d9" d="M78.5 49.252c0 21.25-14.875 31.875-32.555 38.037a4.25 4.25 0 0 1-2.848-.042C25.375 81.127 10.5 70.502 10.5 49.252v-29.75a4.25 4.25 0 0 1 4.25-4.25c8.5 0 19.125-5.1 26.52-11.56a4.97 4.97 0 0 1 6.46 0c7.438 6.502 18.02 11.56 26.52 11.56a4.25 4.25 0 0 1 4.25 4.25z"/><path fill="url(#moderator-a)" d="M78.5 49.252c0 21.25-14.875 31.875-32.555 38.037a4.25 4.25 0 0 1-2.848-.042C25.375 81.127 10.5 70.502 10.5 49.252v-29.75a4.25 4.25 0 0 1 4.25-4.25c8.5 0 19.125-5.1 26.52-11.56a4.97 4.97 0 0 1 6.46 0c7.438 6.502 18.02 11.56 26.52 11.56a4.25 4.25 0 0 1 4.25 4.25z"/><path fill="#000" fill-opacity=".6" d="M78.5 49.252c0 21.25-14.875 31.875-32.555 38.037a4.25 4.25 0 0 1-2.848-.042C25.375 81.127 10.5 70.502 10.5 49.252v-29.75a4.25 4.25 0 0 1 4.25-4.25c8.5 0 19.125-5.1 26.52-11.56a4.97 4.97 0 0 1 6.46 0c7.438 6.502 18.02 11.56 26.52 11.56a4.25 4.25 0 0 1 4.25 4.25z"/><path fill="#000" d="M77 19.502a2.75 2.75 0 0 0-2.75-2.75c-8.98 0-19.891-5.278-27.494-11.92l-.244-.19a3.47 3.47 0 0 0-4.268.19c-7.44 6.495-18.172 11.753-27.071 11.916l-.423.004a2.75 2.75 0 0 0-2.75 2.75v29.75c0 10.229 3.562 17.822 9.226 23.623 5.526 5.66 13.117 9.675 21.544 12.668l.817.286.034.012c.59.22 1.237.23 1.834.03 8.731-3.044 16.624-7.142 22.323-12.99C73.438 67.077 77 59.482 77 49.253zm3 29.75c0 11.021-3.875 19.364-10.073 25.724-6.158 6.317-14.541 10.611-23.489 13.73l-.011.004a5.75 5.75 0 0 1-3.82-.046c-8.97-3.097-17.364-7.38-23.528-13.693C12.875 68.616 9 60.273 9 49.252v-29.75a5.75 5.75 0 0 1 5.75-5.75l.758-.015c7.897-.292 17.77-5.055 24.775-11.174l.013-.011a6.47 6.47 0 0 1 8.185-.183l.223.183.014.01c7.271 6.358 17.516 11.19 25.532 11.19a5.75 5.75 0 0 1 5.75 5.75z"/><path fill="url(#moderator-b)" d="M77 19.502a2.75 2.75 0 0 0-2.75-2.75c-8.98 0-19.891-5.278-27.494-11.92l-.244-.19a3.47 3.47 0 0 0-4.268.19c-7.44 6.495-18.172 11.753-27.071 11.916l-.423.004a2.75 2.75 0 0 0-2.75 2.75v29.75c0 10.229 3.562 17.822 9.226 23.623 5.526 5.66 13.117 9.675 21.544 12.668l.817.286.034.012c.59.22 1.237.23 1.834.03 8.731-3.044 16.624-7.142 22.323-12.99C73.438 67.077 77 59.482 77 49.253zm3 29.75c0 11.021-3.875 19.364-10.073 25.724-6.158 6.317-14.541 10.611-23.489 13.73l-.011.004a5.75 5.75 0 0 1-3.82-.046c-8.97-3.097-17.364-7.38-23.528-13.693C12.875 68.616 9 60.273 9 49.252v-29.75a5.75 5.75 0 0 1 5.75-5.75l.758-.015c7.897-.292 17.77-5.055 24.775-11.174l.013-.011a6.47 6.47 0 0 1 8.185-.183l.223.183.014.01c7.271 6.358 17.516 11.19 25.532 11.19a5.75 5.75 0 0 1 5.75 5.75z"/><path fill="url(#moderator-c)" d="M41.75 19.002a2.75 2.75 0 0 1 5.5 0v3.44a39 39 0 0 0 17.25 4.033H67a2.75 2.75 0 1 1 0 5.5h-1.999l7.081 19.302a2.75 2.75 0 0 1-.909 3.13A15.07 15.07 0 0 1 62 57.528c-3.315 0-6.533-1.1-9.173-3.123a2.75 2.75 0 0 1-.909-3.129l7.201-19.632a44.5 44.5 0 0 1-11.869-3.162v33.769H57a2.75 2.75 0 1 1 0 5.5H32a2.75 2.75 0 0 1 0-5.5h9.75V28.483a44.5 44.5 0 0 1-11.87 3.162l7.202 19.632a2.75 2.75 0 0 1-.909 3.13A15.07 15.07 0 0 1 27 57.528c-3.315 0-6.533-1.1-9.173-3.123a2.75 2.75 0 0 1-.909-3.129L24 31.975H22a2.75 2.75 0 0 1 0-5.5h2.5a39 39 0 0 0 17.25-4.034zm-18.9 32.073a9.5 9.5 0 0 0 4.15.954c1.44 0 2.857-.328 4.15-.954L27 39.765zm35 0a9.5 9.5 0 0 0 4.15.954c1.44 0 2.856-.328 4.15-.954L62 39.765z"/><defs><linearGradient id="moderator-a" x1="32.26" x2="84.247" y1="20.057" y2="133.125" gradientUnits="userSpaceOnUse"><stop stop-color="#ffb66c"/><stop offset=".298" stop-color="#ffa347"/><stop offset="1" stop-color="#ff5100"/></linearGradient><linearGradient id="moderator-b" x1="32.26" x2="84.247" y1="20.057" y2="133.125" gradientUnits="userSpaceOnUse"><stop stop-color="#ffb66c"/><stop offset=".298" stop-color="#ffa347"/><stop offset="1" stop-color="#ff5100"/></linearGradient><linearGradient id="moderator-c" x1="35.5" x2="58" y1="28.502" y2="95.002" gradientUnits="userSpaceOnUse"><stop stop-color="#ffb66c"/><stop offset=".298" stop-color="#ffa347"/><stop offset="1" stop-color="#ff5100"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="none" viewBox="0 0 90 90"><path fill="url(#plus-a)" d="M72 57H58a1 1 0 0 0-1 1v14a7 7 0 0 1-7 7H40a7 7 0 0 1-7-7h3l.005.206A4 4 0 0 0 40 76h10l.206-.005A4 4 0 0 0 54 72V58a4 4 0 0 1 4-4h14l.206-.005A4 4 0 0 0 76 50V40a4 4 0 0 0-3.794-3.995L72 36H58a4 4 0 0 1-4-4V18a4 4 0 0 0-3.794-3.995L50 14H40a4 4 0 0 0-3.995 3.794L36 18h-3a7 7 0 0 1 7-7h10a7 7 0 0 1 7 7v14a1 1 0 0 0 1 1h14a7 7 0 0 1 7 7v10a7 7 0 0 1-7 7M33 18h3v14h-3zm-1 18H18a4 4 0 0 0-3.995 3.794L14 40h-3a7 7 0 0 1 7-7h14a1 1 0 0 0 1-1h3l-.005.206A4 4 0 0 1 32 36m1 22h3v14h-3zm-1-1H18a7 7 0 0 1-7-7h3a4 4 0 0 0 4 4h14l.206.005a4 4 0 0 1 3.79 3.789L36 58h-3a1 1 0 0 0-1-1M11 40h3v10h-3z"/><path fill="#000" fill-opacity=".4" d="M72 57H58a1 1 0 0 0-1 1v14a7 7 0 0 1-7 7H40a7 7 0 0 1-7-7h3l.005.206A4 4 0 0 0 40 76h10l.206-.005A4 4 0 0 0 54 72V58a4 4 0 0 1 4-4h14l.206-.005A4 4 0 0 0 76 50V40a4 4 0 0 0-3.794-3.995L72 36H58a4 4 0 0 1-4-4V18a4 4 0 0 0-3.794-3.995L50 14H40a4 4 0 0 0-3.995 3.794L36 18h-3a7 7 0 0 1 7-7h10a7 7 0 0 1 7 7v14a1 1 0 0 0 1 1h14a7 7 0 0 1 7 7v10a7 7 0 0 1-7 7M33 18h3v14h-3zm-1 18H18a4 4 0 0 0-3.995 3.794L14 40h-3a7 7 0 0 1 7-7h14a1 1 0 0 0 1-1h3l-.005.206A4 4 0 0 1 32 36m1 22h3v14h-3zm-1-1H18a7 7 0 0 1-7-7h3a4 4 0 0 0 4 4h14l.206.005a4 4 0 0 1 3.79 3.789L36 58h-3a1 1 0 0 0-1-1M11 40h3v10h-3z"/><path fill="url(#plus-b)" d="M58 54q-.105 0-.206.005A4 4 0 0 0 54 58h-3a7 7 0 0 1 6.64-6.991L58 51h13.97l.096-.002A1 1 0 0 0 73 50V40a1 1 0 0 0-.934-.997L71.97 39H58a7 7 0 0 1-7-7h3q0 .105.005.206A4 4 0 0 0 58 36h14l.206.005A4 4 0 0 1 76 40v10a4 4 0 0 1-3.794 3.995L72 54zM40 76a4 4 0 0 1-3.995-3.794L36 72h-3a7 7 0 0 0 7 7h10a7 7 0 0 0 7-7V58a1 1 0 0 1 1-1h14a7 7 0 0 0 7-7V40a7 7 0 0 0-7-7H58a1 1 0 0 1-1-1V18a7 7 0 0 0-7-7H40a7 7 0 0 0-7 7h3l.005-.206A4 4 0 0 1 40 14h10l.206.005A4 4 0 0 1 54 18v14h-3V18a1 1 0 0 0-.934-.997L49.97 17H40a1 1 0 0 0-.998.934L39 18.03v14.005l-.001.036-.006.246-.002.041A7 7 0 0 1 32 39H18a1 1 0 0 0-.998.934L17 40.03V50a1 1 0 0 0 .898.995L18 51h14.035l.036.001.246.006.041.002a7 7 0 0 1 6.633 6.633l.002.04.006.247.001.036V71.97l.002.097a1 1 0 0 0 .9.928L40 73h9.97l.096-.002A1 1 0 0 0 51 72V58h3v14a4 4 0 0 1-3.794 3.995L50 76zm-8-40a4 4 0 0 0 3.995-3.794L36 32V18h-3v14a1 1 0 0 1-1 1H18a7 7 0 0 0-7 7h3l.005-.206A4 4 0 0 1 18 36zm-2-6V18c0-5.35 4.202-9.72 9.485-9.987L40 8h10c5.523 0 10 4.477 10 10v12h12c5.523 0 10 4.477 10 10v10c0 5.523-4.477 10-10 10H60v12c0 5.523-4.477 10-10 10H40c-5.523 0-10-4.477-10-10h6V58h-3v14h-3V60H18c-5.523 0-10-4.477-10-10h3q0 .18.009.36A7 7 0 0 0 18 57h14a1 1 0 0 1 1 1h3l-.005-.206a4 4 0 0 0-3.789-3.79L32 54H18q-.105 0-.206-.005A4 4 0 0 1 14 50V40h-3v10H8V40l.013-.515C8.28 34.202 12.65 30 18 30z"/><path fill="#fff" fill-opacity=".4" d="M58 54q-.105 0-.206.005A4 4 0 0 0 54 58h-3a7 7 0 0 1 6.64-6.991L58 51h13.97l.096-.002A1 1 0 0 0 73 50V40a1 1 0 0 0-.934-.997L71.97 39H58a7 7 0 0 1-7-7h3q0 .105.005.206A4 4 0 0 0 58 36h14l.206.005A4 4 0 0 1 76 40v10a4 4 0 0 1-3.794 3.995L72 54zM40 76a4 4 0 0 1-3.995-3.794L36 72h-3a7 7 0 0 0 7 7h10a7 7 0 0 0 7-7V58a1 1 0 0 1 1-1h14a7 7 0 0 0 7-7V40a7 7 0 0 0-7-7H58a1 1 0 0 1-1-1V18a7 7 0 0 0-7-7H40a7 7 0 0 0-7 7h3l.005-.206A4 4 0 0 1 40 14h10l.206.005A4 4 0 0 1 54 18v14h-3V18a1 1 0 0 0-.934-.997L49.97 17H40a1 1 0 0 0-.998.934L39 18.03v14.005l-.001.036-.006.246-.002.041A7 7 0 0 1 32 39H18a1 1 0 0 0-.998.934L17 40.03V50a1 1 0 0 0 .898.995L18 51h14.035l.036.001.246.006.041.002a7 7 0 0 1 6.633 6.633l.002.04.006.247.001.036V71.97l.002.097a1 1 0 0 0 .9.928L40 73h9.97l.096-.002A1 1 0 0 0 51 72V58h3v14a4 4 0 0 1-3.794 3.995L50 76zm-8-40a4 4 0 0 0 3.995-3.794L36 32V18h-3v14a1 1 0 0 1-1 1H18a7 7 0 0 0-7 7h3l.005-.206A4 4 0 0 1 18 36zm-2-6V18c0-5.35 4.202-9.72 9.485-9.987L40 8h10c5.523 0 10 4.477 10 10v12h12c5.523 0 10 4.477 10 10v10c0 5.523-4.477 10-10 10H60v12c0 5.523-4.477 10-10 10H40c-5.523 0-10-4.477-10-10h6V58h-3v14h-3V60H18c-5.523 0-10-4.477-10-10h3q0 .18.009.36A7 7 0 0 0 18 57h14a1 1 0 0 1 1 1h3l-.005-.206a4 4 0 0 0-3.789-3.79L32 54H18q-.105 0-.206-.005A4 4 0 0 1 14 50V40h-3v10H8V40l.013-.515C8.28 34.202 12.65 30 18 30z"/><path fill="url(#plus-c)" d="M32 36a4 4 0 0 0 4-4V18a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v14a4 4 0 0 0 4 4h14a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4H58a4 4 0 0 0-4 4v14a4 4 0 0 1-4 4H40a4 4 0 0 1-4-4V58a4 4 0 0 0-4-4H18a4 4 0 0 1-4-4V40a4 4 0 0 1 4-4z"/><defs><linearGradient id="plus-a" x1="29" x2="55.5" y1="4.5" y2="85" gradientUnits="userSpaceOnUse"><stop stop-color="#d000ff"/><stop offset="1" stop-color="#7b00ff"/></linearGradient><linearGradient id="plus-b" x1="29" x2="55.5" y1="4.5" y2="85" gradientUnits="userSpaceOnUse"><stop stop-color="#d000ff"/><stop offset="1" stop-color="#2f00ff"/></linearGradient><linearGradient id="plus-c" x1="29" x2="55.5" y1="4.5" y2="85" gradientUnits="userSpaceOnUse"><stop stop-color="#da5fff"/><stop offset="1" stop-color="#7b00ff"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="none" viewBox="0 0 90 90"><path fill="url(#staff-a)" d="M85 45c0 22.091-17.909 40-40 40S5 67.091 5 45 22.909 5 45 5s40 17.909 40 40"/><path fill="#000" fill-opacity=".8" d="M85 45c0 22.091-17.909 40-40 40S5 67.091 5 45 22.909 5 45 5s40 17.909 40 40"/><path fill="url(#staff-b)" d="M85 45C85 22.909 67.091 5 45 5S5 22.909 5 45s17.909 40 40 40 40-17.909 40-40m3 0c0 23.748-19.252 43-43 43S2 68.748 2 45 21.252 2 45 2s43 19.252 43 43"/><path fill="url(#staff-c)" fill-rule="evenodd" d="M77.835 53.867A34.042 34.042 0 1 0 11.1 41.5h5.758a28.206 28.206 0 0 1 54.336-6.522l-5.595 1.497A22.33 22.33 0 0 0 51.682 23.75l-1.031 5.821a16.464 16.464 0 0 1 1.329 30.357l1.53 5.71a22.324 22.324 0 0 0 13.595-23.514l5.576-1.487c.59 3.72.416 7.52-.508 11.171z" clip-rule="evenodd"/><path fill="url(#staff-d)" d="M53.77 77.865A34.036 34.036 0 0 1 11 47.489h5.759A28.6 28.6 0 0 0 19.355 57l5.125-3.076a22.352 22.352 0 0 1 21.46-31.191l-1.042 5.82a16.5 16.5 0 0 0-15.87 20.736c.15.542.308 1.08.514 1.56l6.555-3.93-1.962-5.222 6.19-6.358 7.827-1.684 2.25 2.797-3.608 3.652-3.143.989-2.251 2.312 1.104 3.062s2.226 2.37 2.231 2.37l3.153-.834 2.24-2.462 4.895-1.555 1.44 3.282-5.029 6.186-8.465 2.673-3.8-4.228-6.618 3.978a16.53 16.53 0 0 0 13.782 5.562l1.53 5.725a22.36 22.36 0 0 1-20.384-8.24l-5.106 3.062a28.22 28.22 0 0 0 47.795-4.688l5.663 2.058a33.96 33.96 0 0 1-22.06 18.509"/><defs><linearGradient id="staff-a" x1="17.289" x2="58.84" y1="-6.043" y2="119.285" gradientUnits="userSpaceOnUse"><stop offset=".103" stop-color="#1bd96a"/><stop offset=".321" stop-color="#4ef092"/><stop offset=".66" stop-color="#1bd96a"/><stop offset="1" stop-color="#0e7338"/></linearGradient><linearGradient id="staff-b" x1="10.553" x2="72.296" y1="-6.667" y2="93.318" gradientUnits="userSpaceOnUse"><stop stop-color="#1bd9aa"/><stop offset=".5" stop-color="#1bd96a"/><stop offset="1" stop-color="#1bd951"/></linearGradient><linearGradient id="staff-c" x1="15.813" x2="53.404" y1="3.032" y2="78.68" gradientUnits="userSpaceOnUse"><stop stop-color="#1bd9aa"/><stop offset=".5" stop-color="#1bd96a"/><stop offset="1" stop-color="#1bd951"/></linearGradient><linearGradient id="staff-d" x1="15.5" x2="56" y1="14.5" y2="90" gradientUnits="userSpaceOnUse"><stop stop-color="#1bd9aa"/><stop offset=".5" stop-color="#1bd96a"/><stop offset="1" stop-color="#1bd951"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -495,12 +495,13 @@ a:not(.no-click-animation),
|
||||
|
||||
// CARDS
|
||||
.base-card {
|
||||
padding: var(--gap-xl);
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
min-height: var(--font-size-2xl);
|
||||
|
||||
background-color: var(--color-raised-bg);
|
||||
background-color: var(--surface-3);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--surface-4);
|
||||
|
||||
margin-bottom: var(--gap-md);
|
||||
outline: 2px solid transparent;
|
||||
@@ -526,19 +527,6 @@ a:not(.no-click-animation),
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add back later
|
||||
//&.warning {
|
||||
// border-left: 0.5rem solid var(--color-warning-banner-side);
|
||||
// background-color: var(--color-warning-banner-bg);
|
||||
// color: var(--color-warning-banner-text);
|
||||
//}
|
||||
//
|
||||
//&.information {
|
||||
// border-left: 0.5rem solid var(--color-info-banner-side);
|
||||
// background-color: var(--color-info-banner-bg);
|
||||
// color: var(--color-info-banner-text);
|
||||
//}
|
||||
}
|
||||
|
||||
.card {
|
||||
@@ -654,20 +642,20 @@ a:not(.no-click-animation),
|
||||
.v-popper--theme-dropdown,
|
||||
.v-popper--theme-dropdown.v-popper--theme-ribbit-popout {
|
||||
.v-popper__inner {
|
||||
border: 1px solid var(--color-divider) !important;
|
||||
border: 1px solid var(--surface-5) !important;
|
||||
padding: var(--gap-sm) !important;
|
||||
width: fit-content !important;
|
||||
border-radius: var(--radius-md) !important;
|
||||
background-color: var(--color-raised-bg) !important;
|
||||
box-shadow: var(--shadow-floating) !important;
|
||||
border-radius: 12px !important;
|
||||
background-color: var(--surface-3) !important;
|
||||
box-shadow: 3px 3px 0.8rem rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
|
||||
.v-popper__arrow-outer {
|
||||
border-color: var(--color-divider) !important;
|
||||
border-color: var(--surface-5) !important;
|
||||
}
|
||||
|
||||
.v-popper__arrow-inner {
|
||||
border-color: var(--color-raised-bg) !important;
|
||||
border-color: var(--surface-3) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,22 +699,25 @@ a:not(.no-click-animation),
|
||||
}
|
||||
|
||||
.v-popper--theme-tooltip {
|
||||
pointer-events: none;
|
||||
|
||||
.v-popper__inner {
|
||||
background: var(--color-tooltip-bg) !important;
|
||||
color: var(--color-tooltip-text) !important;
|
||||
padding: 0.5rem 0.5rem !important;
|
||||
border-radius: var(--radius-sm) !important;
|
||||
filter: drop-shadow(5px 5px 0.8rem rgba(0, 0, 0, 0.35));
|
||||
background: var(--surface-3) !important;
|
||||
color: var(--color-contrast) !important;
|
||||
padding: 0.625rem 0.75rem !important;
|
||||
border-radius: 12px !important;
|
||||
filter: drop-shadow(2px 2px 0.4rem rgba(0, 0, 0, 0.5));
|
||||
font-size: 0.9rem;
|
||||
font-weight: bold;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
border: 1px solid var(--surface-5);
|
||||
}
|
||||
|
||||
.v-popper__arrow-outer {
|
||||
border-color: var(--surface-5) !important;
|
||||
}
|
||||
|
||||
.v-popper__arrow-outer,
|
||||
.v-popper__arrow-inner {
|
||||
border-color: var(--color-tooltip-bg);
|
||||
visibility: visible;
|
||||
border-color: var(--surface-3) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="filteredLinks.length > 1"
|
||||
ref="scrollContainer"
|
||||
class="relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
|
||||
:class="{ 'drop-shadow-xl': mode === 'navigation' }"
|
||||
:class="{ 'drop-shadow-xl border border-solid border-surface-4': mode === 'navigation' }"
|
||||
>
|
||||
<template v-if="mode === 'navigation'">
|
||||
<RouterLink
|
||||
|
||||
@@ -14,4 +14,5 @@ export * from './search'
|
||||
export * from './servers'
|
||||
export * from './settings'
|
||||
export * from './skin'
|
||||
export * from './user'
|
||||
export * from './version'
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { ExternalIcon } from '@modrinth/assets'
|
||||
import { Tooltip } from 'floating-vue'
|
||||
import { type Component, useId } from 'vue'
|
||||
|
||||
import { type MessageDescriptor, useVIntl } from '#ui/composables/i18n.ts'
|
||||
|
||||
import AutoLink from '../base/AutoLink.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
defineProps<{
|
||||
icon: Component
|
||||
name: MessageDescriptor
|
||||
about: MessageDescriptor[]
|
||||
values?: Record<string, unknown>
|
||||
link?: {
|
||||
href: string
|
||||
message: MessageDescriptor
|
||||
}
|
||||
}>()
|
||||
|
||||
const baseId = useId()
|
||||
</script>
|
||||
<template>
|
||||
<Tooltip theme="tooltip" :triggers="['hover', 'focus']" :aria-id="`${baseId}-${name.id}`">
|
||||
<AutoLink
|
||||
:to="link?.href"
|
||||
class="rounded-2xl flex"
|
||||
:class="{
|
||||
'hover:bg-surface-4 focus:bg-surface-4': !!link,
|
||||
}"
|
||||
target="_blank"
|
||||
tabindex="0"
|
||||
>
|
||||
<component :is="icon" class="size-full p-0.5" />
|
||||
</AutoLink>
|
||||
<template #popper>
|
||||
<div class="flex flex-col max-w-[22rem] leading-tight gap-0.5">
|
||||
<span class="text-contrast mb-1">{{ formatMessage(name, values) }}</span>
|
||||
<span v-for="message of about" :key="message.id" class="text-primary">
|
||||
{{ formatMessage(message, values) }}
|
||||
</span>
|
||||
<span v-if="link" class="text-secondary text-xs opacity-80">
|
||||
{{ formatMessage(link.message, values) }} <ExternalIcon />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</template>
|
||||
@@ -0,0 +1,507 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import {
|
||||
AlphaBadge,
|
||||
BetaBadge,
|
||||
Downloads1mBadge,
|
||||
Downloads10mBadge,
|
||||
Downloads25mBadge,
|
||||
Downloads50mBadge,
|
||||
Downloads100mBadge,
|
||||
Downloads250mBadge,
|
||||
Downloads500mBadge,
|
||||
EarlyDatapackBadge,
|
||||
EarlyHostingBadge,
|
||||
EarlyModpackBadge,
|
||||
EarlyPluginBadge,
|
||||
EarlyResourcepackBadge,
|
||||
EarlyServersBadge,
|
||||
EarlyShadersBadge,
|
||||
ModeratorBadge,
|
||||
PlusBadge,
|
||||
PrideBadge,
|
||||
StaffBadge,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
defineMessage,
|
||||
defineMessages,
|
||||
type MessageDescriptor,
|
||||
useFormatNumber,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { UserBadge as BadgeBitflag } from '@modrinth/utils'
|
||||
import { type Component, computed } from 'vue'
|
||||
|
||||
import UserBadge from './UserBadge.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatNumber = useFormatNumber()
|
||||
|
||||
type EarlyAdopterProjectTypes =
|
||||
| 'modpack'
|
||||
| 'resourcepack'
|
||||
| 'plugin'
|
||||
| 'datapack'
|
||||
| 'shader'
|
||||
| 'server'
|
||||
|
||||
type BadgeCriterion =
|
||||
| {
|
||||
type: 'earliest_project_date'
|
||||
project_type: EarlyAdopterProjectTypes
|
||||
cutoff: Date
|
||||
}
|
||||
| {
|
||||
type: 'join_date'
|
||||
cutoff: Date
|
||||
}
|
||||
| {
|
||||
type: 'badge'
|
||||
bitflag: number
|
||||
}
|
||||
| {
|
||||
type: 'midas'
|
||||
}
|
||||
| {
|
||||
type: 'pride'
|
||||
}
|
||||
| {
|
||||
type: 'role'
|
||||
role: Labrinth.Users.v3.Role
|
||||
}
|
||||
|
||||
type Badge = {
|
||||
icon: Component
|
||||
name: MessageDescriptor
|
||||
about: MessageDescriptor[]
|
||||
criteria: BadgeCriterion[] // if any criterion matches, the badge will apply (OR logic)
|
||||
link?: {
|
||||
href: string
|
||||
message: MessageDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
const BADGES = [
|
||||
{
|
||||
icon: StaffBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.staff.name',
|
||||
defaultMessage: 'Modrinth Team',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.staff.about.1',
|
||||
defaultMessage: `This user works for Modrinth.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'role',
|
||||
role: 'admin',
|
||||
},
|
||||
{
|
||||
type: 'role',
|
||||
role: 'moderator',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: ModeratorBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.moderator.name',
|
||||
defaultMessage: 'Content Moderator',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.moderator.about.1',
|
||||
defaultMessage: `This user works for Modrinth as a Content Moderator.`,
|
||||
}),
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.moderator.about.2',
|
||||
defaultMessage: `Content Moderators on Modrinth review projects, handle reports, and help keep Modrinth safe.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'role',
|
||||
role: 'moderator',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: AlphaBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.alpha.name',
|
||||
defaultMessage: 'Alpha Tester',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.alpha.about.1',
|
||||
defaultMessage: `This user has been around since Modrinth Alpha, which ended in November 2020`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'badge',
|
||||
bitflag: BadgeBitflag.ALPHA_TESTER,
|
||||
},
|
||||
{
|
||||
type: 'join_date',
|
||||
cutoff: new Date('2020-11-30T08:00:00.000Z'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: BetaBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.beta.name',
|
||||
defaultMessage: 'Beta Tester',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.beta.about.1',
|
||||
defaultMessage: `This user has been around since Modrinth Beta, which ended in February 2022.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'join_date',
|
||||
cutoff: new Date('2022-02-27T08:00:00.000Z'),
|
||||
},
|
||||
],
|
||||
link: {
|
||||
href: 'https://modrinth.com/news/article/modrinth-beta/',
|
||||
message: defineMessage({
|
||||
id: 'user.profile.badge.beta.link',
|
||||
defaultMessage: `Click to read about the launch of Modrinth Beta.`,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: PlusBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.plus.name',
|
||||
defaultMessage: 'Modrinth+ Member',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.plus.about.1',
|
||||
defaultMessage: `This user is going the extra mile to support Modrinth and the creators on the platform.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'badge',
|
||||
bitflag: BadgeBitflag.MIDAS,
|
||||
},
|
||||
{
|
||||
type: 'midas',
|
||||
},
|
||||
],
|
||||
link: {
|
||||
href: 'https://modrinth.com/plus',
|
||||
message: defineMessage({
|
||||
id: 'user.profile.badge.plus.link',
|
||||
defaultMessage: `Click to learn more about how you can become a member.`,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: PrideBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.pride.name',
|
||||
defaultMessage: 'Pride Fundraiser Supporter',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.pride.about.1',
|
||||
defaultMessage: `This user participated in at least one of Modrinth's Pride fundraisers for the LGBTQ+ community.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'pride',
|
||||
},
|
||||
],
|
||||
link: {
|
||||
href: 'https://modrinth.com/pride',
|
||||
message: defineMessage({
|
||||
id: 'user.profile.badge.pride.link',
|
||||
defaultMessage: `Click to visit our latest Pride fundraiser.`,
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: EarlyModpackBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.early-modpack-adopter.name',
|
||||
defaultMessage: 'Early Modpack Adopter',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.early-modpack-adopter.about.1',
|
||||
defaultMessage: `This user helped us test Modpack projects on Modrinth before we launched them in May 2022.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'earliest_project_date',
|
||||
project_type: 'modpack',
|
||||
cutoff: new Date('2022-05-23T00:57:00.000Z'),
|
||||
},
|
||||
{
|
||||
type: 'badge',
|
||||
bitflag: BadgeBitflag.EARLY_MODPACK_ADOPTER,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: EarlyResourcepackBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.early-resourcepack-adopter.name',
|
||||
defaultMessage: 'Early Resource Pack Adopter',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.early-resourcepack-adopter.about.1',
|
||||
defaultMessage: `This user helped us test Resource Pack projects on Modrinth before we launched them in August 2022.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'earliest_project_date',
|
||||
project_type: 'resourcepack',
|
||||
cutoff: new Date('2022-08-27T23:03:00.000Z'),
|
||||
},
|
||||
{
|
||||
type: 'badge',
|
||||
bitflag: BadgeBitflag.EARLY_RESPACK_ADOPTER,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: EarlyPluginBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.early-plugin-adopter.name',
|
||||
defaultMessage: 'Early Plugin Adopter',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.early-plugin-adopter.about.1',
|
||||
defaultMessage: `This user helped us test Plugin projects on Modrinth before we launched them in August 2022.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'earliest_project_date',
|
||||
project_type: 'plugin',
|
||||
cutoff: new Date('2022-08-27T23:03:00.000Z'),
|
||||
},
|
||||
{
|
||||
type: 'badge',
|
||||
bitflag: BadgeBitflag.EARLY_PLUGIN_ADOPTER,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: EarlyDatapackBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.early-datapack-adopter.name',
|
||||
defaultMessage: 'Early Data Pack Adopter',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.early-datapack-adopter.about.1',
|
||||
defaultMessage: `This user helped us test Data Pack projects on Modrinth before we launched them in January 2023.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'earliest_project_date',
|
||||
project_type: 'datapack',
|
||||
cutoff: new Date('2023-01-08T02:00:00.000Z'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: EarlyShadersBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.early-shader-adopter.name',
|
||||
defaultMessage: 'Early Shader Adopter',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.early-shader-adopter.about.1',
|
||||
defaultMessage: `This user helped us test Shader projects on Modrinth before we launched them in January 2023.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'earliest_project_date',
|
||||
project_type: 'shader',
|
||||
cutoff: new Date('2023-01-08T02:00:00.000Z'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: EarlyServersBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.early-server-adopter.name',
|
||||
defaultMessage: 'Early Server Adopter',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.early-server-adopter.about.1',
|
||||
defaultMessage: `This user helped us test Server projects on Modrinth before we launched them in March 2026.`,
|
||||
}),
|
||||
],
|
||||
criteria: [
|
||||
{
|
||||
type: 'earliest_project_date',
|
||||
project_type: 'server',
|
||||
cutoff: new Date('2026-03-04T01:33:00.000Z'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: EarlyHostingBadge,
|
||||
name: defineMessage({
|
||||
id: 'user.profile.badge.hosting-alpha.name',
|
||||
defaultMessage: 'Modrinth Hosting Alpha Tester',
|
||||
}),
|
||||
about: [
|
||||
defineMessage({
|
||||
id: 'user.profile.badge.hosting-alpha.about.1',
|
||||
defaultMessage: `This user participated in a closed alpha test of Modrinth Hosting before we launched Modrinth Hosting Beta in November 2024`,
|
||||
}),
|
||||
],
|
||||
criteria: [], // TODO: Add badge on backend for Hosting Alpha Tester
|
||||
},
|
||||
] satisfies Badge[]
|
||||
|
||||
const DOWNLOAD_BADGES = [
|
||||
{
|
||||
icon: Downloads1mBadge,
|
||||
threshold: 1_000_000,
|
||||
},
|
||||
{
|
||||
icon: Downloads10mBadge,
|
||||
threshold: 10_000_000,
|
||||
},
|
||||
{
|
||||
icon: Downloads25mBadge,
|
||||
threshold: 25_000_000,
|
||||
},
|
||||
{
|
||||
icon: Downloads50mBadge,
|
||||
threshold: 50_000_000,
|
||||
},
|
||||
{
|
||||
icon: Downloads100mBadge,
|
||||
threshold: 100_000_000,
|
||||
},
|
||||
{
|
||||
icon: Downloads250mBadge,
|
||||
threshold: 250_000_000,
|
||||
},
|
||||
{
|
||||
icon: Downloads500mBadge,
|
||||
threshold: 500_000_000,
|
||||
},
|
||||
].sort((a, b) => b.threshold - a.threshold)
|
||||
|
||||
const props = defineProps<{
|
||||
role: Labrinth.Users.v2.Role
|
||||
badges: number
|
||||
hasMidas?: boolean
|
||||
hasPride?: boolean
|
||||
downloads: number
|
||||
joinDate: Date
|
||||
earliestProjectByType: Record<EarlyAdopterProjectTypes, Date>
|
||||
}>()
|
||||
|
||||
const downloadsBadge = computed(() => {
|
||||
return DOWNLOAD_BADGES.find((badge) => props.downloads >= badge.threshold)
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'profile.label.badges',
|
||||
defaultMessage: 'Badges',
|
||||
},
|
||||
downloadsBadgeName: {
|
||||
id: 'user.profile.badge.downloads.name',
|
||||
defaultMessage: '{download_sum} Downloads',
|
||||
},
|
||||
downloadsBadgeAbout1: {
|
||||
id: 'user.profile.badge.downloads.about.1',
|
||||
defaultMessage: `This user's projects have collectively achieved {download_sum} downloads.`,
|
||||
},
|
||||
})
|
||||
|
||||
function passesCriterion(criterion: BadgeCriterion) {
|
||||
switch (criterion.type) {
|
||||
case 'role': {
|
||||
return props.role === criterion.role
|
||||
}
|
||||
case 'badge': {
|
||||
return props.badges & criterion.bitflag
|
||||
}
|
||||
case 'midas': {
|
||||
return props.hasMidas === true
|
||||
}
|
||||
case 'pride': {
|
||||
return props.hasPride === true
|
||||
}
|
||||
case 'join_date': {
|
||||
return props.joinDate < criterion.cutoff
|
||||
}
|
||||
case 'earliest_project_date': {
|
||||
const date = props.earliestProjectByType[criterion.project_type]
|
||||
return date && date < criterion.cutoff
|
||||
}
|
||||
default: {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const earnedBadges = computed(() => {
|
||||
const badges: Badge[] = []
|
||||
|
||||
loopingBadges: for (const badge of BADGES) {
|
||||
for (const criterion of badge.criteria) {
|
||||
if (passesCriterion(criterion)) {
|
||||
badges.push(badge)
|
||||
continue loopingBadges
|
||||
}
|
||||
}
|
||||
}
|
||||
return badges
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="earnedBadges.length > 0 || !!downloadsBadge" class="flex flex-col">
|
||||
<h2 class="text-lg text-contrast m-0 mb-2">
|
||||
{{ formatMessage(messages.title) }}
|
||||
</h2>
|
||||
<div class="grid grid-cols-[repeat(auto-fill,minmax(64px,1fr))] gap-2">
|
||||
<UserBadge
|
||||
v-for="badge in earnedBadges"
|
||||
:key="badge.name.id"
|
||||
:name="badge.name"
|
||||
:icon="badge.icon"
|
||||
:about="badge.about"
|
||||
:link="badge.link"
|
||||
/>
|
||||
<UserBadge
|
||||
v-if="downloadsBadge"
|
||||
:name="messages.downloadsBadgeName"
|
||||
:icon="downloadsBadge.icon"
|
||||
:about="[messages.downloadsBadgeAbout1]"
|
||||
:values="{ download_sum: formatNumber(downloadsBadge.threshold) }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as UserBadges } from './UserBadges.vue'
|
||||
@@ -30,14 +30,14 @@ const filterClass = computed(() => {
|
||||
if (ctx.filtersMenuOpen?.value) {
|
||||
return 'border-0 border-b-[1px] border-solid border-divider last:border-b-0'
|
||||
}
|
||||
return 'card-shadow rounded-2xl bg-bg-raised'
|
||||
return 'card-shadow rounded-2xl bg-surface-3 border border-solid border-surface-4'
|
||||
})
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
if (isApp.value) {
|
||||
return 'button-animation flex flex-col gap-1 px-4 py-3 w-full bg-transparent cursor-pointer border-none hover:bg-button-bg'
|
||||
return 'button-animation flex flex-col gap-1 px-3 py-3 w-full bg-transparent cursor-pointer border-none hover:bg-button-bg'
|
||||
}
|
||||
return 'button-animation flex flex-col gap-1 px-6 py-4 w-full bg-transparent cursor-pointer border-none'
|
||||
return 'button-animation flex flex-col gap-1 px-6 py-3 w-full bg-transparent cursor-pointer border-none'
|
||||
})
|
||||
|
||||
const contentClass = computed(() => (isApp.value ? 'mt-2 mb-3' : 'mb-4 mx-3'))
|
||||
|
||||
@@ -2423,6 +2423,9 @@
|
||||
"payment-method.visa": {
|
||||
"defaultMessage": "Visa"
|
||||
},
|
||||
"profile.label.badges": {
|
||||
"defaultMessage": "Badges"
|
||||
},
|
||||
"project-card.date.published.tooltip": {
|
||||
"defaultMessage": "Published {date}"
|
||||
},
|
||||
@@ -4483,5 +4486,101 @@
|
||||
},
|
||||
"ui.stacked-admonitions.dismiss-all": {
|
||||
"defaultMessage": "Dismiss all"
|
||||
},
|
||||
"user.profile.badge.alpha.about.1": {
|
||||
"defaultMessage": "This user has been around since Modrinth Alpha, which ended in November 2020"
|
||||
},
|
||||
"user.profile.badge.alpha.name": {
|
||||
"defaultMessage": "Alpha Tester"
|
||||
},
|
||||
"user.profile.badge.beta.about.1": {
|
||||
"defaultMessage": "This user has been around since Modrinth Beta, which ended in February 2022."
|
||||
},
|
||||
"user.profile.badge.beta.link": {
|
||||
"defaultMessage": "Click to read about the launch of Modrinth Beta."
|
||||
},
|
||||
"user.profile.badge.beta.name": {
|
||||
"defaultMessage": "Beta Tester"
|
||||
},
|
||||
"user.profile.badge.downloads.about.1": {
|
||||
"defaultMessage": "This user's projects have collectively achieved {download_sum} downloads."
|
||||
},
|
||||
"user.profile.badge.downloads.name": {
|
||||
"defaultMessage": "{download_sum} Downloads"
|
||||
},
|
||||
"user.profile.badge.early-datapack-adopter.about.1": {
|
||||
"defaultMessage": "This user helped us test Data Pack projects on Modrinth before we launched them in January 2023."
|
||||
},
|
||||
"user.profile.badge.early-datapack-adopter.name": {
|
||||
"defaultMessage": "Early Data Pack Adopter"
|
||||
},
|
||||
"user.profile.badge.early-modpack-adopter.about.1": {
|
||||
"defaultMessage": "This user helped us test Modpack projects on Modrinth before we launched them in May 2022."
|
||||
},
|
||||
"user.profile.badge.early-modpack-adopter.name": {
|
||||
"defaultMessage": "Early Modpack Adopter"
|
||||
},
|
||||
"user.profile.badge.early-plugin-adopter.about.1": {
|
||||
"defaultMessage": "This user helped us test Plugin projects on Modrinth before we launched them in August 2022."
|
||||
},
|
||||
"user.profile.badge.early-plugin-adopter.name": {
|
||||
"defaultMessage": "Early Plugin Adopter"
|
||||
},
|
||||
"user.profile.badge.early-resourcepack-adopter.about.1": {
|
||||
"defaultMessage": "This user helped us test Resource Pack projects on Modrinth before we launched them in August 2022."
|
||||
},
|
||||
"user.profile.badge.early-resourcepack-adopter.name": {
|
||||
"defaultMessage": "Early Resource Pack Adopter"
|
||||
},
|
||||
"user.profile.badge.early-server-adopter.about.1": {
|
||||
"defaultMessage": "This user helped us test Server projects on Modrinth before we launched them in March 2026."
|
||||
},
|
||||
"user.profile.badge.early-server-adopter.name": {
|
||||
"defaultMessage": "Early Server Adopter"
|
||||
},
|
||||
"user.profile.badge.early-shader-adopter.about.1": {
|
||||
"defaultMessage": "This user helped us test Shader projects on Modrinth before we launched them in January 2023."
|
||||
},
|
||||
"user.profile.badge.early-shader-adopter.name": {
|
||||
"defaultMessage": "Early Shader Adopter"
|
||||
},
|
||||
"user.profile.badge.hosting-alpha.about.1": {
|
||||
"defaultMessage": "This user participated in a closed alpha test of Modrinth Hosting before we launched Modrinth Hosting Beta in November 2024"
|
||||
},
|
||||
"user.profile.badge.hosting-alpha.name": {
|
||||
"defaultMessage": "Modrinth Hosting Alpha Tester"
|
||||
},
|
||||
"user.profile.badge.moderator.about.1": {
|
||||
"defaultMessage": "This user works for Modrinth as a Content Moderator."
|
||||
},
|
||||
"user.profile.badge.moderator.about.2": {
|
||||
"defaultMessage": "Content Moderators on Modrinth review projects, handle reports, and help keep Modrinth safe."
|
||||
},
|
||||
"user.profile.badge.moderator.name": {
|
||||
"defaultMessage": "Content Moderator"
|
||||
},
|
||||
"user.profile.badge.plus.about.1": {
|
||||
"defaultMessage": "This user is going the extra mile to support Modrinth and the creators on the platform."
|
||||
},
|
||||
"user.profile.badge.plus.link": {
|
||||
"defaultMessage": "Click to learn more about how you can become a member."
|
||||
},
|
||||
"user.profile.badge.plus.name": {
|
||||
"defaultMessage": "Modrinth+ Member"
|
||||
},
|
||||
"user.profile.badge.pride.about.1": {
|
||||
"defaultMessage": "This user participated in at least one of Modrinth's Pride fundraisers for the LGBTQ+ community."
|
||||
},
|
||||
"user.profile.badge.pride.link": {
|
||||
"defaultMessage": "Click to visit our latest Pride fundraiser."
|
||||
},
|
||||
"user.profile.badge.pride.name": {
|
||||
"defaultMessage": "Pride Fundraiser Supporter"
|
||||
},
|
||||
"user.profile.badge.staff.about.1": {
|
||||
"defaultMessage": "This user works for Modrinth."
|
||||
},
|
||||
"user.profile.badge.staff.name": {
|
||||
"defaultMessage": "Modrinth Team"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ import type { Ref } from 'vue'
|
||||
|
||||
import { createContext } from './create-context'
|
||||
|
||||
export type AuthUser = Labrinth.Users.v2.User | Labrinth.Users.v3.User
|
||||
|
||||
export interface AuthProvider {
|
||||
session_token: Ref<string | null>
|
||||
user: Ref<Labrinth.Users.v2.User | null>
|
||||
user: Ref<AuthUser | null>
|
||||
/** True once the initial auth check has completed (regardless of result). */
|
||||
isReady?: Ref<boolean>
|
||||
requestSignIn: (redirectPath: string) => void | Promise<void>
|
||||
|
||||
@@ -15,6 +15,9 @@ export default defineConfig({
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false,
|
||||
cleanupIds: {
|
||||
minify: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||