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