diff --git a/apps/frontend/src/components/ui/servers/marketing/ServerPlanSelector.vue b/apps/frontend/src/components/ui/servers/marketing/ServerPlanSelector.vue new file mode 100644 index 000000000..7c08bdf9e --- /dev/null +++ b/apps/frontend/src/components/ui/servers/marketing/ServerPlanSelector.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/apps/frontend/src/composables/pyroFetch.ts b/apps/frontend/src/composables/pyroFetch.ts index 0d0da31b9..1c2e45a98 100644 --- a/apps/frontend/src/composables/pyroFetch.ts +++ b/apps/frontend/src/composables/pyroFetch.ts @@ -10,6 +10,7 @@ interface PyroFetchOptions { token?: string; }; retry?: boolean; + bypassAuth?: boolean; } export class PyroFetchError extends Error { @@ -28,7 +29,7 @@ export async function usePyroFetch(path: string, options: PyroFetchOptions = const auth = await useAuth(); const authToken = auth.value?.token; - if (!authToken) { + if (!authToken && !options.bypassAuth) { throw new PyroFetchError("Cannot pyrofetch without auth", 10000); } @@ -52,9 +53,15 @@ export async function usePyroFetch(path: string, options: PyroFetchOptions = type HeadersRecord = Record; + const authHeader: HeadersRecord = options.bypassAuth + ? {} + : { + Authorization: `Bearer ${override?.token ?? authToken}`, + "Access-Control-Allow-Headers": "Authorization", + }; + const headers: HeadersRecord = { - Authorization: `Bearer ${override?.token ?? authToken}`, - "Access-Control-Allow-Headers": "Authorization", + ...authHeader, "User-Agent": "Pyro/1.0 (https://pyro.host)", Vary: "Accept, Origin", "Content-Type": contentType, diff --git a/apps/frontend/src/locales/en-US/index.json b/apps/frontend/src/locales/en-US/index.json index a8cb4682b..79fbaf584 100644 --- a/apps/frontend/src/locales/en-US/index.json +++ b/apps/frontend/src/locales/en-US/index.json @@ -1043,6 +1043,33 @@ "servers.notices.no-notices": { "message": "No notices" }, + "servers.plan.large.description": { + "message": "Ideal for larger communities, modpacks, and heavy modding." + }, + "servers.plan.large.name": { + "message": "Large" + }, + "servers.plan.large.symbol": { + "message": "L" + }, + "servers.plan.medium.description": { + "message": "Great for modded multiplayer and small communities." + }, + "servers.plan.medium.name": { + "message": "Medium" + }, + "servers.plan.medium.symbol": { + "message": "M" + }, + "servers.plan.small.description": { + "message": "Perfect for vanilla multiplayer, small friend groups, SMPs, and light modding." + }, + "servers.plan.small.name": { + "message": "Small" + }, + "servers.plan.small.symbol": { + "message": "S" + }, "settings.billing.modal.cancel.action": { "message": "Cancel subscription" }, diff --git a/apps/frontend/src/pages/servers/index.vue b/apps/frontend/src/pages/servers/index.vue index 1b5c6cd4a..695bed554 100644 --- a/apps/frontend/src/pages/servers/index.vue +++ b/apps/frontend/src/pages/servers/index.vue @@ -211,7 +211,7 @@

Experience modern, reliable hosting

Modrinth Servers are hosted on - 2023 Ryzen 7/9 CPUs with DDR5 RAM, running on + high-performance AMD CPUs with DDR5 RAM, running on custom-built software to ensure your server performs smoothly.

@@ -329,27 +329,6 @@ alt="" class="absolute -bottom-12 -right-[15%] hidden max-w-2xl rounded-2xl bg-brand p-4 lg:block" /> -
- - - -
@@ -418,9 +397,24 @@ What kind of CPUs do Modrinth Servers run on? -

- Modrinth Servers use 2023 Ryzen 7 and Ryzen 9 CPUs at 4+ GHz, paired with DDR5 - memory. +

+ Modrinth Servers are powered by AMD Ryzen 7900 and 7950X3D equivalent CPUs at 5+ + GHz, paired with with DDR5 memory. +

+ +
+ + + + + How do CPU burst threads work? + +

+ When your server is under heavy load, we temporarily give it access to additional + CPU threads to help mitigate lag spikes and instability. This helps prevent the TPS + from going below 20, ensuring the smoothest experience possible. Since those extra + CPU threads are only shortly available during high load periods, they might not show + up in Spark reports or other profiling tools.

@@ -431,10 +425,12 @@ Do Modrinth Servers have DDoS protection? -

- Yes. All Modrinth Servers come with DDoS protection. Protection is powered by a - combination of in-house network filtering as well as with our data center provider. - Your server is safe on Modrinth. +

+ Yes. All Modrinth Servers come with DDoS protection powered by + OVHcloud® Anti-DDoS infrastructure + which has over 17Tbps capacity. Your server is safe on Modrinth.

@@ -445,11 +441,9 @@ Where are Modrinth Servers located? Can I choose a region? -

- Currently, Modrinth Servers are located throughout the United States in New York, - Los Angelas, Dallas, Miami, and Spokane. More regions are coming soon! Your server's - location is currently chosen algorithmically, but you will be able to choose a - region in the future. +

+ Currently, Modrinth Servers are located on the east coast of the United States in + Vint Hill, Virginia. More regions to come in the future!

@@ -460,7 +454,7 @@ Can I increase the storage on my server? -

+

Yes, storage can be increased on your server at no additional cost. If you need more storage, reach out to Modrinth Support.

@@ -471,13 +465,19 @@ - How fast are Modrinth Servers? How many players can they handle? + How fast are Modrinth Servers? -

- During the Modrinth "Emergency SMP" test, we had over 80 players on a server running - on the Large plan. The server ran smoothly and was only limited by RAM. We're - confident that Modrinth Servers can handle a large number of players, with any kind - of modpack. +

+ Modrinth Servers are hosted on very modern high-performance hardware, but it's tough + to say how exactly that will translate into how fast your server will run because + there are so many factors that affect it, such as the mods, data packs, or plugins + you're running on your server, and even user behavior. +

+

+ Most performance issues that arise tend to be the fault of an unoptimized modpack, + mod, data pack, or plugin that causes the server to lag. Since our servers are very + high-end, you shouldn't run into much trouble as long as you pick an appropriate + plan for the content you're running on the server.

@@ -486,6 +486,7 @@
@@ -596,182 +597,49 @@
    -
  • -
    - Only {{ capacityStatuses?.small?.available }} left in stock! -
    -
    -
    -
    -

    Small

    -
    - S -
    -
    -

    - Perfect for vanilla multiplayer, small friend groups, SMPs, and light modding. -

    -
    -

    4 GB RAM

    -
    -

    4 vCPUs

    -
    -

    32 GB Storage

    -
    -

    - $12/month -

    -
    - - - Out of Stock - - - - -
    -
  • - -
  • -
    - Only {{ capacityStatuses?.medium?.available }} left in stock! -
    -
    -
    -
    -

    Medium

    -
    - M -
    -
    -

    Great for modded multiplayer and small communities.

    -
    -

    6 GB RAM

    -
    -

    6 vCPUs

    -
    -

    48 GB Storage

    -
    -

    - $18/month -

    -
    - - - Out of Stock - - - - -
    -
  • - -
  • -
    - Only {{ capacityStatuses?.large?.available }} left in stock! -
    -
    -
    -
    -

    Large

    -
    - L -
    -
    -

    Ideal for larger communities, modpacks, and heavy modding.

    -
    -

    8 GB RAM

    -
    -

    8 vCPUs

    -
    -

    64 GB Storage

    -
    -

    - $24/month -

    -
    - - - Out of Stock - - - - -
    -
  • + + +

Build your own

@@ -781,11 +649,13 @@
-
+

Starting at $3/GB RAM

@@ -802,9 +672,6 @@ import { BoxIcon, GameIcon, RightArrowIcon, - SearchIcon, - SortAscendingIcon, - ExternalIcon, TerminalSquareIcon, TransferIcon, VersionIcon, @@ -813,6 +680,7 @@ import { import { products } from "~/generated/state.json"; import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue"; import Globe from "~/components/ui/servers/Globe.vue"; +import ServerPlanSelector from "~/components/ui/servers/marketing/ServerPlanSelector.vue"; const pyroProducts = products.filter((p) => p.metadata.type === "pyro"); const pyroPlanProducts = pyroProducts.filter( @@ -893,6 +761,7 @@ async function fetchCapacityStatuses(customProduct = null) { swap_mb: product.metadata.swap, storage_mb: product.metadata.storage, }, + bypassAuth: true, }), ); @@ -931,21 +800,6 @@ const isMediumAtCapacity = computed(() => capacityStatuses.value?.medium?.availa const isLargeAtCapacity = computed(() => capacityStatuses.value?.large?.available === 0); const isCustomAtCapacity = computed(() => capacityStatuses.value?.custom?.available === 0); -const isSmallLowStock = computed(() => { - const available = capacityStatuses.value?.small?.available; - return available !== undefined && available > 0 && available < 8; -}); - -const isMediumLowStock = computed(() => { - const available = capacityStatuses.value?.medium?.available; - return available !== undefined && available > 0 && available < 8; -}); - -const isLargeLowStock = computed(() => { - const available = capacityStatuses.value?.large?.available; - return available !== undefined && available > 0 && available < 8; -}); - const startTyping = () => { const currentWord = words[currentWordIndex.value]; if (isDeleting.value) { diff --git a/apps/frontend/src/pages/settings/billing/index.vue b/apps/frontend/src/pages/settings/billing/index.vue index 29e93f7c3..893721eb5 100644 --- a/apps/frontend/src/pages/settings/billing/index.vue +++ b/apps/frontend/src/pages/settings/billing/index.vue @@ -196,7 +196,10 @@
- {{ getPyroProduct(subscription)?.metadata?.cpu }} vCores (CPU) + + {{ getPyroProduct(subscription)?.metadata?.cpu / 2 }} Shared CPUs (Bursts up + to {{ getPyroProduct(subscription)?.metadata?.cpu }} CPUs) +
diff --git a/packages/ui/src/components/base/ButtonStyled.vue b/packages/ui/src/components/base/ButtonStyled.vue index abcbd755d..4be4a2fc4 100644 --- a/packages/ui/src/components/base/ButtonStyled.vue +++ b/packages/ui/src/components/base/ButtonStyled.vue @@ -6,7 +6,7 @@ const props = withDefaults( color?: 'standard' | 'brand' | 'red' | 'orange' | 'green' | 'blue' | 'purple' size?: 'standard' | 'large' circular?: boolean - type?: 'standard' | 'outlined' | 'transparent' | 'highlight' + type?: 'standard' | 'outlined' | 'transparent' | 'highlight' | 'highlight-colored-text' colorFill?: 'auto' | 'background' | 'text' | 'none' hoverColorFill?: 'auto' | 'background' | 'text' | 'none' highlightedStyle?: 'main-nav-primary' | 'main-nav-secondary' @@ -24,20 +24,40 @@ const props = withDefaults( }, ) +const highlightedColorVar = computed(() => { + switch (props.color) { + case 'brand': + return 'var(--color-brand-highlight)' + case 'red': + return 'var(--color-red-highlight)' + case 'orange': + return 'var(--color-orange-highlight)' + case 'green': + return 'var(--color-green-highlight)' + case 'blue': + return 'var(--color-blue-highlight)' + case 'purple': + return 'var(--color-purple-highlight)' + case 'standard': + default: + return null + } +}) + const colorVar = computed(() => { switch (props.color) { case 'brand': - return props.type === 'highlight' ? 'var(--color-brand-highlight)' : 'var(--color-brand)' + return 'var(--color-brand)' case 'red': - return props.type === 'highlight' ? 'var(--color-red-highlight)' : 'var(--color-red)' + return 'var(--color-red)' case 'orange': - return props.type === 'highlight' ? 'var(--color-orange-highlight)' : 'var(--color-orange)' + return 'var(--color-orange)' case 'green': - return props.type === 'highlight' ? 'var(--color-green-highlight)' : 'var(--color-green)' + return 'var(--color-green)' case 'blue': - return props.type === 'highlight' ? 'var(--color-blue-highlight)' : 'var(--color-blue)' + return 'var(--color-blue)' case 'purple': - return props.type === 'highlight' ? 'var(--color-purple-highlight)' : 'var(--color-purple)' + return 'var(--color-purple)' case 'standard': default: return null @@ -111,10 +131,14 @@ function setColorFill( ): { bg: string; text: string } { if (colorVar.value) { if (fill === 'background') { - colors.bg = colorVar.value - if (props.type === 'highlight') { + if (props.type === 'highlight' && highlightedColorVar.value) { + colors.bg = highlightedColorVar.value colors.text = 'var(--color-contrast)' + } else if (props.type === 'highlight-colored-text' && highlightedColorVar.value) { + colors.bg = highlightedColorVar.value + colors.text = colorVar.value } else { + colors.bg = colorVar.value colors.text = 'var(--color-accent-contrast)' } } else if (fill === 'text') { diff --git a/packages/ui/src/components/billing/PurchaseModal.vue b/packages/ui/src/components/billing/PurchaseModal.vue index 00d6710bd..65d92dae3 100644 --- a/packages/ui/src/components/billing/PurchaseModal.vue +++ b/packages/ui/src/components/billing/PurchaseModal.vue @@ -106,13 +106,16 @@

Your current plan has {{ existingPlan.metadata.ram / 1024 }} GB RAM and - {{ existingPlan.metadata.cpu }} vCPUs. + {{ existingPlan.metadata.cpu / 2 }} shared CPUs (bursts up to + {{ existingPlan.metadata.cpu }} CPUs).

@@ -131,12 +134,28 @@ class="flex sm:flex-row flex-col gap-4 w-full" >
-
vCPUs
- +
Shared CPUs
+ +
+
+
+ Max Burst CPUs + +
+
Storage
- +
-
-
@@ -500,6 +520,7 @@ import { ref, computed, nextTick, reactive, watch } from 'vue' import NewModal from '../modal/NewModal.vue' import { + UnknownIcon, SpinnerIcon, CardIcon, CheckCircleIcon, @@ -765,7 +786,11 @@ function updateRamValues() { customMinRam.value = Math.min(...ramValues) customMaxRam.value = Math.max(...ramValues) - customServerConfig.ramInGb = customMinRam.value + if (props.product.some((product) => product.metadata.ram / 1024 === 4)) { + customServerConfig.ramInGb = 4 + } else { + customServerConfig.ramInGb = customMinRam.value + } } if (props.customServer) { @@ -832,6 +857,10 @@ const metadata = computed(() => { return null }) +const sharedCpus = computed(() => { + return (mutatedProduct.value?.metadata?.cpu ?? 0) / 2 +}) + function nextStep() { if ( mutatedProduct.value.metadata.type === 'pyro' && diff --git a/packages/utils/changelog.ts b/packages/utils/changelog.ts index eaf6fc353..4da01f34b 100644 --- a/packages/utils/changelog.ts +++ b/packages/utils/changelog.ts @@ -10,6 +10,12 @@ export type VersionEntry = { } const VERSIONS: VersionEntry[] = [ + { + date: `2025-04-18T22:30:00-07:00`, + product: 'web', + body: `### Improvements +- Updated Modrinth Servers marketing page to be accurate to post-Pyro infrastructure.`, + }, { date: `2025-04-17T02:25:00-07:00`, product: 'servers',