Servers marketing enhancements (#3252)
* feat: locations page + stock callouts * feat: misalligned but spirits there!! * fix readability on colors on globe * Enhancements to globe * Fix out of stock indicator styling * Start globe near US and slow speed * Remove debug statement * Switch from capacity to stock API * Make custom use its own stock checker * Fix lint, add changelog entries --------- Co-authored-by: Elizabeth <checksum@pyro.host> Co-authored-by: Lio <git@lio.cat>
This commit is contained in:
parent
098519dea1
commit
6d810a421a
@ -57,6 +57,8 @@
|
||||
"pinia": "^2.1.7",
|
||||
"qrcode.vue": "^3.4.0",
|
||||
"semver": "^7.5.4",
|
||||
"three": "^0.172.0",
|
||||
"@types/three": "^0.172.0",
|
||||
"vue-multiselect": "3.0.0-alpha.2",
|
||||
"vue-typed-virtual-list": "^1.0.10",
|
||||
"vue3-ace-editor": "^2.2.4",
|
||||
|
308
apps/frontend/src/components/ui/servers/Globe.vue
Normal file
308
apps/frontend/src/components/ui/servers/Globe.vue
Normal file
@ -0,0 +1,308 @@
|
||||
<template>
|
||||
<div ref="container" class="relative h-[400px] w-full cursor-move lg:h-[600px]">
|
||||
<div
|
||||
v-for="location in locations"
|
||||
:key="location.name"
|
||||
:class="{
|
||||
'opacity-0': !showLabels,
|
||||
hidden: !isLocationVisible(location),
|
||||
'z-40': location.clicked,
|
||||
}"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: `${location.screenPosition?.x || 0}px`,
|
||||
top: `${location.screenPosition?.y || 0}px`,
|
||||
}"
|
||||
class="location-button center-on-top-left flex transform cursor-pointer items-center rounded-full bg-bg px-3 outline-1 outline-red transition-opacity duration-200 hover:z-50"
|
||||
@click="toggleLocationClicked(location)"
|
||||
>
|
||||
<div
|
||||
:class="{
|
||||
'animate-pulse': location.active,
|
||||
'border-gray-400': !location.active,
|
||||
'border-purple bg-purple': location.active,
|
||||
'border-dashed': !location.active,
|
||||
'opacity-40': !location.active,
|
||||
}"
|
||||
class="my-3 size-2.5 shrink-0 rounded-full border-2"
|
||||
></div>
|
||||
<div
|
||||
class="expanding-item"
|
||||
:class="{
|
||||
expanded: location.clicked,
|
||||
}"
|
||||
>
|
||||
<div class="whitespace-nowrap text-sm">
|
||||
<span class="ml-2"> {{ location.name }} </span>
|
||||
<span v-if="!location.active" class="ml-1 text-xs text-secondary">(Coming Soon)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
|
||||
const container = ref(null);
|
||||
const showLabels = ref(false);
|
||||
|
||||
const locations = ref([
|
||||
// Active locations
|
||||
{ name: "New York", lat: 40.7128, lng: -74.006, active: true, clicked: false },
|
||||
{ name: "Los Angeles", lat: 34.0522, lng: -118.2437, active: true, clicked: false },
|
||||
{ name: "Miami", lat: 25.7617, lng: -80.1918, active: true, clicked: false },
|
||||
{ name: "Seattle", lat: 47.608013, lng: -122.3321, active: true, clicked: false },
|
||||
// Future Locations
|
||||
// { name: "London", lat: 51.5074, lng: -0.1278, active: false, clicked: false },
|
||||
// { name: "Frankfurt", lat: 50.1109, lng: 8.6821, active: false, clicked: false },
|
||||
// { name: "Amsterdam", lat: 52.3676, lng: 4.9041, active: false, clicked: false },
|
||||
// { name: "Paris", lat: 48.8566, lng: 2.3522, active: false, clicked: false },
|
||||
// { name: "Singapore", lat: 1.3521, lng: 103.8198, active: false, clicked: false },
|
||||
// { name: "Tokyo", lat: 35.6762, lng: 139.6503, active: false, clicked: false },
|
||||
// { name: "Sydney", lat: -33.8688, lng: 151.2093, active: false, clicked: false },
|
||||
// { name: "São Paulo", lat: -23.5505, lng: -46.6333, active: false, clicked: false },
|
||||
// { name: "Toronto", lat: 43.6532, lng: -79.3832, active: false, clicked: false },
|
||||
]);
|
||||
|
||||
const isLocationVisible = (location) => {
|
||||
if (!location.screenPosition || !globe) return false;
|
||||
|
||||
const vector = latLngToVector3(location.lat, location.lng).clone();
|
||||
vector.applyMatrix4(globe.matrixWorld);
|
||||
|
||||
const cameraVector = new THREE.Vector3();
|
||||
camera.getWorldPosition(cameraVector);
|
||||
|
||||
const viewVector = vector.clone().sub(cameraVector).normalize();
|
||||
|
||||
const normal = vector.clone().normalize();
|
||||
|
||||
const dotProduct = normal.dot(viewVector);
|
||||
|
||||
return dotProduct < -0.15;
|
||||
};
|
||||
|
||||
const toggleLocationClicked = (location) => {
|
||||
console.log("clicked", location.name);
|
||||
locations.value.find((loc) => loc.name === location.name).clicked = !location.clicked;
|
||||
};
|
||||
|
||||
let scene, camera, renderer, globe, controls;
|
||||
let animationFrame;
|
||||
|
||||
const init = () => {
|
||||
scene = new THREE.Scene();
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
45,
|
||||
container.value.clientWidth / container.value.clientHeight,
|
||||
0.1,
|
||||
1000,
|
||||
);
|
||||
renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true,
|
||||
powerPreference: "low-power",
|
||||
});
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
renderer.setSize(container.value.clientWidth, container.value.clientHeight);
|
||||
container.value.appendChild(renderer.domElement);
|
||||
|
||||
const geometry = new THREE.SphereGeometry(5, 64, 64);
|
||||
const outlineTexture = new THREE.TextureLoader().load("/earth-outline.png");
|
||||
outlineTexture.minFilter = THREE.LinearFilter;
|
||||
outlineTexture.magFilter = THREE.LinearFilter;
|
||||
|
||||
const material = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
outlineTexture: { value: outlineTexture },
|
||||
globeColor: { value: new THREE.Color("#60fbb5") },
|
||||
},
|
||||
vertexShader: `
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform sampler2D outlineTexture;
|
||||
uniform vec3 globeColor;
|
||||
varying vec2 vUv;
|
||||
void main() {
|
||||
vec4 texColor = texture2D(outlineTexture, vUv);
|
||||
|
||||
float brightness = max(max(texColor.r, texColor.g), texColor.b);
|
||||
gl_FragColor = vec4(globeColor, brightness * 0.8);
|
||||
}
|
||||
`,
|
||||
transparent: true,
|
||||
side: THREE.FrontSide,
|
||||
});
|
||||
|
||||
globe = new THREE.Mesh(geometry, material);
|
||||
scene.add(globe);
|
||||
|
||||
const atmosphereGeometry = new THREE.SphereGeometry(5.2, 64, 64);
|
||||
const atmosphereMaterial = new THREE.ShaderMaterial({
|
||||
transparent: true,
|
||||
side: THREE.BackSide,
|
||||
uniforms: {
|
||||
color: { value: new THREE.Color("#56f690") },
|
||||
viewVector: { value: camera.position },
|
||||
},
|
||||
vertexShader: `
|
||||
uniform vec3 viewVector;
|
||||
varying float intensity;
|
||||
void main() {
|
||||
vec3 vNormal = normalize(normalMatrix * normal);
|
||||
vec3 vNormel = normalize(normalMatrix * viewVector);
|
||||
intensity = pow(0.7 - dot(vNormal, vNormel), 2.0);
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform vec3 color;
|
||||
varying float intensity;
|
||||
void main() {
|
||||
gl_FragColor = vec4(color, intensity * 0.4);
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
|
||||
scene.add(atmosphere);
|
||||
|
||||
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
|
||||
scene.add(ambientLight);
|
||||
|
||||
camera.position.z = 15;
|
||||
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
controls.dampingFactor = 0.05;
|
||||
controls.rotateSpeed = 0.3;
|
||||
controls.enableZoom = false;
|
||||
controls.enablePan = false;
|
||||
controls.autoRotate = true;
|
||||
controls.autoRotateSpeed = 0.05;
|
||||
controls.minPolarAngle = Math.PI * 0.3;
|
||||
controls.maxPolarAngle = Math.PI * 0.7;
|
||||
|
||||
globe.rotation.y = Math.PI * 1.9;
|
||||
globe.rotation.x = Math.PI * 0.15;
|
||||
};
|
||||
|
||||
const animate = () => {
|
||||
animationFrame = requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
|
||||
locations.value.forEach((location) => {
|
||||
const position = latLngToVector3(location.lat, location.lng);
|
||||
const vector = position.clone();
|
||||
vector.applyMatrix4(globe.matrixWorld);
|
||||
|
||||
const coords = vector.project(camera);
|
||||
const screenPosition = {
|
||||
x: (coords.x * 0.5 + 0.5) * container.value.clientWidth,
|
||||
y: (-coords.y * 0.5 + 0.5) * container.value.clientHeight,
|
||||
};
|
||||
location.screenPosition = screenPosition;
|
||||
});
|
||||
|
||||
renderer.render(scene, camera);
|
||||
};
|
||||
|
||||
const latLngToVector3 = (lat, lng) => {
|
||||
const phi = (90 - lat) * (Math.PI / 180);
|
||||
const theta = (lng + 180) * (Math.PI / 180);
|
||||
const radius = 5;
|
||||
|
||||
return new THREE.Vector3(
|
||||
-radius * Math.sin(phi) * Math.cos(theta),
|
||||
radius * Math.cos(phi),
|
||||
radius * Math.sin(phi) * Math.sin(theta),
|
||||
);
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
if (!container.value) return;
|
||||
camera.aspect = container.value.clientWidth / container.value.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(container.value.clientWidth, container.value.clientHeight);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
animate();
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
setTimeout(() => {
|
||||
showLabels.value = true;
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (animationFrame) {
|
||||
cancelAnimationFrame(animationFrame);
|
||||
}
|
||||
window.removeEventListener("resize", handleResize);
|
||||
if (renderer) {
|
||||
renderer.dispose();
|
||||
}
|
||||
if (container.value) {
|
||||
container.value.innerHTML = "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(27, 217, 106, 0.3);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 4px rgba(27, 217, 106, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(27, 217, 106, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
.center-on-top-left {
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.expanding-item.expanded {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.location-button:hover .expanding-item {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.expanding-item {
|
||||
display: grid;
|
||||
grid-template-columns: 0fr;
|
||||
transition: grid-template-columns 0.15s ease-in-out;
|
||||
overflow: hidden;
|
||||
|
||||
> div {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.expanding-item {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -494,6 +494,97 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="relative mt-24 flex flex-col bg-[radial-gradient(65%_50%_at_50%_-10%,var(--color-brand-highlight)_0%,var(--color-accent-contrast)_100%)] px-3 pt-24 md:mt-48 md:pt-48"
|
||||
>
|
||||
<div class="faded-brand-line absolute left-0 top-0 h-[1px] w-full"></div>
|
||||
<div class="mx-auto flex w-full max-w-7xl flex-col gap-8">
|
||||
<div class="grid grid-cols-1 items-center gap-12 lg:grid-cols-2">
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
class="relative w-fit rounded-full bg-highlight-green px-3 py-1 text-sm font-bold text-brand backdrop-blur-lg"
|
||||
>
|
||||
Server Locations
|
||||
</div>
|
||||
<h1 class="relative m-0 max-w-2xl text-4xl leading-[120%] md:text-7xl">
|
||||
Coast-to-Coast Coverage
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-8">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="grid size-8 place-content-center rounded-full bg-highlight-green">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="text-brand"
|
||||
>
|
||||
<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z" />
|
||||
<circle cx="12" cy="10" r="3" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="relative m-0 text-xl font-medium leading-[155%] md:text-2xl">
|
||||
US Coverage
|
||||
</h2>
|
||||
</div>
|
||||
<p
|
||||
class="relative m-0 max-w-xl text-base font-normal leading-[155%] text-secondary md:text-[18px]"
|
||||
>
|
||||
With strategically placed servers in New York, Los Angeles, Seattle, and Miami, we
|
||||
ensure low latency connections for players across North America. Each location is
|
||||
equipped with high-performance hardware and DDoS protection.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="grid size-8 place-content-center rounded-full bg-highlight-blue">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="text-blue"
|
||||
>
|
||||
<path d="M12 2a10 10 0 1 0 10 10" />
|
||||
<path d="M18 13a6 6 0 0 0-6-6" />
|
||||
<path d="M13 2.05a10 10 0 0 1 2 2" />
|
||||
<path d="M19.5 8.5a10 10 0 0 1 2 2" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="relative m-0 text-xl font-medium leading-[155%] md:text-2xl">
|
||||
Global Expansion
|
||||
</h2>
|
||||
</div>
|
||||
<p
|
||||
class="relative m-0 max-w-xl text-base font-normal leading-[155%] text-secondary md:text-[18px]"
|
||||
>
|
||||
We're expanding to Europe and Asia-Pacific regions soon, bringing Modrinth's
|
||||
seamless hosting experience worldwide. Join our Discord to stay updated on new
|
||||
region launches.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Globe />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="plan"
|
||||
class="relative mt-24 flex flex-col bg-[radial-gradient(65%_50%_at_50%_-10%,var(--color-brand-highlight)_0%,var(--color-accent-contrast)_100%)] px-3 pt-24 md:mt-48 md:pt-48"
|
||||
@ -511,147 +602,180 @@
|
||||
? "We are currently at capacity. Please try again later."
|
||||
: "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, Los Angeles, Seattle, and Miami. More
|
||||
regions coming soon!
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<ul class="m-0 flex w-full flex-col gap-8 p-0 lg:flex-row">
|
||||
<li class="flex w-full flex-col gap-4 rounded-2xl bg-bg p-8 text-left lg:w-1/3">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h1 class="m-0">Small</h1>
|
||||
<div
|
||||
class="grid size-8 place-content-center rounded-full bg-highlight-blue text-xs font-bold text-blue"
|
||||
>
|
||||
S
|
||||
<ul class="m-0 mt-8 flex w-full flex-col gap-8 p-0 lg:flex-row">
|
||||
<li class="relative flex w-full flex-col justify-between pt-12 lg:w-1/3">
|
||||
<div
|
||||
v-if="isSmallLowStock"
|
||||
class="absolute left-0 right-0 top-[-2px] rounded-t-2xl bg-yellow-500/20 p-4 text-center font-bold"
|
||||
>
|
||||
Only {{ capacityStatuses?.small?.available }} left in stock!
|
||||
</div>
|
||||
<div
|
||||
class="flex w-full flex-col justify-between gap-4 rounded-2xl bg-bg p-8 text-left"
|
||||
:class="{ '!rounded-t-none': isSmallLowStock }"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h1 class="m-0">Small</h1>
|
||||
<div
|
||||
class="grid size-8 place-content-center rounded-full bg-highlight-blue text-xs font-bold text-blue"
|
||||
>
|
||||
S
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0">
|
||||
Perfect for vanilla multiplayer, small friend groups, SMPs, and light modding.
|
||||
</p>
|
||||
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
|
||||
<p class="m-0">4 GB RAM</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">4 vCPUs</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">32 GB Storage</p>
|
||||
</div>
|
||||
<h2 class="m-0 text-3xl text-contrast">
|
||||
$12<span class="text-sm font-normal text-secondary">/month</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ButtonStyled color="blue" size="large">
|
||||
<NuxtLink
|
||||
v-if="!loggedOut && isSmallAtCapacity"
|
||||
:to="outOfStockUrl"
|
||||
target="_blank"
|
||||
class="flex items-center gap-2 !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('small')"
|
||||
>
|
||||
Get Started
|
||||
<RightArrowIcon class="!min-h-4 !min-w-4 !text-blue" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p class="m-0">
|
||||
Perfect for vanilla multiplayer, small friend groups, SMPs, and light modding.
|
||||
</p>
|
||||
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
|
||||
<p class="m-0">4 GB RAM</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">4 vCPUs</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">32 GB Storage</p>
|
||||
</div>
|
||||
<h2 class="m-0 text-3xl text-contrast">
|
||||
$12<span class="text-sm font-normal text-secondary">/month</span>
|
||||
</h2>
|
||||
<ButtonStyled color="blue" size="large">
|
||||
<NuxtLink
|
||||
v-if="!loggedOut && 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('small')"
|
||||
>
|
||||
Get Started
|
||||
<RightArrowIcon class="!min-h-4 !min-w-4 !text-blue" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</li>
|
||||
|
||||
<li
|
||||
style="
|
||||
background: radial-gradient(
|
||||
86.12% 101.64% at 95.97% 94.07%,
|
||||
rgba(27, 217, 106, 0.23) 0%,
|
||||
rgba(14, 115, 56, 0.2) 100%
|
||||
);
|
||||
border: 1px solid rgba(12, 107, 52, 0.55);
|
||||
box-shadow: 0px 12px 38.1px rgba(27, 217, 106, 0.13);
|
||||
"
|
||||
class="flex w-full flex-col gap-4 rounded-2xl bg-bg p-8 text-left lg:w-1/3"
|
||||
>
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h1 class="m-0">Medium</h1>
|
||||
<div
|
||||
class="grid size-8 place-content-center rounded-full bg-highlight-green text-xs font-bold text-brand"
|
||||
>
|
||||
M
|
||||
<li class="relative flex w-full flex-col justify-between pt-12 lg:w-1/3">
|
||||
<div
|
||||
v-if="isMediumLowStock"
|
||||
class="absolute left-0 right-0 top-[-2px] rounded-t-2xl bg-yellow-500/20 p-4 text-center font-bold"
|
||||
>
|
||||
Only {{ capacityStatuses?.medium?.available }} left in stock!
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
background: radial-gradient(
|
||||
86.12% 101.64% at 95.97% 94.07%,
|
||||
rgba(27, 217, 106, 0.23) 0%,
|
||||
rgba(14, 115, 56, 0.2) 100%
|
||||
);
|
||||
border: 1px solid rgba(12, 107, 52, 0.55);
|
||||
box-shadow: 0px 12px 38.1px rgba(27, 217, 106, 0.13);
|
||||
"
|
||||
class="flex w-full flex-col justify-between gap-4 rounded-2xl p-8 text-left"
|
||||
:class="{ '!rounded-t-none': isMediumLowStock }"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h1 class="m-0">Medium</h1>
|
||||
<div
|
||||
class="grid size-8 place-content-center rounded-full bg-highlight-green text-xs font-bold text-brand"
|
||||
>
|
||||
M
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0">Great for modded multiplayer and small communities.</p>
|
||||
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
|
||||
<p class="m-0">6 GB RAM</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">6 vCPUs</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">48 GB Storage</p>
|
||||
</div>
|
||||
<h2 class="m-0 text-3xl text-contrast">
|
||||
$18<span class="text-sm font-normal text-secondary">/month</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<NuxtLink
|
||||
v-if="!loggedOut && isMediumAtCapacity"
|
||||
:to="outOfStockUrl"
|
||||
target="_blank"
|
||||
class="flex items-center gap-2 !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('medium')"
|
||||
>
|
||||
Get Started
|
||||
<RightArrowIcon class="!min-h-4 !min-w-4 !text-green" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p class="m-0">Great for modded multiplayer and small communities.</p>
|
||||
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
|
||||
<p class="m-0">6 GB RAM</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">6 vCPUs</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">48 GB Storage</p>
|
||||
</div>
|
||||
<h2 class="m-0 text-3xl text-contrast">
|
||||
$18<span class="text-sm font-normal text-secondary">/month</span>
|
||||
</h2>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<NuxtLink
|
||||
v-if="!loggedOut && 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('medium')"
|
||||
>
|
||||
Get Started
|
||||
<RightArrowIcon class="!min-h-4 !min-w-4 !text-green" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</li>
|
||||
|
||||
<li class="flex w-full flex-col gap-4 rounded-2xl bg-bg p-8 text-left lg:w-1/3">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h1 class="m-0">Large</h1>
|
||||
<div
|
||||
class="grid size-8 place-content-center rounded-full bg-highlight-purple text-xs font-bold text-purple"
|
||||
>
|
||||
L
|
||||
<li class="relative flex w-full flex-col justify-between pt-12 lg:w-1/3">
|
||||
<div
|
||||
v-if="isLargeLowStock"
|
||||
class="absolute left-0 right-0 top-[-2px] rounded-t-2xl bg-yellow-500/20 p-4 text-center font-bold"
|
||||
>
|
||||
Only {{ capacityStatuses?.large?.available }} left in stock!
|
||||
</div>
|
||||
<div
|
||||
class="flex w-full flex-col justify-between gap-4 rounded-2xl bg-bg p-8 text-left"
|
||||
:class="{ '!rounded-t-none': isLargeLowStock }"
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<h1 class="m-0">Large</h1>
|
||||
<div
|
||||
class="grid size-8 place-content-center rounded-full bg-highlight-purple text-xs font-bold text-purple"
|
||||
>
|
||||
L
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0">Ideal for larger communities, modpacks, and heavy modding.</p>
|
||||
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
|
||||
<p class="m-0">8 GB RAM</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">8 vCPUs</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">64 GB Storage</p>
|
||||
</div>
|
||||
<h2 class="m-0 text-3xl text-contrast">
|
||||
$24<span class="text-sm font-normal text-secondary">/month</span>
|
||||
</h2>
|
||||
</div>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<NuxtLink
|
||||
v-if="!loggedOut && isLargeAtCapacity"
|
||||
:to="outOfStockUrl"
|
||||
target="_blank"
|
||||
class="flex items-center gap-2 !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('large')"
|
||||
>
|
||||
Get Started
|
||||
<RightArrowIcon class="!min-h-4 !min-w-4 !text-purple" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p class="m-0">Ideal for larger communities, modpacks, and heavy modding.</p>
|
||||
<div class="flex flex-row flex-wrap items-center gap-3 text-nowrap">
|
||||
<p class="m-0">8 GB RAM</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">8 vCPUs</p>
|
||||
<div class="size-1.5 rounded-full bg-secondary opacity-25"></div>
|
||||
<p class="m-0">64 GB Storage</p>
|
||||
</div>
|
||||
<h2 class="m-0 text-3xl text-contrast">
|
||||
$24<span class="text-sm font-normal text-secondary">/month</span>
|
||||
</h2>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<NuxtLink
|
||||
v-if="!loggedOut && 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('large')"
|
||||
>
|
||||
Get Started
|
||||
<RightArrowIcon class="!min-h-4 !min-w-4 !text-purple" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@ -697,6 +821,7 @@ import {
|
||||
} from "@modrinth/assets";
|
||||
import { products } from "~/generated/state.json";
|
||||
import LoaderIcon from "~/components/ui/servers/icons/LoaderIcon.vue";
|
||||
import Globe from "~/components/ui/servers/Globe.vue";
|
||||
|
||||
const pyroProducts = products.filter((p) => p.metadata.type === "pyro");
|
||||
const pyroPlanProducts = pyroProducts.filter(
|
||||
@ -760,9 +885,16 @@ const { data: hasServers } = await useAsyncData("ServerListCountCheck", async ()
|
||||
|
||||
async function fetchCapacityStatuses(customProduct = null) {
|
||||
try {
|
||||
const productsToCheck = customProduct?.metadata ? [customProduct] : pyroPlanProducts;
|
||||
const productsToCheck = customProduct?.metadata
|
||||
? [customProduct]
|
||||
: [
|
||||
...pyroPlanProducts,
|
||||
pyroProducts.reduce((min, product) =>
|
||||
product.metadata.ram < min.metadata.ram ? product : min,
|
||||
),
|
||||
];
|
||||
const capacityChecks = productsToCheck.map((product) =>
|
||||
usePyroFetch("capacity", {
|
||||
usePyroFetch("stock", {
|
||||
method: "POST",
|
||||
body: {
|
||||
cpu: product.metadata.cpu,
|
||||
@ -774,6 +906,7 @@ async function fetchCapacityStatuses(customProduct = null) {
|
||||
);
|
||||
|
||||
const results = await Promise.all(capacityChecks);
|
||||
|
||||
if (customProduct?.metadata) {
|
||||
return {
|
||||
custom: results[0],
|
||||
@ -783,6 +916,7 @@ async function fetchCapacityStatuses(customProduct = null) {
|
||||
small: results[0],
|
||||
medium: results[1],
|
||||
large: results[2],
|
||||
custom: results[3],
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
@ -804,6 +938,22 @@ const { data: capacityStatuses, refresh: refreshCapacity } = await useAsyncData(
|
||||
const isSmallAtCapacity = computed(() => capacityStatuses.value?.small?.available === 0);
|
||||
const isMediumAtCapacity = computed(() => capacityStatuses.value?.medium?.available === 0);
|
||||
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];
|
||||
@ -907,7 +1057,9 @@ const selectProduct = async (product) => {
|
||||
}
|
||||
|
||||
await refreshCapacity();
|
||||
if (isAtCapacity.value) {
|
||||
console.log(capacityStatuses.value);
|
||||
|
||||
if ((product === "custom" && isCustomAtCapacity.value) || isAtCapacity.value) {
|
||||
addNotification({
|
||||
group: "main",
|
||||
title: "Server Capacity Full",
|
||||
|
BIN
apps/frontend/src/public/earth-outline.png
Normal file
BIN
apps/frontend/src/public/earth-outline.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 MiB |
@ -6,10 +6,12 @@
|
||||
"ui:intl:extract": "pnpm run --filter=@modrinth/ui intl:extract",
|
||||
"web:dev": "turbo run dev --filter=@modrinth/frontend",
|
||||
"web:build": "turbo run build --filter=@modrinth/frontend",
|
||||
"web:fix": "turbo run fix --filter=@modrinth/frontend",
|
||||
"web:intl:extract": "pnpm run --filter=@modrinth/frontend intl:extract",
|
||||
"app:dev": "turbo run dev --filter=@modrinth/app",
|
||||
"docs:dev": "turbo run dev --filter=@modrinth/docs",
|
||||
"app:build": "turbo run build --filter=@modrinth/app",
|
||||
"app:fix": "turbo run fix --filter=@modrinth/app",
|
||||
"app:intl:extract": "pnpm run --filter=@modrinth/app-frontend intl:extract",
|
||||
"pages:build": "NITRO_PRESET=cloudflare-pages pnpm --filter frontend run build",
|
||||
"build": "turbo run build --continue",
|
||||
|
@ -18,11 +18,16 @@
|
||||
<div class="w-2 h-2 rounded-full bg-secondary" />
|
||||
</template>
|
||||
<span :class="{ 'text-primary font-bold': showType }">
|
||||
{{ entry.version ?? formattedDate }}
|
||||
{{ versionName }}
|
||||
</span>
|
||||
</h2>
|
||||
</AutoLink>
|
||||
<div v-if="recent" v-tooltip="dateTooltip" class="hidden sm:flex" :class="{ 'cursor-help': dateTooltip }">
|
||||
<div
|
||||
v-if="recent"
|
||||
v-tooltip="dateTooltip"
|
||||
class="hidden sm:flex"
|
||||
:class="{ 'cursor-help': dateTooltip }"
|
||||
>
|
||||
{{ relativeDate }}
|
||||
</div>
|
||||
<div v-else-if="entry.version" :class="{ 'cursor-help': dateTooltip }">
|
||||
@ -61,11 +66,12 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const currentDate = ref(dayjs())
|
||||
const recent = computed(() => props.entry.date.isAfter(currentDate.value.subtract(1, 'week')))
|
||||
const dateTooltip = computed(() => props.entry.date.format('MMMM D, YYYY [at] h:mm A'))
|
||||
const formattedDate = computed(() =>
|
||||
props.entry.version ? props.entry.date.fromNow() : props.entry.date.format('MMMM D, YYYY'),
|
||||
const recent = computed(
|
||||
() =>
|
||||
props.entry.date.isAfter(currentDate.value.subtract(1, 'week')) &&
|
||||
props.entry.date.isBefore(currentDate.value),
|
||||
)
|
||||
const dateTooltip = computed(() => props.entry.date.format('MMMM D, YYYY [at] h:mm A'))
|
||||
|
||||
const relativeDate = computed(() => props.entry.date.fromNow())
|
||||
const longDate = computed(() => props.entry.date.format('MMMM D, YYYY'))
|
||||
|
@ -10,6 +10,16 @@ export type VersionEntry = {
|
||||
}
|
||||
|
||||
const VERSIONS: VersionEntry[] = [
|
||||
{
|
||||
date: `2025-02-12T12:10:00-08:00`,
|
||||
product: 'web',
|
||||
body: `### Added
|
||||
- Added a 3D globe to visualize node locations to Modrinth Servers marketing page.
|
||||
- Added an indicator to show when certain server plans are running low on availability.
|
||||
|
||||
### Improvements
|
||||
- Improved out-of-stock notifications on Modrinth Servers page to be more accurate.`,
|
||||
},
|
||||
{
|
||||
date: `2025-02-11T13:00:00-08:00`,
|
||||
product: 'web',
|
||||
|
67
pnpm-lock.yaml
generated
67
pnpm-lock.yaml
generated
@ -209,6 +209,9 @@ importers:
|
||||
'@pinia/nuxt':
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1(magicast@0.3.5)(rollup@4.28.1)(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4))
|
||||
'@types/three':
|
||||
specifier: ^0.172.0
|
||||
version: 0.172.0
|
||||
'@vintl/vintl':
|
||||
specifier: ^4.4.1
|
||||
version: 4.4.1(typescript@5.5.4)(vue@3.5.13(typescript@5.5.4))
|
||||
@ -260,6 +263,9 @@ importers:
|
||||
semver:
|
||||
specifier: ^7.5.4
|
||||
version: 7.6.3
|
||||
three:
|
||||
specifier: ^0.172.0
|
||||
version: 0.172.0
|
||||
vue-multiselect:
|
||||
specifier: 3.0.0-alpha.2
|
||||
version: 3.0.0-alpha.2
|
||||
@ -2146,6 +2152,9 @@ packages:
|
||||
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
'@tweenjs/tween.js@23.1.3':
|
||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||
|
||||
'@types/acorn@4.0.6':
|
||||
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
||||
|
||||
@ -2242,6 +2251,12 @@ packages:
|
||||
'@types/semver@7.5.8':
|
||||
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
|
||||
|
||||
'@types/stats.js@0.17.3':
|
||||
resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==}
|
||||
|
||||
'@types/three@0.172.0':
|
||||
resolution: {integrity: sha512-LrUtP3FEG26Zg5WiF0nbg8VoXiKokBLTcqM2iLvM9vzcfEiYmmBAPGdBgV0OYx9fvWlY3R/3ERTZcD9X5sc0NA==}
|
||||
|
||||
'@types/trusted-types@2.0.7':
|
||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||
|
||||
@ -2257,6 +2272,9 @@ packages:
|
||||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
||||
'@types/webxr@0.5.21':
|
||||
resolution: {integrity: sha512-geZIAtLzjGmgY2JUi6VxXdCrTb99A7yP49lxLr2Nm/uIK0PkkxcEi4OGhoGDO4pxCf3JwGz2GiJL2Ej4K2bKaA==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@6.21.0':
|
||||
resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
|
||||
engines: {node: ^16.0.0 || >=18.0.0}
|
||||
@ -2708,6 +2726,9 @@ packages:
|
||||
'@webassemblyjs/wast-printer@1.12.1':
|
||||
resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==}
|
||||
|
||||
'@webgpu/types@0.1.54':
|
||||
resolution: {integrity: sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg==}
|
||||
|
||||
'@xtuc/ieee754@1.2.0':
|
||||
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
||||
|
||||
@ -4026,6 +4047,9 @@ packages:
|
||||
fflate@0.4.8:
|
||||
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
|
||||
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
|
||||
file-entry-cache@6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
@ -5008,6 +5032,9 @@ packages:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
meshoptimizer@0.18.1:
|
||||
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
|
||||
|
||||
micromark-core-commonmark@2.0.1:
|
||||
resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==}
|
||||
|
||||
@ -6613,6 +6640,9 @@ packages:
|
||||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
three@0.172.0:
|
||||
resolution: {integrity: sha512-6HMgMlzU97MsV7D/tY8Va38b83kz8YJX+BefKjspMNAv0Vx6dxMogHOrnRl/sbMIs3BPUKijPqDqJ/+UwJbIow==}
|
||||
|
||||
tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
|
||||
@ -8913,7 +8943,7 @@ snapshots:
|
||||
|
||||
'@nuxtjs/eslint-config-typescript@12.1.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)':
|
||||
dependencies:
|
||||
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
|
||||
'@nuxtjs/eslint-config': 12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
@ -8926,10 +8956,10 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))':
|
||||
'@nuxtjs/eslint-config@12.0.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))':
|
||||
dependencies:
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-config-standard: 17.1.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-node: 11.1.0(eslint@9.13.0(jiti@2.4.1))
|
||||
@ -9369,6 +9399,8 @@ snapshots:
|
||||
|
||||
'@trysound/sax@0.2.0': {}
|
||||
|
||||
'@tweenjs/tween.js@23.1.3': {}
|
||||
|
||||
'@types/acorn@4.0.6':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
@ -9482,6 +9514,17 @@ snapshots:
|
||||
|
||||
'@types/semver@7.5.8': {}
|
||||
|
||||
'@types/stats.js@0.17.3': {}
|
||||
|
||||
'@types/three@0.172.0':
|
||||
dependencies:
|
||||
'@tweenjs/tween.js': 23.1.3
|
||||
'@types/stats.js': 0.17.3
|
||||
'@types/webxr': 0.5.21
|
||||
'@webgpu/types': 0.1.54
|
||||
fflate: 0.8.2
|
||||
meshoptimizer: 0.18.1
|
||||
|
||||
'@types/trusted-types@2.0.7': {}
|
||||
|
||||
'@types/unist@2.0.11': {}
|
||||
@ -9492,6 +9535,8 @@ snapshots:
|
||||
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
|
||||
'@types/webxr@0.5.21': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.11.0
|
||||
@ -10260,6 +10305,8 @@ snapshots:
|
||||
'@xtuc/long': 4.2.2
|
||||
optional: true
|
||||
|
||||
'@webgpu/types@0.1.54': {}
|
||||
|
||||
'@xtuc/ieee754@1.2.0':
|
||||
optional: true
|
||||
|
||||
@ -11376,10 +11423,10 @@ snapshots:
|
||||
dependencies:
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
|
||||
eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
|
||||
eslint-config-standard@17.1.0(eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-n@15.7.0(eslint@9.13.0(jiti@2.4.1)))(eslint-plugin-promise@6.4.0(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
|
||||
dependencies:
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-n: 15.7.0(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-promise: 6.4.0(eslint@9.13.0(jiti@2.4.1))
|
||||
|
||||
@ -11405,7 +11452,7 @@ snapshots:
|
||||
debug: 4.4.0(supports-color@9.4.0)
|
||||
enhanced-resolve: 5.17.1
|
||||
eslint: 9.13.0(jiti@2.4.1)
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1))
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.16.1(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint@9.13.0(jiti@2.4.1))
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.7.5
|
||||
@ -11417,7 +11464,7 @@ snapshots:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@9.13.0(jiti@2.4.1)):
|
||||
eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@9.13.0(jiti@2.4.1))(typescript@5.5.4))(eslint-plugin-import@2.29.1)(eslint@9.13.0(jiti@2.4.1)))(eslint@9.13.0(jiti@2.4.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@ -11932,6 +11979,8 @@ snapshots:
|
||||
|
||||
fflate@0.4.8: {}
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
file-entry-cache@6.0.1:
|
||||
dependencies:
|
||||
flat-cache: 3.2.0
|
||||
@ -13156,6 +13205,8 @@ snapshots:
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
meshoptimizer@0.18.1: {}
|
||||
|
||||
micromark-core-commonmark@2.0.1:
|
||||
dependencies:
|
||||
decode-named-character-reference: 1.0.2
|
||||
@ -15226,6 +15277,8 @@ snapshots:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
three@0.172.0: {}
|
||||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
tinyexec@0.3.1: {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user