feat(marketing): miami, overallocation card, misc fixes (#2926)

* fix(marketing): make faq headings medium

* feat(marketing): add card for overallocation

* feat(marketing): add miami location

* fix(marketing): 'login' -> 'sign in' consistency

* feat: plan query string support + simplify buttons
This commit is contained in:
he3als 2024-11-08 23:52:32 +00:00 committed by GitHub
parent f5208a85b0
commit 24e90f0a54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -167,7 +167,7 @@
class="hidden w-full rounded-2xl sm:block"
/>
</div>
<div class="grid w-full grid-cols-1 gap-8 lg:grid-cols-2">
<div class="grid w-full grid-cols-1 gap-8 lg:grid-cols-3">
<div class="flex flex-col gap-4 rounded-2xl bg-bg p-6 text-left md:p-12">
<svg
xmlns="http://www.w3.org/2000/svg"
@ -218,6 +218,22 @@
custom-built software to ensure your server performs smoothly.
</h3>
</div>
<div class="relative flex flex-col gap-4 rounded-2xl bg-bg p-6 text-left md:p-12">
<ServerIcon class="size-8 text-brand" />
<h2 class="m-0 text-lg font-bold">Consistently fast</h2>
<h3 class="m-0 text-base font-normal text-secondary">
Under Pyro, infrastructure is never overloaded, meaning each Modrinth server always
runs at its full performance.
<a
class="mt-2 flex items-center gap-2 font-medium text-contrast transition-all hover:gap-3"
href="https://status.pyro.host/"
target="_blank"
>
See the infrastructure <RightArrowIcon class="flex-none" />
</a>
</h3>
</div>
</div>
</div>
</section>
@ -406,7 +422,7 @@
<h1 class="m-0 text-lg font-bold">Frequently Asked Questions</h1>
<div class="details-hide flex flex-col gap-1">
<details pyro-hash="cpus" class="group" :open="$route.hash === '#cpus'">
<summary class="flex cursor-pointer items-center py-3 font-bold text-contrast">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
</span>
@ -419,7 +435,7 @@
</details>
<details pyro-hash="ddos" class="group" :open="$route.hash === '#ddos'">
<summary class="flex cursor-pointer items-center py-3 font-bold text-contrast">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
</span>
@ -433,21 +449,21 @@
</details>
<details pyro-hash="region" class="group" :open="$route.hash === '#region'">
<summary class="flex cursor-pointer items-center py-3 font-bold text-contrast">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
</span>
Where are Modrinth Servers located? Can I choose a region?
</summary>
<p class="m-0 !leading-[190%]">
Currently, Modrinth Servers are located in New York and Los Angeles. 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 in New York, Los Angeles, and Miami. 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.
</p>
</details>
<details pyro-hash="storage" class="group" :open="$route.hash === '#storage'">
<summary class="flex cursor-pointer items-center py-3 font-bold text-contrast">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
</span>
@ -460,7 +476,7 @@
</details>
<details pyro-hash="players" class="group" :open="$route.hash === '#players'">
<summary class="flex cursor-pointer items-center py-3 font-bold text-contrast">
<summary class="flex cursor-pointer items-center py-3 font-medium text-contrast">
<span class="mr-2 transition-transform duration-200 group-open:rotate-90">
<RightArrowIcon />
</span>
@ -496,7 +512,7 @@
: "There's a plan for everyone! Choose the one that fits your needs."
}}
<span class="font-bold">
Servers are currently US only, in New York and Los Angeles. More regions coming
Servers are currently US only, in New York, Los Angeles, and Miami. More regions coming
soon!</span
>
</h2>
@ -526,33 +542,22 @@
</h2>
<ButtonStyled color="blue" size="large">
<NuxtLink
v-if="loggedOut"
:to="loginUrl"
target="_self"
v-if="!loggedOut && isSmallAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-blue !font-medium !text-blue"
>
Login
<UserIcon class="!min-h-4 !min-w-4 !text-blue" />
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-blue" />
</NuxtLink>
<template v-else>
<NuxtLink
v-if="isSmallAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-blue !font-medium !text-blue"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-blue" />
</NuxtLink>
<button
v-else
class="!bg-highlight-blue !font-medium !text-blue"
@click="selectProduct(pyroPlanProducts[0])"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-blue" />
</button>
</template>
<button
v-else
class="!bg-highlight-blue !font-medium !text-blue"
@click="selectProduct('small')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-blue" />
</button>
</ButtonStyled>
</li>
@ -589,33 +594,22 @@
</h2>
<ButtonStyled color="brand" size="large">
<NuxtLink
v-if="loggedOut"
:to="loginUrl"
target="_self"
v-if="!loggedOut && isMediumAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-green !font-medium !text-green"
>
Login
<UserIcon class="!min-h-4 !min-w-4 !text-green" />
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-green" />
</NuxtLink>
<template v-else>
<NuxtLink
v-if="isMediumAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-green !font-medium !text-green"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-green" />
</NuxtLink>
<button
v-else
class="!bg-highlight-green !font-medium !text-green"
@click="selectProduct(pyroPlanProducts[1])"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-green" />
</button>
</template>
<button
v-else
class="!bg-highlight-green !font-medium !text-green"
@click="selectProduct('medium')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-green" />
</button>
</ButtonStyled>
</li>
@ -639,35 +633,24 @@
<h2 class="m-0 text-3xl text-contrast">
$24<span class="text-sm font-normal text-secondary">/month</span>
</h2>
<ButtonStyled color="purple" size="large">
<ButtonStyled color="brand" size="large">
<NuxtLink
v-if="loggedOut"
:to="loginUrl"
target="_self"
v-if="!loggedOut && isLargeAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-purple !font-medium !text-purple"
>
Login
<UserIcon class="!min-h-4 !min-w-4 !text-purple" />
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-purple" />
</NuxtLink>
<template v-else>
<NuxtLink
v-if="isLargeAtCapacity"
:to="outOfStockUrl"
target="_blank"
class="!bg-highlight-purple !font-medium !text-purple"
>
Out of Stock
<ExternalIcon class="!min-h-4 !min-w-4 !text-purple" />
</NuxtLink>
<button
v-else
class="!bg-highlight-purple !font-medium !text-purple"
@click="selectProduct(pyroPlanProducts[2])"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-purple" />
</button>
</template>
<button
v-else
class="!bg-highlight-purple !font-medium !text-purple"
@click="selectProduct('large')"
>
Get Started
<RightArrowIcon class="!min-h-4 !min-w-4 !text-purple" />
</button>
</ButtonStyled>
</li>
</ul>
@ -685,11 +668,7 @@
<div class="flex w-full flex-col-reverse gap-2 md:w-auto md:flex-col md:items-center">
<ButtonStyled color="standard" size="large">
<NuxtLink v-if="loggedOut" :to="loginUrl" target="_self" class="w-full md:w-fit">
Login
<UserIcon class="!min-h-4 !min-w-4" />
</NuxtLink>
<button v-else class="w-full md:w-fit" @click="selectProduct(pyroProducts, true)">
<button class="w-full md:w-fit" @click="selectProduct('custom')">
Build your own
<RightArrowIcon class="!min-h-4 !min-w-4" />
</button>
@ -712,9 +691,9 @@ import {
SortAscendingIcon,
ExternalIcon,
TerminalSquareIcon,
UserIcon,
TransferIcon,
VersionIcon,
ServerIcon,
} from "@modrinth/assets";
import { products } from "~/generated/state.json";
import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue";
@ -724,11 +703,6 @@ const pyroPlanProducts = pyroProducts.filter(
(p) => p.metadata.ram === 4096 || p.metadata.ram === 6144 || p.metadata.ram === 8192,
);
pyroPlanProducts.sort((a, b) => a.metadata.ram - b.metadata.ram);
// yep. this is a thing.
if (!pyroProducts.metadata) {
pyroProducts.metadata = {};
}
pyroProducts.metadata.type = "pyro";
const title = "Modrinth Servers";
const description =
@ -772,7 +746,6 @@ const deletingSpeed = 25;
const pauseTime = 2000;
const loggedOut = computed(() => !auth.value.user);
const loginUrl = `/auth/sign-in?redirect=${encodeURIComponent("/servers#plan")}`;
const outOfStockUrl = "https://support.modrinth.com";
const { data: hasServers } = await useAsyncData("ServerListCountCheck", async () => {
@ -920,19 +893,20 @@ onMounted(scrollToFaq);
watch(() => route.hash, scrollToFaq);
const selectProduct = async (product, custom) => {
if (isAtCapacity.value) {
addNotification({
group: "main",
title: "Server Capacity Full",
type: "error",
text: "We are currently at capacity. Please try again later.",
});
const plans = {
small: pyroPlanProducts?.[0],
medium: pyroPlanProducts?.[1],
large: pyroPlanProducts?.[2],
custom: pyroProducts || [],
};
const selectProduct = async (product) => {
if (loggedOut.value) {
data.$router.push(`/auth/sign-in?redirect=${encodeURIComponent("/servers?plan=" + product)}`);
return;
}
await refreshCapacity();
if (isAtCapacity.value) {
addNotification({
group: "main",
@ -943,68 +917,53 @@ const selectProduct = async (product, custom) => {
return;
}
if (!auth.value.user) {
data.$router.push(loginUrl);
const selectedPlan = plans[product];
if (!selectedPlan) return;
if (
(product === "custom" && !selectedPlan.length) ||
(product !== "custom" && !selectedPlan.metadata)
) {
addNotification({
group: "main",
title: "Invalid product",
type: "error",
text: "The selected product was found but lacks necessary data. Please contact support.",
});
return;
}
customServer.value = !!custom;
selectedProduct.value = product;
// required for the purchase modal
if (!pyroProducts.metadata) {
pyroProducts.metadata = {};
}
pyroProducts.metadata.type = "pyro";
customServer.value = product === "custom";
selectedProduct.value = selectedPlan;
showModal.value = true;
modalKey.value++;
await nextTick();
if (purchaseModal.value && purchaseModal.value.show) {
purchaseModal.value.show();
}
};
const openPurchaseModal = () => {
if (isAtCapacity.value) {
addNotification({
group: "main",
title: "Server Capacity Full",
type: "error",
text: "We are currently at capacity. Please try again later.",
});
return;
const planQuery = () => {
if (route.query.plan) {
document.getElementById("plan").scrollIntoView();
selectProduct(route.query.plan);
}
refreshCapacity();
if (isAtCapacity.value) {
addNotification({
group: "main",
title: "Server Capacity Full",
type: "error",
text: "We are currently at capacity. Please try again later.",
});
return;
}
customServer.value = false;
selectedProduct.value = pyroPlanProducts[0];
showModal.value = true;
modalKey.value++;
nextTick(() => {
if (purchaseModal.value && purchaseModal.value.show) {
purchaseModal.value.show();
}
});
};
onMounted(() => {
startTyping();
if (route.query.showModal) {
openPurchaseModal();
}
planQuery();
});
watch(customer, (newCustomer) => {
if (newCustomer) {
if (route.query.showModal) {
openPurchaseModal();
}
}
if (newCustomer) planQuery();
});
onMounted(() => {