Dynamic product pages + Battlefield ACP marketing page

- Web Hosting & Game Servers now pull plans from database (same pattern as VPS/Dedicated)
- Add 6 game server plans to PlanSeeder (Minecraft, Rust, ARK, Valheim, CS2, Palworld)
- Create Battlefield ACP marketing page with ProCon replacement hero, 6 feature cards,
  ML analytics, ban appeals, PunkBuster screenshots, Discord integration sections
- Add Battlefield ACP to Products dropdown navigation
- Marketing pages use SectionHeader component, fade-in animations, Vuexy design patterns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Dev
2026-02-10 11:32:35 -05:00
parent 45d25d61ba
commit 169b06e349
20 changed files with 1498 additions and 481 deletions

View File

@@ -443,6 +443,110 @@ class PlanSeeder extends Seeder
'sort_order' => 23,
],
// ─── Game Server Plans ──────────────────────────────────────
[
'name' => 'Minecraft',
'slug' => 'game-minecraft',
'description' => 'Java & Bedrock Minecraft server with full mod support.',
'service_type' => 'game',
'price' => 7.99,
'billing_cycle' => 'monthly',
'features' => [
'ram' => '2 GB',
'slots' => '20 Players',
'storage' => '10 GB SSD',
'cpu' => '2 Threads',
'mods' => 'Full Mod Support',
'ddos' => 'DDoS Protection',
],
'sort_order' => 40,
],
[
'name' => 'Rust',
'slug' => 'game-rust',
'description' => 'High-performance Rust server with Oxide/uMod support.',
'service_type' => 'game',
'price' => 14.99,
'billing_cycle' => 'monthly',
'features' => [
'ram' => '4 GB',
'slots' => '50 Players',
'storage' => '25 GB SSD',
'cpu' => '3 Threads',
'mods' => 'Oxide/uMod Support',
'ddos' => 'DDoS Protection',
],
'sort_order' => 41,
],
[
'name' => 'ARK: Survival Evolved',
'slug' => 'game-ark',
'description' => 'ARK servers with cluster support and full mod compatibility.',
'service_type' => 'game',
'price' => 19.99,
'billing_cycle' => 'monthly',
'features' => [
'ram' => '8 GB',
'slots' => '30 Players',
'storage' => '50 GB SSD',
'cpu' => '4 Threads',
'mods' => 'Full Mod Support',
'ddos' => 'DDoS Protection',
],
'sort_order' => 42,
],
[
'name' => 'Valheim',
'slug' => 'game-valheim',
'description' => 'Dedicated Valheim server with BepInEx mod support.',
'service_type' => 'game',
'price' => 9.99,
'billing_cycle' => 'monthly',
'features' => [
'ram' => '4 GB',
'slots' => '10 Players',
'storage' => '10 GB SSD',
'cpu' => '2 Threads',
'mods' => 'BepInEx Support',
'ddos' => 'DDoS Protection',
],
'sort_order' => 43,
],
[
'name' => 'CS2',
'slug' => 'game-cs2',
'description' => 'Counter-Strike 2 competitive and casual servers.',
'service_type' => 'game',
'price' => 12.99,
'billing_cycle' => 'monthly',
'features' => [
'ram' => '4 GB',
'slots' => '32 Players',
'storage' => '15 GB SSD',
'cpu' => '3 Threads',
'mods' => 'Workshop Support',
'ddos' => 'DDoS Protection',
],
'sort_order' => 44,
],
[
'name' => 'Palworld',
'slug' => 'game-palworld',
'description' => 'Palworld dedicated server with full configuration.',
'service_type' => 'game',
'price' => 14.99,
'billing_cycle' => 'monthly',
'features' => [
'ram' => '8 GB',
'slots' => '32 Players',
'storage' => '20 GB SSD',
'cpu' => '4 Threads',
'mods' => 'Community Mods',
'ddos' => 'DDoS Protection',
],
'sort_order' => 45,
],
// ─── MySQL Hosting Plans ─────────────────────────────────────
[
'name' => 'Bronze',

View File

@@ -0,0 +1,68 @@
// ━━━ Marketing Page Styles ━━━
// Standard section spacing (matches Vuexy convention)
.marketing-section {
padding-block: 5.25rem;
}
// Animated gradient text effect
.hero-gradient-text {
background: linear-gradient(
135deg,
rgb(var(--v-theme-primary)) 0%,
#9f8fff 50%,
rgb(var(--v-theme-primary)) 100%
);
background-size: 200% auto;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: gradient-shine 3s ease infinite;
}
@keyframes gradient-shine {
0% { background-position: 0% center; }
50% { background-position: 100% center; }
100% { background-position: 0% center; }
}
// Card hover effect - lift up with shadow
.feature-card-hover {
transition: transform 0.3s ease, box-shadow 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 4px 25px 0 rgba(var(--v-shadow-key-umbra-color), 0.16) !important;
}
}
// Fade-in-up animation
.fade-in-up {
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.6s ease forwards;
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
// Staggered animation delays (for card grids)
@for $i from 1 through 8 {
.stagger-delay-#{$i} {
animation-delay: #{$i * 0.1}s;
}
}
// Alternating section background
.section-alt-bg {
background-color: rgba(var(--v-theme-on-surface), var(--v-hover-opacity));
}
// Hero section with rounded bottom
.hero-section {
border-radius: 0 0 50px 50px;
}

View File

@@ -4,6 +4,9 @@
// Vertical sidebar navigation layout
@use "@layouts/styles/vertical-nav";
// Marketing page shared styles
@use "marketing";
// ━━━ Project-specific overrides ━━━
html {

View File

@@ -0,0 +1,46 @@
<script setup lang="ts">
interface Props {
label?: string
labelColor?: string
title: string
highlightWord?: string
subtitle?: string
}
withDefaults(defineProps<Props>(), {
label: undefined,
labelColor: 'primary',
highlightWord: undefined,
subtitle: undefined,
})
</script>
<template>
<div class="text-center mb-12">
<VChip
v-if="label"
:color="labelColor"
variant="tonal"
class="mb-4"
>
{{ label }}
</VChip>
<h2 class="text-h3 font-weight-bold mb-3">
<template v-if="highlightWord && title.includes(highlightWord)">
{{ title.split(highlightWord)[0] }}<span class="hero-gradient-text">{{ highlightWord }}</span>{{ title.split(highlightWord)[1] }}
</template>
<template v-else>
{{ title }}
</template>
</h2>
<p
v-if="subtitle"
class="text-body-1 text-medium-emphasis mx-auto"
style="max-inline-size: 600px;"
>
{{ subtitle }}
</p>
</div>
</template>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
import { useForm, usePage } from '@inertiajs/vue3'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useTheme } from 'vuetify'
import { marketingNavItems } from '@/navigation/marketing'
import ThemeSwitcher from '@/Components/ThemeSwitcher.vue'
@@ -9,6 +9,45 @@ import logoWhite from '@images/ezscale_logo_white.png'
const theme = useTheme()
const isDark = computed(() => theme.global.current.value.dark)
const isScrolled = ref(false)
function handleScroll(): void {
isScrolled.value = window.scrollY > 50
}
onMounted(() => {
window.addEventListener('scroll', handleScroll)
handleScroll()
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
const currentPath = computed<string>(() => {
if (typeof window !== 'undefined') {
return window.location.pathname
}
return '/'
})
const newsletterForm = useForm({
email: '',
})
const newsletterSuccess = ref(false)
function subscribeNewsletter(): void {
// For now, just show success state (no backend endpoint yet)
if (newsletterForm.email) {
newsletterSuccess.value = true
setTimeout(() => {
newsletterSuccess.value = false
newsletterForm.email = ''
}, 3000)
}
}
interface PageProps {
domains: { marketing: string; account: string; admin: string }
}
@@ -53,7 +92,11 @@ const socialLinks = [
<template>
<VApp>
<VAppBar flat>
<VAppBar
:elevation="isScrolled ? 4 : 0"
:class="isScrolled ? 'navbar-scrolled' : 'navbar-transparent'"
class="marketing-navbar"
>
<VContainer class="d-flex align-center">
<a href="/" class="d-inline-flex align-center">
<img
@@ -94,7 +137,11 @@ const socialLinks = [
</VMenu>
<a v-else :href="item.href" class="text-decoration-none">
<VBtn variant="text" size="small">
<VBtn
variant="text"
size="small"
:color="item.href === currentPath ? 'primary' : undefined"
>
{{ item.title }}
</VBtn>
</a>
@@ -148,16 +195,23 @@ const socialLinks = [
High-performance VPS, dedicated servers, and hosting solutions with 24/7 support and enterprise-grade infrastructure.
</div>
<VForm class="subscribe-form d-flex align-center">
<VForm class="subscribe-form d-flex align-center" @submit.prevent="subscribeNewsletter">
<VTextField
label="Subscribe to newsletter"
v-model="newsletterForm.email"
:label="newsletterSuccess ? 'Subscribed!' : 'Subscribe to newsletter'"
placeholder="john@email.com"
variant="outlined"
density="comfortable"
hide-details
type="email"
:color="newsletterSuccess ? 'success' : undefined"
/>
<VBtn class="align-self-end rounded-s-0">
Subscribe
<VBtn
type="submit"
class="align-self-end rounded-s-0"
:color="newsletterSuccess ? 'success' : undefined"
>
{{ newsletterSuccess ? 'Done!' : 'Subscribe' }}
</VBtn>
</VForm>
</div>
@@ -291,6 +345,14 @@ const socialLinks = [
</template>
<style lang="scss" scoped>
.marketing-navbar {
transition: all 0.3s ease;
}
.navbar-scrolled {
backdrop-filter: blur(10px);
}
.footer-title {
color: rgba(255, 255, 255, 92%);
}

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup>
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout })
@@ -11,10 +12,17 @@ const values = [
]
const milestones = [
{ year: '2024', event: 'EZSCALE founded with a mission to simplify cloud hosting.' },
{ year: '2024', event: 'Launched VPS and Dedicated Server product lines.' },
{ year: '2025', event: 'Expanded to 50+ data center locations worldwide.' },
{ year: '2025', event: 'Surpassed 10,000 active customers.' },
{ year: '2024', event: 'EZSCALE founded with a mission to simplify cloud hosting.', color: 'primary' },
{ year: '2024', event: 'Launched VPS and Dedicated Server product lines.', color: 'success' },
{ year: '2025', event: 'Expanded to 50+ data center locations worldwide.', color: 'warning' },
{ year: '2025', event: 'Surpassed 10,000 active customers.', color: 'error' },
]
const stats = [
{ value: '10K+', label: 'Customers', color: 'primary' },
{ value: '99.99%', label: 'Uptime', color: 'success' },
{ value: '50+', label: 'Locations', color: 'warning' },
{ value: '24/7', label: 'Support', color: 'error' },
]
</script>
@@ -24,7 +32,9 @@ const milestones = [
<VContainer class="py-16">
<VRow align="center">
<VCol cols="12" md="8" class="mx-auto text-center">
<h1 class="text-h2 font-weight-bold mb-4">About EZSCALE</h1>
<h1 class="text-h2 font-weight-bold mb-4">
About <span class="hero-gradient-text">EZSCALE</span>
</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular">
We're on a mission to make cloud hosting simple, affordable, and accessible to everyone.
From individual developers to growing businesses, EZSCALE provides the infrastructure you need to succeed.
@@ -36,13 +46,17 @@ const milestones = [
<!-- Values -->
<div class="bg-surface-variant py-16">
<VContainer>
<div class="text-center mb-12">
<h2 class="text-h3 font-weight-bold mb-3">Our Values</h2>
</div>
<SectionHeader
label="Our Values"
title="What We Stand For"
/>
<VRow>
<VCol v-for="value in values" :key="value.title" cols="12" sm="6" md="3">
<VCard variant="flat" class="text-center pa-6 h-100 bg-transparent">
<VCol v-for="(value, index) in values" :key="value.title" cols="12" sm="6" md="3">
<VCard
variant="flat"
:class="`text-center pa-6 h-100 bg-transparent feature-card-hover fade-in-up stagger-delay-${index + 1}`"
>
<VAvatar :color="value.color" variant="tonal" size="64" class="mb-4">
<VIcon :icon="value.icon" size="32" />
</VAvatar>
@@ -56,9 +70,10 @@ const milestones = [
<!-- Timeline -->
<VContainer class="py-16">
<div class="text-center mb-12">
<h2 class="text-h3 font-weight-bold mb-3">Our Journey</h2>
</div>
<SectionHeader
label="Our Journey"
title="Milestones Along the Way"
/>
<VRow justify="center">
<VCol cols="12" md="8">
@@ -66,12 +81,12 @@ const milestones = [
<VTimelineItem
v-for="(milestone, index) in milestones"
:key="index"
dot-color="primary"
:dot-color="milestone.color"
size="small"
>
<VCard variant="outlined">
<VCardText>
<div class="text-caption text-primary font-weight-bold mb-1">{{ milestone.year }}</div>
<div :class="`text-caption text-${milestone.color} font-weight-bold mb-1`">{{ milestone.year }}</div>
<div class="text-body-1">{{ milestone.event }}</div>
</VCardText>
</VCard>
@@ -85,21 +100,11 @@ const milestones = [
<div class="bg-surface-variant py-16">
<VContainer>
<VRow>
<VCol cols="6" md="3" class="text-center">
<div class="text-h3 font-weight-bold text-primary">10K+</div>
<div class="text-body-1 text-medium-emphasis mt-1">Customers</div>
</VCol>
<VCol cols="6" md="3" class="text-center">
<div class="text-h3 font-weight-bold text-primary">99.99%</div>
<div class="text-body-1 text-medium-emphasis mt-1">Uptime</div>
</VCol>
<VCol cols="6" md="3" class="text-center">
<div class="text-h3 font-weight-bold text-primary">50+</div>
<div class="text-body-1 text-medium-emphasis mt-1">Locations</div>
</VCol>
<VCol cols="6" md="3" class="text-center">
<div class="text-h3 font-weight-bold text-primary">24/7</div>
<div class="text-body-1 text-medium-emphasis mt-1">Support</div>
<VCol v-for="stat in stats" :key="stat.label" cols="6" md="3">
<VCard flat class="pa-4" :style="`border-inline-start: 4px solid rgb(var(--v-theme-${stat.color}));`">
<div :class="`text-h3 font-weight-bold text-${stat.color}`">{{ stat.value }}</div>
<div class="text-body-1 text-medium-emphasis mt-1">{{ stat.label }}</div>
</VCard>
</VCol>
</VRow>
</VContainer>

View File

@@ -45,7 +45,7 @@ const sections: Section[] = [
<p class="text-body-1 mb-6">
This Acceptable Use Policy ("AUP") governs the use of all services provided by {{ companyName }}
("EZSCALE," "we," "us," or "our"). This AUP is incorporated into and forms part of our
<Link :href="route('terms')" class="text-primary text-decoration-none font-weight-medium">Terms of Service</Link>.
<Link href="/terms-of-service" class="text-primary text-decoration-none font-weight-medium">Terms of Service</Link>.
By using our Services, you agree to comply with this policy. Violations may result in suspension
or termination of your account.
</p>

View File

@@ -0,0 +1,256 @@
<script lang="ts" setup>
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout })
interface Feature {
icon: string
title: string
description: string
}
interface ExtraFeature {
icon: string
title: string
}
interface Game {
name: string
icon: string
description: string
}
const keyFeatures: Feature[] = [
{
icon: 'tabler-terminal',
title: 'RCON Management',
description: 'Live scoreboard with admin actions — kill, kick, ban, yell, say, nuke, team switch, punish, forgive, and mute players in real-time.',
},
{
icon: 'tabler-brain',
title: 'ML Analytics',
description: 'Cheat detection, chat toxicity scoring, player risk assessments, server health monitoring, and intelligent slot recommendations.',
},
{
icon: 'tabler-gavel',
title: 'Ban Appeal System',
description: 'Public portal — no account needed. Battlelog verification, tracking codes, threaded discussions, file attachments up to 2 GB, and Discord notifications.',
},
{
icon: 'tabler-camera',
title: 'PunkBuster Screenshots',
description: 'S3 storage with automatic FTP sync, OCR metadata extraction, thumbnail grid viewer, pagination, and search.',
},
{
icon: 'tabler-world',
title: 'Public Pages',
description: 'Server browser with population graphs, leaderboards (score, kills, K/D, HSR%, SPM, KPM, playtime, wins), and detailed player profiles.',
},
{
icon: 'tabler-brand-discord',
title: 'Discord Integration',
description: 'Real-time webhooks for all ban appeal events with rich embeds — player avatars, server names, ban types, and tracking codes.',
},
]
const moreFeatures: ExtraFeature[] = [
{ icon: 'tabler-fingerprint', title: 'Passkey Authentication' },
{ icon: 'tabler-activity-heartbeat', title: 'System Health Dashboard' },
{ icon: 'tabler-moon-stars', title: 'Dark & Light Themes' },
{ icon: 'tabler-device-mobile', title: 'Mobile Support' },
{ icon: 'tabler-search', title: 'Player Search' },
{ icon: 'tabler-list-details', title: 'Event Logger' },
{ icon: 'tabler-message-dots', title: 'Chat Log Viewer' },
{ icon: 'tabler-star', title: 'Reputation System' },
]
const supportedGames: Game[] = [
{ name: 'Battlefield 3', icon: 'tabler-military-rank', description: 'Full support for BF3 servers including all RCON plugins and leaderboards.' },
{ name: 'Battlefield 4', icon: 'tabler-military-award', description: 'Complete BF4 server management with real-time scoreboard and ML analytics.' },
{ name: 'Battlefield Hardline', icon: 'tabler-badge', description: 'Hardline server support with all admin tools and ban appeal system.' },
]
</script>
<template>
<div>
<!-- Hero -->
<div class="hero-section" style="padding-block: 5.25rem; background: linear-gradient(135deg, rgb(var(--v-theme-info), 0.12), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<VChip color="info" variant="tonal" class="mb-4">Game Server Management</VChip>
<h1 class="text-h2 font-weight-bold mb-3">
Battlefield Admin <span class="hero-gradient-text">Control Panel</span>
</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular mb-4 mx-auto" style="max-width: 700px;">
A ground-up rebuild in Laravel 12 + Vue 3. Manage your BF3, BF4, and Hardline servers with a modern web panel no more ProCon.
</p>
<p class="text-body-1 text-medium-emphasis mb-8 mx-auto" style="max-width: 600px;">
ACP v5.0 replaces the ProCon dependency entirely. Configure all 24 RCON plugins directly from the panel with real-time auto-save.
</p>
<a href="/contact" class="text-decoration-none">
<VBtn color="info" size="x-large" rounded="lg">
Order Now
<VIcon icon="tabler-arrow-right" end />
</VBtn>
</a>
</VContainer>
</div>
<!-- No More ProCon -->
<VContainer class="marketing-section">
<VRow align="center">
<VCol cols="12" md="6">
<VChip color="success" variant="tonal" class="mb-3">Headline Feature</VChip>
<h2 class="text-h4 font-weight-bold mb-3">
No More <span class="text-info">ProCon</span>
</h2>
<p class="text-body-1 text-medium-emphasis mb-4">
ACP v5.0 eliminates the need for ProCon entirely. All 24 RCON plugins can now be configured directly from the server edit page in your browser.
</p>
<VList density="compact" class="pa-0 bg-transparent">
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-circle-check" color="success" size="20" class="me-2" />
</template>
<VListItemTitle class="text-body-1">Toggle plugins on/off from the web panel</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-circle-check" color="success" size="20" class="me-2" />
</template>
<VListItemTitle class="text-body-1">Adjust settings in real-time with auto-save</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-circle-check" color="success" size="20" class="me-2" />
</template>
<VListItemTitle class="text-body-1">24 RCON plugins configurable without external tools</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-circle-check" color="success" size="20" class="me-2" />
</template>
<VListItemTitle class="text-body-1">Modern Vue 3 UI with dark/light theme and mobile support</VListItemTitle>
</VListItem>
</VList>
</VCol>
<VCol cols="12" md="6">
<VCard variant="outlined" class="pa-6 text-center">
<VAvatar color="info" variant="tonal" size="80" class="mb-4">
<VIcon icon="tabler-plug-connected-x" size="40" />
</VAvatar>
<h3 class="text-h5 font-weight-bold mb-2">ProCon Replacement</h3>
<p class="text-body-2 text-medium-emphasis mb-4">
No downloads. No .NET runtime. No port forwarding. Just log in and manage your server from any browser.
</p>
<div class="d-flex flex-wrap justify-center ga-2">
<VChip size="small" variant="tonal" color="info">Web-Based</VChip>
<VChip size="small" variant="tonal" color="info">Real-Time</VChip>
<VChip size="small" variant="tonal" color="info">Auto-Save</VChip>
<VChip size="small" variant="tonal" color="info">24 Plugins</VChip>
</div>
</VCard>
</VCol>
</VRow>
</VContainer>
<!-- Key Features -->
<div class="section-alt-bg marketing-section">
<VContainer>
<SectionHeader
label="Features"
label-color="info"
title="Powerful Server Management"
highlight-word="Management"
subtitle="Everything you need to run, monitor, and moderate your Battlefield servers."
/>
<VRow>
<VCol v-for="(feature, index) in keyFeatures" :key="feature.title" cols="12" sm="6" md="4">
<VCard variant="outlined" :class="['h-100 feature-card-hover', `fade-in-up stagger-delay-${index + 1}`]">
<VCardText class="pa-5">
<VAvatar color="info" variant="tonal" size="48" class="mb-3">
<VIcon :icon="feature.icon" size="24" />
</VAvatar>
<h3 class="text-subtitle-1 font-weight-bold mb-2">{{ feature.title }}</h3>
<p class="text-body-2 text-medium-emphasis mb-0">{{ feature.description }}</p>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</div>
<!-- More Features -->
<VContainer class="marketing-section">
<SectionHeader
label="And More"
label-color="info"
title="Built for Admins"
highlight-word="Admins"
subtitle="Additional capabilities that make ACP the most complete Battlefield admin tool."
/>
<VRow>
<VCol v-for="(feat, index) in moreFeatures" :key="feat.title" cols="6" sm="4" md="3">
<div :class="['text-center pa-4 feature-card-hover rounded-lg', `fade-in-up stagger-delay-${(index % 4) + 1}`]">
<VAvatar color="info" variant="tonal" size="48" class="mb-2">
<VIcon :icon="feat.icon" size="24" />
</VAvatar>
<h4 class="text-body-1 font-weight-bold">{{ feat.title }}</h4>
</div>
</VCol>
</VRow>
</VContainer>
<!-- Supported Games -->
<div class="section-alt-bg marketing-section">
<VContainer>
<SectionHeader
label="Compatibility"
label-color="info"
title="Supported Games"
highlight-word="Games"
subtitle="Full support for the Battlefield franchise."
/>
<VRow justify="center">
<VCol v-for="(game, index) in supportedGames" :key="game.name" cols="12" sm="6" md="4">
<VCard variant="outlined" :class="['h-100 feature-card-hover', `fade-in-up stagger-delay-${index + 1}`]">
<VCardText class="text-center pa-6">
<VAvatar color="info" variant="tonal" size="56" class="mb-3">
<VIcon :icon="game.icon" size="28" />
</VAvatar>
<h3 class="text-h6 font-weight-bold mb-2">{{ game.name }}</h3>
<p class="text-body-2 text-medium-emphasis mb-0">{{ game.description }}</p>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</div>
<!-- CTA -->
<div class="marketing-section" style="background: linear-gradient(135deg, rgb(var(--v-theme-info), 0.08), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<h2 class="text-h4 font-weight-bold mb-3">Ready to Take Control of Your Battlefield Servers?</h2>
<p class="text-body-1 text-medium-emphasis mb-6">
Get ACP v5.0 the most powerful Battlefield admin panel available. No ProCon required.
</p>
<div class="d-flex ga-3 justify-center flex-wrap">
<a href="/contact" class="text-decoration-none">
<VBtn color="info" size="large" rounded="lg">
Order Now
<VIcon icon="tabler-arrow-right" end />
</VBtn>
</a>
<a href="/contact" class="text-decoration-none">
<VBtn color="info" variant="outlined" size="large" rounded="lg">
Contact Sales
</VBtn>
</a>
</div>
</VContainer>
</div>
</div>
</template>

View File

@@ -1,6 +1,7 @@
<script lang="ts" setup>
import { useForm } from '@inertiajs/vue3'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
import AppTextField from '@/Components/app-form-elements/AppTextField.vue'
import AppSelect from '@/Components/app-form-elements/AppSelect.vue'
import AppTextarea from '@/Components/app-form-elements/AppTextarea.vue'
@@ -39,13 +40,12 @@ const contactInfo = [
<template>
<div>
<VContainer class="py-16">
<div class="text-center mb-12">
<h1 class="text-h2 font-weight-bold mb-3">Contact Us</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular">
Have a question? We'd love to hear from you.
</p>
</div>
<VContainer class="marketing-section">
<SectionHeader
label="Get in Touch"
title="Contact Us"
subtitle="Have a question? We'd love to hear from you."
/>
<VRow>
<!-- Contact Form -->
@@ -115,7 +115,7 @@ const contactInfo = [
<!-- Contact Info -->
<VCol cols="12" md="5">
<div class="d-flex flex-column ga-4">
<VCard v-for="info in contactInfo" :key="info.title" variant="outlined">
<VCard v-for="info in contactInfo" :key="info.title" variant="outlined" class="feature-card-hover">
<VCardText class="d-flex align-center ga-4">
<VAvatar color="primary" variant="tonal" size="48">
<VIcon :icon="info.icon" size="24" />
@@ -140,8 +140,9 @@ const contactInfo = [
<p class="text-body-2 mb-3">
Our support team is available 24/7 through your account dashboard.
</p>
<VBtn variant="outlined" size="small">
<VBtn variant="outlined" size="small" href="https://ezscale.support/en/knowledgebase" target="_blank">
Visit Knowledge Base
<VIcon icon="tabler-external-link" end size="16" />
</VBtn>
</VCardText>
</VCard>

View File

@@ -2,28 +2,28 @@
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout })
interface Plan {
id: number
name: string
slug: string
price: string
features: Record<string, string | number> | null
stock_quantity: number | null
}
interface PageProps {
plans: Plan[]
domains: { marketing: string; account: string; admin: string }
}
const page = usePage()
const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed(() => `https://${props.value.domains?.account}`)
interface ServerConfig {
model: string
formFactor: string
cpu: string
coresThreads: string
clockSpeed: string
ram: string
bays: string
price: string
inStock: boolean
}
const plans = computed(() => props.value.plans || [])
const features = [
{ icon: 'tabler-cpu', title: 'Dedicated Hardware', description: 'No shared resources — all CPU, RAM, and storage are exclusively yours.' },
@@ -34,97 +34,6 @@ const features = [
{ icon: 'tabler-headset', title: '24/7 Support', description: 'Expert engineers available around the clock for hardware and network issues.' },
]
const servers: ServerConfig[] = [
{
model: 'Dell R330 LFF',
formFactor: '4-Bay',
cpu: '1x Intel Xeon E3-1220 v5',
coresThreads: '4C/4T',
clockSpeed: '3.0/3.5 GHz',
ram: '16 GB',
bays: '4x 3.5"',
price: '$44.39',
inStock: true,
},
{
model: 'Dell R420 LFF',
formFactor: '4-Bay',
cpu: '2x Intel Xeon E5-2430v2',
coresThreads: '12C/24T',
clockSpeed: '2.5/3.0 GHz',
ram: '32 GB',
bays: '4x 3.5"',
price: '$58.79',
inStock: false,
},
{
model: 'Dell R620 SFF',
formFactor: '10-Bay',
cpu: '2x Intel Xeon E5-2667v2',
coresThreads: '16C/32T',
clockSpeed: '3.3/4.0 GHz',
ram: '32 GB',
bays: '10x 2.5"',
price: '$61.19',
inStock: false,
},
{
model: 'Dell R620 SFF',
formFactor: '8-Bay',
cpu: '2x Intel Xeon E5-2667v2',
coresThreads: '16C/32T',
clockSpeed: '3.3/4.0 GHz',
ram: '32 GB',
bays: '8x 2.5"',
price: '$61.19',
inStock: false,
},
{
model: 'Dell R520 LFF',
formFactor: '8-Bay',
cpu: '2x Intel Xeon E5-2420v2',
coresThreads: '12C/24T',
clockSpeed: '2.2/2.7 GHz',
ram: '32 GB',
bays: '8x 3.5"',
price: '$64.79',
inStock: true,
},
{
model: 'Dell R430 LFF',
formFactor: '4-Bay',
cpu: '2x Intel Xeon E5-2667v4',
coresThreads: '16C/32T',
clockSpeed: '3.2/3.6 GHz',
ram: '32 GB',
bays: '4x 3.5"',
price: '$87.59',
inStock: true,
},
{
model: 'Dell R630 SFF',
formFactor: '8-Bay',
cpu: '2x Intel Xeon E5-2697A v4',
coresThreads: '32C/64T',
clockSpeed: '2.6/3.6 GHz',
ram: '32 GB',
bays: '8x 2.5"',
price: '$93.59',
inStock: true,
},
{
model: 'Dell R730 LFF',
formFactor: '8-Bay',
cpu: '2x Intel Xeon E5-2680v4',
coresThreads: '28C/56T',
clockSpeed: '2.4/3.3 GHz',
ram: '32 GB',
bays: '8x 3.5"',
price: '$107.99',
inStock: true,
},
]
const included = [
{ icon: 'tabler-world', label: '10 TB Bandwidth' },
{ icon: 'tabler-network', label: '1 Gbps Port' },
@@ -133,15 +42,30 @@ const included = [
{ icon: 'tabler-hexagons', label: '1x /64 IPv6 Subnet' },
{ icon: 'tabler-dashboard', label: 'SynergyCP Panel' },
]
function isInStock(plan: Plan): boolean {
return plan.stock_quantity === null || plan.stock_quantity > 0
}
function getFeature(plan: Plan, key: string): string {
return String(plan.features?.[key] ?? '-')
}
function formatPrice(plan: Plan): string {
const price = parseFloat(plan.price) || 0
return price % 1 === 0 ? `$${price}` : `$${price.toFixed(2)}`
}
</script>
<template>
<div>
<!-- Hero -->
<div class="py-16" style="background: linear-gradient(135deg, rgb(var(--v-theme-success), 0.1), rgb(var(--v-theme-surface)));">
<div class="hero-section" style="padding-block: 5.25rem; background: linear-gradient(135deg, rgb(var(--v-theme-success), 0.1), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<VChip color="success" variant="tonal" class="mb-4">Dedicated Servers</VChip>
<h1 class="text-h2 font-weight-bold mb-3">Bare Metal Power</h1>
<h1 class="text-h2 font-weight-bold mb-3">
Bare Metal <span class="hero-gradient-text">Power</span>
</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular mb-8 mx-auto" style="max-width: 600px;">
Enterprise-grade Dell PowerEdge servers with full root access, SynergyCP management, and same-day deployment from our Atlanta datacenter.
</p>
@@ -155,14 +79,17 @@ const included = [
</div>
<!-- Features -->
<VContainer class="py-16">
<div class="text-center mb-12">
<h2 class="text-h3 font-weight-bold mb-3">Enterprise Hardware</h2>
<p class="text-body-1 text-medium-emphasis">Every dedicated server comes with these features included.</p>
</div>
<VContainer class="marketing-section">
<SectionHeader
label="Features"
label-color="success"
title="Enterprise Hardware"
highlight-word="Hardware"
subtitle="Every dedicated server comes with these features included."
/>
<VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="4">
<div class="d-flex ga-3 mb-4">
<VCol v-for="(feature, index) in features" :key="feature.title" cols="12" sm="6" md="4">
<div :class="['d-flex ga-3 mb-4 feature-card-hover pa-3 rounded-lg', `fade-in-up stagger-delay-${index + 1}`]">
<VAvatar color="success" variant="tonal" size="44">
<VIcon :icon="feature.icon" size="22" />
</VAvatar>
@@ -176,39 +103,40 @@ const included = [
</VContainer>
<!-- Server Configurations -->
<div class="bg-surface-variant py-16">
<div class="section-alt-bg marketing-section">
<VContainer>
<div class="text-center mb-8">
<h2 class="text-h3 font-weight-bold mb-3">Server Configurations</h2>
<p class="text-body-1 text-medium-emphasis">Real Dell PowerEdge servers. Storage sold separately -- configure your drives at checkout.</p>
</div>
<SectionHeader
label="Servers"
label-color="success"
title="Server Configurations"
highlight-word="Configurations"
subtitle="Real Dell PowerEdge servers. Storage sold separately -- configure your drives at checkout."
/>
<VRow>
<VCol v-for="(server, index) in servers" :key="index" cols="12" sm="6" lg="3">
<VCol v-for="(plan, index) in plans" :key="plan.id" cols="12" sm="6" lg="3">
<VCard
variant="outlined"
class="h-100"
:class="{ 'server-card-unavailable': !server.inStock }"
:style="server.inStock ? {} : { opacity: 0.7 }"
:class="['h-100 feature-card-hover', `fade-in-up stagger-delay-${Math.min(index + 1, 8)}`, { 'server-card-unavailable': !isInStock(plan) }]"
>
<VCardText class="pa-5">
<!-- Header with model and stock status -->
<div class="d-flex align-center justify-space-between mb-1">
<h3 class="text-subtitle-1 font-weight-bold">{{ server.model }}</h3>
<h3 class="text-subtitle-1 font-weight-bold">{{ plan.name }}</h3>
<VChip
:color="server.inStock ? 'success' : 'error'"
:color="isInStock(plan) ? 'success' : 'error'"
size="small"
variant="tonal"
>
{{ server.inStock ? 'In Stock' : 'Sold Out' }}
{{ isInStock(plan) ? 'In Stock' : 'Sold Out' }}
</VChip>
</div>
<p class="text-caption text-medium-emphasis mb-3">{{ server.formFactor }}</p>
<p class="text-caption text-medium-emphasis mb-3">{{ getFeature(plan, 'storage_bays') }}</p>
<!-- Price -->
<div class="mb-4">
<span class="text-h4 font-weight-bold" :class="server.inStock ? 'text-success' : 'text-medium-emphasis'">{{ server.price }}</span>
<span class="text-h4 font-weight-bold" :class="isInStock(plan) ? 'text-success' : 'text-medium-emphasis'">{{ formatPrice(plan) }}</span>
<span class="text-body-2 text-medium-emphasis">/mo</span>
</div>
@@ -218,25 +146,25 @@ const included = [
<div class="mb-4">
<div class="d-flex align-center ga-2 py-1">
<VIcon icon="tabler-cpu" size="16" color="medium-emphasis" />
<span class="text-body-2">{{ server.cpu }}</span>
<span class="text-body-2">{{ getFeature(plan, 'cpu') }}</span>
</div>
<div class="d-flex align-center ga-2 py-1">
<VIcon icon="tabler-topology-star-3" size="16" color="medium-emphasis" />
<span class="text-body-2">{{ server.coresThreads }} @ {{ server.clockSpeed }}</span>
<span class="text-body-2">{{ getFeature(plan, 'cores') }}</span>
</div>
<div class="d-flex align-center ga-2 py-1">
<VIcon icon="tabler-database" size="16" color="medium-emphasis" />
<span class="text-body-2">{{ server.ram }} RAM</span>
<span class="text-body-2">{{ getFeature(plan, 'ram') }} RAM</span>
</div>
<div class="d-flex align-center ga-2 py-1">
<VIcon icon="tabler-server" size="16" color="medium-emphasis" />
<span class="text-body-2">{{ server.bays }} drive bays</span>
<span class="text-body-2">{{ getFeature(plan, 'storage_bays') }}</span>
</div>
</div>
<!-- Order Button -->
<a
v-if="server.inStock"
v-if="isInStock(plan)"
:href="accountUrl + '/register'"
class="text-decoration-none d-block"
>
@@ -261,11 +189,14 @@ const included = [
</div>
<!-- Included With Every Server -->
<VContainer class="py-16">
<div class="text-center mb-8">
<h2 class="text-h3 font-weight-bold mb-3">Included With Every Server</h2>
<p class="text-body-1 text-medium-emphasis">No hidden fees. All servers come with these essentials.</p>
</div>
<VContainer class="marketing-section">
<SectionHeader
label="Included"
label-color="success"
title="Included With Every Server"
highlight-word="Every"
subtitle="No hidden fees. All servers come with these essentials."
/>
<VRow justify="center">
<VCol v-for="item in included" :key="item.label" cols="6" sm="4" md="2">
<div class="text-center">
@@ -279,7 +210,7 @@ const included = [
</VContainer>
<!-- CTA -->
<div class="py-12" style="background: linear-gradient(135deg, rgb(var(--v-theme-success), 0.08), rgb(var(--v-theme-surface)));">
<div class="marketing-section" style="background: linear-gradient(135deg, rgb(var(--v-theme-success), 0.08), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<h2 class="text-h4 font-weight-bold mb-3">Need a Custom Configuration?</h2>
<p class="text-body-1 text-medium-emphasis mb-6">
@@ -302,3 +233,9 @@ const included = [
</div>
</div>
</template>
<style lang="scss" scoped>
.server-card-unavailable {
opacity: 0.7;
}
</style>

View File

@@ -2,27 +2,57 @@
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout })
interface Plan {
id: number
name: string
slug: string
description: string | null
price: string
features: Record<string, string | number> | null
}
interface PageProps {
plans: Plan[]
domains: { marketing: string; account: string; admin: string }
}
interface Feature {
icon: string
title: string
description: string
}
const gameIcons: Record<string, string> = {
'Minecraft': 'tabler-cube',
'Rust': 'tabler-shield',
'ARK: Survival Evolved': 'tabler-dinosaur',
'Valheim': 'tabler-sword',
'CS2': 'tabler-crosshair',
'Palworld': 'tabler-paw',
}
const page = usePage()
const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed(() => `https://${props.value.domains?.account}`)
const plans = computed(() => props.value.plans || [])
const games = [
{ name: 'Minecraft', icon: 'tabler-cube', description: 'Java & Bedrock editions with mod support.', startingAt: '$7.99/mo' },
{ name: 'Rust', icon: 'tabler-shield', description: 'High-performance Rust servers with Oxide support.', startingAt: '$14.99/mo' },
{ name: 'ARK: Survival Evolved', icon: 'tabler-dinosaur', description: 'ARK servers with cluster support.', startingAt: '$19.99/mo' },
{ name: 'Valheim', icon: 'tabler-sword', description: 'Dedicated Valheim servers with mod support.', startingAt: '$9.99/mo' },
{ name: 'CS2', icon: 'tabler-crosshair', description: 'Counter-Strike 2 competitive and casual servers.', startingAt: '$12.99/mo' },
{ name: 'Palworld', icon: 'tabler-paw', description: 'Palworld dedicated servers with full configuration.', startingAt: '$14.99/mo' },
]
function formatPrice(plan: Plan): string {
const price = parseFloat(plan.price) || 0
return price % 1 === 0 ? `$${price}` : `$${price.toFixed(2)}`
}
const features = [
function getIcon(plan: Plan): string {
return gameIcons[plan.name] || 'tabler-device-gamepad-2'
}
function getFeature(plan: Plan, key: string): string {
return String(plan.features?.[key] ?? '-')
}
const features: Feature[] = [
{ icon: 'tabler-bolt', title: 'Low Latency', description: 'Optimized network routing for minimal ping.' },
{ icon: 'tabler-puzzle', title: 'Mod Support', description: 'Easy plugin and mod installation with one-click tools.' },
{ icon: 'tabler-shield-check', title: 'DDoS Protection', description: 'Always-on protection to keep your server online.' },
@@ -33,40 +63,64 @@ const features = [
<template>
<div>
<!-- Hero -->
<div class="py-16" style="background: linear-gradient(135deg, rgb(var(--v-theme-error), 0.1), rgb(var(--v-theme-surface)));">
<div class="hero-section" style="padding-block: 5.25rem; background: linear-gradient(135deg, rgb(var(--v-theme-error), 0.1), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<VChip color="error" variant="tonal" class="mb-4">Game Servers</VChip>
<h1 class="text-h2 font-weight-bold mb-3">Game Server Hosting</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular mb-8 mx-auto" style="max-width: 600px;">
<h1 class="text-h2 font-weight-bold mb-3">
Game Server <span class="hero-gradient-text">Hosting</span>
</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular mb-4 mx-auto" style="max-width: 600px;">
Low-latency game server hosting with instant setup, mod support, and DDoS protection.
</p>
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VChip color="warning" variant="flat" class="mb-8">
<VIcon icon="tabler-clock" start size="16" />
Coming Soon
</VChip>
<div>
<VBtn color="error" size="x-large" rounded="lg">
Get Your Server
<VIcon icon="tabler-arrow-right" end />
Notify Me
<VIcon icon="tabler-bell" end />
</VBtn>
</a>
</div>
</VContainer>
</div>
<!-- Supported Games -->
<VContainer class="py-16">
<div class="text-center mb-12">
<h2 class="text-h3 font-weight-bold mb-3">Supported Games</h2>
<p class="text-body-1 text-medium-emphasis">Popular titles with more being added regularly.</p>
</div>
<VContainer class="marketing-section">
<SectionHeader
label="Games"
label-color="error"
title="Supported Games"
highlight-word="Games"
subtitle="Popular titles with more being added regularly."
/>
<VRow>
<VCol v-for="game in games" :key="game.name" cols="12" sm="6" md="4">
<VCard variant="outlined" class="h-100">
<VCardText class="d-flex align-center ga-4">
<VCol v-for="(plan, index) in plans" :key="plan.id" cols="12" sm="6" md="4">
<VCard variant="outlined" :class="['h-100 feature-card-hover', `fade-in-up stagger-delay-${index + 1}`]">
<VCardText class="pa-5">
<div class="d-flex align-center ga-4 mb-3">
<VAvatar color="error" variant="tonal" size="48">
<VIcon :icon="game.icon" size="24" />
<VIcon :icon="getIcon(plan)" size="24" />
</VAvatar>
<div>
<h3 class="text-subtitle-1 font-weight-bold">{{ game.name }}</h3>
<p class="text-body-2 text-medium-emphasis mb-1">{{ game.description }}</p>
<span class="text-body-2 text-error font-weight-medium">From {{ game.startingAt }}</span>
<h3 class="text-subtitle-1 font-weight-bold">{{ plan.name }}</h3>
<p class="text-body-2 text-medium-emphasis mb-0">{{ plan.description }}</p>
</div>
</div>
<VDivider class="mb-3" />
<div class="d-flex flex-wrap ga-2 mb-3">
<VChip size="x-small" variant="tonal" color="error">{{ getFeature(plan, 'ram') }} RAM</VChip>
<VChip size="x-small" variant="tonal" color="error">{{ getFeature(plan, 'slots') }}</VChip>
<VChip size="x-small" variant="tonal" color="error">{{ getFeature(plan, 'storage') }}</VChip>
<VChip size="x-small" variant="tonal" color="error">{{ getFeature(plan, 'mods') }}</VChip>
</div>
<div class="d-flex align-center justify-space-between">
<span class="text-h6 text-error font-weight-bold">{{ formatPrice(plan) }}<span class="text-body-2 text-medium-emphasis font-weight-regular">/mo</span></span>
<VChip color="warning" size="x-small" variant="flat">Coming Soon</VChip>
</div>
</VCardText>
</VCard>
@@ -75,14 +129,17 @@ const features = [
</VContainer>
<!-- Features -->
<div class="bg-surface-variant py-16">
<div class="section-alt-bg marketing-section">
<VContainer>
<div class="text-center mb-12">
<h2 class="text-h3 font-weight-bold mb-3">Why Gamers Choose EZSCALE</h2>
</div>
<SectionHeader
label="Features"
label-color="error"
title="Why Gamers Choose EZSCALE"
highlight-word="EZSCALE"
/>
<VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="3">
<VCard variant="flat" class="text-center pa-4 h-100 bg-transparent">
<VCol v-for="(feature, index) in features" :key="feature.title" cols="12" sm="6" md="3">
<VCard variant="flat" :class="['text-center pa-4 h-100 bg-transparent feature-card-hover', `fade-in-up stagger-delay-${index + 1}`]">
<VAvatar color="error" variant="tonal" size="56" class="mb-3">
<VIcon :icon="feature.icon" size="28" />
</VAvatar>
@@ -93,5 +150,26 @@ const features = [
</VRow>
</VContainer>
</div>
<!-- CTA -->
<div class="marketing-section" style="background: linear-gradient(135deg, rgb(var(--v-theme-error), 0.08), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<h2 class="text-h4 font-weight-bold mb-3">Be the First to Know</h2>
<p class="text-body-1 text-medium-emphasis mb-6">
Game server hosting is coming soon. Sign up to get notified when we launch.
</p>
<div class="d-flex ga-3 justify-center flex-wrap">
<VBtn color="error" size="large" rounded="lg">
Notify Me
<VIcon icon="tabler-bell" end />
</VBtn>
<a href="/contact" class="text-decoration-none">
<VBtn color="error" variant="outlined" size="large" rounded="lg">
Contact Us
</VBtn>
</a>
</div>
</VContainer>
</div>
</div>
</template>

View File

@@ -2,6 +2,7 @@
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout })
@@ -13,101 +14,442 @@ const page = usePage()
const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed(() => `https://${props.value.domains?.account}`)
const features = [
interface Product {
icon: string
title: string
description: string
href: string
color: string
}
const products: Product[] = [
{ icon: 'tabler-server', title: 'VPS Hosting', description: 'SSD VPS with VirtFusion panel, instant provisioning, and full root access. Starting at $4.20/mo.', href: '/vps-hosting', color: 'primary' },
{ icon: 'tabler-server-2', title: 'Dedicated Servers', description: 'Dell PowerEdge servers with SynergyCP management and 1Gbps connectivity. Starting at $44.39/mo.', href: '/dedicated-servers', color: 'success' },
{ icon: 'tabler-world', title: 'Web Hosting', description: 'Web hosting with Enhance panel, free SSL, Cloudflare DNS, and Redis cache. Starting at $2.39/mo.', href: '/web-hosting', color: 'warning' },
{ icon: 'tabler-device-gamepad-2', title: 'Game Servers', description: 'Low-latency game hosting for Minecraft, Rust, ARK, and more. Coming soon.', href: '/game-servers', color: 'error' },
]
const stats = [
{ value: '10,000+', label: 'Active Customers' },
{ value: '99.99%', label: 'Uptime SLA' },
{ value: '50+', label: 'Server Locations' },
{ value: '24/7', label: 'Expert Support' },
interface ValueProp {
icon: string
title: string
description: string
color: string
}
const valueProps: ValueProp[] = [
{ icon: 'tabler-shield-check', title: '99.99% Uptime', description: 'Enterprise-grade infrastructure with redundant power, cooling, and networking ensures your services stay online around the clock.', color: 'success' },
{ icon: 'tabler-headset', title: '24/7 Expert Support', description: 'Our team of experienced engineers is available day and night to help you resolve issues and optimize your infrastructure.', color: 'primary' },
{ icon: 'tabler-server', title: 'Enterprise Hardware', description: 'Dell PowerEdge and HP ProLiant servers with NVMe SSDs, ECC RAM, and RAID storage for maximum performance and reliability.', color: 'warning' },
]
interface Stat {
value: string
label: string
icon: string
color: string
}
const stats: Stat[] = [
{ value: '10,000+', label: 'Active Customers', icon: 'tabler-users', color: 'primary' },
{ value: '99.99%', label: 'Uptime SLA', icon: 'tabler-arrow-up', color: 'success' },
{ value: '50+', label: 'Server Locations', icon: 'tabler-map-pin', color: 'warning' },
{ value: '24/7', label: 'Expert Support', icon: 'tabler-headset', color: 'error' },
]
interface Testimonial {
quote: string
name: string
company: string
rating: number
avatarColor: string
}
const testimonials: Testimonial[] = [
{
quote: 'EZSCALE\'s VPS hosting is incredibly fast and reliable. Migration was seamless.',
name: 'Alex Turner',
company: 'DevOps Lead',
rating: 5,
avatarColor: 'primary',
},
{
quote: 'Best dedicated server hosting we\'ve used. The SynergyCP panel is fantastic.',
name: 'Sarah Chen',
company: 'CTO at TechVentures',
rating: 5,
avatarColor: 'success',
},
{
quote: 'Their support team resolved our issue in minutes, not hours. Impressive!',
name: 'Marcus Rodriguez',
company: 'Founder at LaunchPad',
rating: 5,
avatarColor: 'warning',
},
]
</script>
<template>
<div>
<!-- Hero Section -->
<div class="py-16" style="background: linear-gradient(135deg, rgb(var(--v-theme-primary), 0.1), rgb(var(--v-theme-surface)));">
<div
class="hero-section"
style="padding-block: 6rem 4rem; background: linear-gradient(135deg, rgb(var(--v-theme-primary), 0.1), rgb(var(--v-theme-surface)));"
>
<VContainer>
<VRow align="center" justify="center">
<VCol cols="12" md="8" class="text-center">
<VRow
align="center"
justify="center"
>
<VCol
cols="12"
md="8"
class="text-center"
>
<h1 class="text-h2 text-md-h1 font-weight-bold mb-4">
Cloud Hosting
<span class="text-primary">Made Simple</span>
<span class="hero-gradient-text">Made Simple</span>
</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular mb-8 mx-auto" style="max-width: 600px;">
<p
class="text-h6 text-medium-emphasis font-weight-regular mb-8 mx-auto"
style="max-inline-size: 600px;"
>
VPS, Dedicated Servers, Web Hosting, and Game Servers. Deploy in minutes with enterprise-grade infrastructure.
</p>
<div class="d-flex justify-center ga-4 flex-wrap">
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="primary" size="x-large" rounded="lg">
<div class="d-flex justify-center ga-4 flex-wrap mb-4">
<a
:href="accountUrl + '/register'"
class="text-decoration-none"
>
<VBtn
color="primary"
size="x-large"
rounded="lg"
>
Start Free Trial
<VIcon icon="tabler-arrow-right" end />
<VIcon
icon="tabler-arrow-right"
end
/>
</VBtn>
</a>
<a href="/pricing" class="text-decoration-none">
<VBtn variant="outlined" size="x-large" rounded="lg">
<a
href="/pricing"
class="text-decoration-none"
>
<VBtn
variant="outlined"
size="x-large"
rounded="lg"
>
View Pricing
</VBtn>
</a>
</div>
<p class="text-body-2 text-disabled">
No credit card required. Deploy in 60 seconds.
</p>
</VCol>
</VRow>
</VContainer>
</div>
<!-- Features Section -->
<VContainer class="py-16">
<div class="text-center mb-12">
<h2 class="text-h3 font-weight-bold mb-3">Our Products</h2>
<p class="text-body-1 text-medium-emphasis">Everything you need to build, deploy, and scale.</p>
</div>
<!-- Products Section -->
<VContainer class="marketing-section">
<SectionHeader
label="Our Products"
title="Everything You Need to Build, Deploy, and Scale"
highlight-word="Scale"
/>
<VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="3">
<VCard variant="outlined" class="h-100" :href="feature.href">
<VCol
v-for="(product, index) in products"
:key="product.title"
cols="12"
sm="6"
md="3"
>
<VCard
variant="outlined"
class="h-100 feature-card-hover fade-in-up"
:class="`stagger-delay-${index + 1}`"
:href="product.href"
>
<VCardText class="text-center pa-6">
<VAvatar :color="feature.color" variant="tonal" size="64" class="mb-4">
<VIcon :icon="feature.icon" size="32" />
<VAvatar
:color="product.color"
variant="tonal"
size="64"
class="mb-4"
>
<VIcon
:icon="product.icon"
size="32"
/>
</VAvatar>
<h3 class="text-h6 font-weight-bold mb-2">{{ feature.title }}</h3>
<p class="text-body-2 text-medium-emphasis mb-0">{{ feature.description }}</p>
<h3 class="text-h6 font-weight-bold mb-2">
{{ product.title }}
</h3>
<p class="text-body-2 text-medium-emphasis mb-0">
{{ product.description }}
</p>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
<!-- Stats Section -->
<div class="bg-surface-variant py-16">
<VContainer>
<!-- Why Choose EZSCALE Section -->
<div class="section-alt-bg">
<VContainer class="marketing-section">
<SectionHeader
label="Why EZSCALE"
title="Built for Performance and Reliability"
highlight-word="Reliability"
subtitle="We invest in enterprise hardware, redundant networks, and expert staff so you can focus on building your business."
/>
<VRow>
<VCol v-for="stat in stats" :key="stat.label" cols="6" md="3" class="text-center">
<div class="text-h3 font-weight-bold text-primary">{{ stat.value }}</div>
<div class="text-body-1 text-medium-emphasis mt-1">{{ stat.label }}</div>
<VCol
v-for="(prop, index) in valueProps"
:key="prop.title"
cols="12"
md="4"
>
<VCard
class="h-100 feature-card-hover fade-in-up"
:class="`stagger-delay-${index + 1}`"
>
<VCardText class="text-center pa-8">
<VAvatar
:color="prop.color"
variant="tonal"
size="64"
class="mb-5"
>
<VIcon
:icon="prop.icon"
size="32"
/>
</VAvatar>
<h3 class="text-h6 font-weight-bold mb-3">
{{ prop.title }}
</h3>
<p class="text-body-1 text-medium-emphasis mb-0">
{{ prop.description }}
</p>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</div>
<!-- CTA Section -->
<VContainer class="py-16">
<VCard color="primary" class="text-center pa-12">
<h2 class="text-h3 font-weight-bold text-white mb-3">Ready to get started?</h2>
<p class="text-h6 font-weight-regular mb-6" style="opacity: 0.9;">
Deploy your first server in under 60 seconds.
<!-- Stats Section -->
<div class="section-alt-bg">
<VContainer class="marketing-section">
<VRow>
<VCol
v-for="stat in stats"
:key="stat.label"
cols="6"
md="3"
>
<VCard
flat
class="h-100"
:style="`border-inline-start: 4px solid rgb(var(--v-theme-${stat.color}))`"
>
<VCardText class="pa-5">
<div class="d-flex align-center ga-3 mb-2">
<VAvatar
:color="stat.color"
variant="tonal"
size="40"
>
<VIcon
:icon="stat.icon"
size="22"
/>
</VAvatar>
<span class="text-h3 font-weight-bold">{{ stat.value }}</span>
</div>
<div class="text-body-1 text-medium-emphasis">
{{ stat.label }}
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
</div>
<!-- Testimonials Section -->
<VContainer class="marketing-section">
<SectionHeader
label="Testimonials"
title="Trusted by Thousands of Customers"
highlight-word="Thousands"
subtitle="See what our customers have to say about their experience with EZSCALE."
/>
<VRow>
<VCol
v-for="(testimonial, index) in testimonials"
:key="testimonial.name"
cols="12"
md="4"
>
<VCard
class="h-100 feature-card-hover fade-in-up"
:class="`stagger-delay-${index + 1}`"
>
<VCardText class="pa-6 d-flex flex-column h-100">
<VRating
:model-value="testimonial.rating"
readonly
color="warning"
density="compact"
class="mb-4"
/>
<p class="text-body-1 font-italic text-medium-emphasis flex-grow-1 mb-4">
"{{ testimonial.quote }}"
</p>
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="white" size="x-large" rounded="lg">
<VDivider class="mb-4" />
<div class="d-flex align-center ga-3">
<VAvatar
:color="testimonial.avatarColor"
variant="tonal"
size="40"
>
<VIcon
icon="tabler-user"
size="22"
/>
</VAvatar>
<div>
<div class="font-weight-bold text-body-1">
{{ testimonial.name }}
</div>
<div class="text-body-2 text-medium-emphasis">
{{ testimonial.company }}
</div>
</div>
</div>
</VCardText>
</VCard>
</VCol>
</VRow>
</VContainer>
<!-- CTA Section -->
<VContainer class="marketing-section">
<VCard
color="primary"
class="overflow-hidden"
>
<VRow
no-gutters
align="center"
>
<VCol
cols="12"
md="7"
>
<VCardText class="pa-12">
<h2 class="text-h3 font-weight-bold text-white mb-3">
Ready to Get Started?
</h2>
<p
class="text-h6 font-weight-regular mb-6"
style="opacity: 0.85;"
>
Deploy your first server in under 60 seconds. No credit card required, cancel anytime.
</p>
<a
:href="accountUrl + '/register'"
class="text-decoration-none"
>
<VBtn
color="white"
size="x-large"
rounded="lg"
>
Create Free Account
<VIcon icon="tabler-arrow-right" end />
<VIcon
icon="tabler-arrow-right"
end
/>
</VBtn>
</a>
</VCardText>
</VCol>
<VCol
cols="12"
md="5"
class="d-none d-md-flex align-center justify-center"
>
<div class="cta-decoration">
<VAvatar
color="rgba(255, 255, 255, 0.15)"
size="140"
>
<VIcon
icon="tabler-rocket"
size="72"
color="white"
/>
</VAvatar>
</div>
</VCol>
</VRow>
</VCard>
</VContainer>
</div>
</template>
<style lang="scss" scoped>
.cta-decoration {
position: relative;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
&::before {
position: absolute;
border: 2px solid rgba(255, 255, 255, 10%);
border-radius: 50%;
block-size: 200px;
content: "";
inline-size: 200px;
}
&::after {
position: absolute;
border: 1px solid rgba(255, 255, 255, 5%);
border-radius: 50%;
block-size: 260px;
content: "";
inline-size: 260px;
}
}
</style>

View File

@@ -2,6 +2,7 @@
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout })
@@ -30,6 +31,47 @@ const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed(() => `https://${props.value.domains?.account}`)
const plans = computed(() => props.value.plans || [])
// Internal provisioning keys that should never be shown to visitors
const internalKeys = new Set([
'virtfusion_package_id',
'synergy_package_id',
'enhance_package_id',
'pterodactyl_package_id',
'virtfusion_server_id',
'synergy_server_id',
])
function isDisplayableFeature(key: string): boolean {
return !internalKeys.has(key)
}
const featureLabels: Record<string, string> = {
vcpu: 'vCPU',
cpu: 'CPU',
ram: 'RAM',
storage: 'Storage',
bandwidth: 'Bandwidth',
ip_addresses: 'IP Addresses',
backups: 'Backups',
ddos_protection: 'DDoS Protection',
support: 'Support',
uptime_sla: 'Uptime SLA',
}
function humanizeFeatureKey(key: string): string {
if (featureLabels[key]) return featureLabels[key]
return key
.replace(/_/g, ' ')
.replace(/\b\w/g, c => c.toUpperCase())
}
function getDisplayFeatures(plan: Plan): Array<{ key: string; value: string }> {
if (!plan.features) return []
return Object.entries(plan.features)
.filter(([key]) => isDisplayableFeature(key))
.map(([key, value]) => ({ key, value }))
}
function getMonthlyPrice(plan: Plan): string {
const price = parseFloat(plan.price ?? '0') || 0
return price % 1 === 0 ? price.toString() : price.toFixed(2)
@@ -44,17 +86,19 @@ function isPopular(index: number): boolean {
return plans.value.length > 1 && index === 1
}
// Feature comparison data
// Feature comparison data — excluding internal keys
const featureComparison = computed(() => {
if (plans.value.length === 0) return []
const allFeatures = new Set<string>()
plans.value.forEach(plan => {
if (plan.features) {
Object.keys(plan.features).forEach(f => allFeatures.add(f))
Object.keys(plan.features)
.filter(isDisplayableFeature)
.forEach(f => allFeatures.add(f))
}
})
return Array.from(allFeatures).map(feature => ({
feature,
feature: humanizeFeatureKey(feature),
plans: plans.value.map(plan => ({
value: plan.features?.[feature] ?? null,
})),
@@ -86,17 +130,11 @@ const faqs = [
<VCard class="pricing-card" flat>
<!-- Plan Cards Section -->
<VContainer>
<div class="text-center">
<h3 class="text-h3 pricing-title mb-2">
Pricing Plans
</h3>
<p class="mb-0 text-body-1">
All plans include 24/7 monitoring and enterprise-grade infrastructure.
</p>
<p class="mb-2 text-body-1">
Choose the best plan to fit your needs.
</p>
</div>
<SectionHeader
label="Pricing"
title="Pricing Plans"
subtitle="All plans include 24/7 monitoring and enterprise-grade infrastructure. Choose the best plan to fit your needs."
/>
<!-- Plan Cards -->
<VRow v-if="plans.length">
@@ -109,6 +147,7 @@ const faqs = [
<VCard
flat
border
class="feature-card-hover"
:class="isPopular(index) ? 'border-primary border-opacity-100' : ''"
>
<VCardText
@@ -167,8 +206,8 @@ const faqs = [
<!-- Plan Features -->
<VList class="card-list mb-4">
<VListItem
v-for="(value, feature) in plan.features"
:key="String(feature)"
v-for="feat in getDisplayFeatures(plan)"
:key="feat.key"
>
<template #prepend>
<VIcon
@@ -179,7 +218,7 @@ const faqs = [
</template>
<VListItemTitle class="text-body-1">
{{ value }}
{{ feat.value }}
</VListItemTitle>
</VListItem>
</VList>
@@ -209,17 +248,26 @@ const faqs = [
We're finalizing our plans. Check back soon or sign up to be notified.
</p>
</VCard>
<!-- Money-back guarantee banner -->
<VCard v-if="plans.length" variant="tonal" color="primary" class="my-8">
<VCardText class="text-center py-4">
<div class="d-flex align-center justify-center ga-2">
<VIcon icon="tabler-sparkles" />
<span class="text-body-1 font-weight-medium">All plans include a 30-day money-back guarantee</span>
</div>
</VCardText>
</VCard>
</VContainer>
<!-- Feature Comparison Table -->
<VContainer v-if="plans.length && featureComparison.length">
<VCardText class="text-center py-16 pricing-section">
<h3 class="text-h3 mb-2">
Pick a plan that works best for you
</h3>
<p class="text-body-1">
Stay cool, we have a 30-day money back guarantee!
</p>
<SectionHeader
label="Compare Plans"
title="Pick a Plan That Works Best for You"
subtitle="Stay cool, we have a 30-day money back guarantee!"
/>
<VTable class="text-no-wrap border rounded pricing-table">
<thead>

View File

@@ -72,7 +72,8 @@ const products = [
</VAvatar>
<div>
<h2 class="text-h5 font-weight-bold">{{ product.title }}</h2>
<span class="text-body-2 text-medium-emphasis">Starting at {{ product.startingAt }}</span>
<span v-if="product.startingAt.startsWith('$')" class="text-body-2 text-medium-emphasis">Starting at {{ product.startingAt }}</span>
<VChip v-else color="info" size="small" variant="tonal">{{ product.startingAt }}</VChip>
</div>
</div>

View File

@@ -70,7 +70,7 @@ const creditTiers: CreditTier[] = [
<!-- Introduction -->
<p class="text-body-1 mb-6">
This Service Level Agreement ("SLA") is part of the
<Link :href="route('terms')" class="text-primary text-decoration-none font-weight-medium">Terms of Service</Link>
<Link href="/terms-of-service" class="text-primary text-decoration-none font-weight-medium">Terms of Service</Link>
between you and {{ companyName }} ("EZSCALE," "we," "us," or "our"). This SLA describes our
uptime commitments and the remedies available to you if we fail to meet them.
</p>

View File

@@ -77,9 +77,9 @@ const sections: Section[] = [
<p class="text-body-1 mb-3">
By creating an account, placing an order, or otherwise using any of our Services, you acknowledge
that you have read, understood, and agree to be bound by these Terms, as well as our
<Link :href="route('privacy')" class="text-primary text-decoration-none font-weight-medium">Privacy Policy</Link>,
<Link :href="route('aup')" class="text-primary text-decoration-none font-weight-medium">Acceptable Use Policy</Link>, and
<Link :href="route('sla')" class="text-primary text-decoration-none font-weight-medium">Service Level Agreement</Link>.
<Link href="/privacy-policy" class="text-primary text-decoration-none font-weight-medium">Privacy Policy</Link>,
<Link href="/acceptable-use" class="text-primary text-decoration-none font-weight-medium">Acceptable Use Policy</Link>, and
<Link href="/sla" class="text-primary text-decoration-none font-weight-medium">Service Level Agreement</Link>.
</p>
<p class="text-body-1 mb-3">
If you are using the Services on behalf of an organization, you represent and warrant that you have
@@ -174,7 +174,7 @@ const sections: Section[] = [
<h2 class="text-h5 font-weight-bold mb-4">6. Acceptable Use</h2>
<p class="text-body-1 mb-3">
Your use of the Services is subject to our
<Link :href="route('aup')" class="text-primary text-decoration-none font-weight-medium">Acceptable Use Policy</Link>.
<Link href="/acceptable-use" class="text-primary text-decoration-none font-weight-medium">Acceptable Use Policy</Link>.
You agree not to use our Services for any purpose that is unlawful or prohibited by these Terms. Prohibited uses include, but are not limited to:
</p>
<ul class="text-body-1 mb-3 ml-6">
@@ -195,7 +195,7 @@ const sections: Section[] = [
<h2 class="text-h5 font-weight-bold mb-4">7. Service Level Agreement</h2>
<p class="text-body-1 mb-3">
Our commitment to uptime and service reliability is detailed in our
<Link :href="route('sla')" class="text-primary text-decoration-none font-weight-medium">Service Level Agreement (SLA)</Link>.
<Link href="/sla" class="text-primary text-decoration-none font-weight-medium">Service Level Agreement (SLA)</Link>.
The SLA outlines our uptime guarantees, credit policies, and the process for reporting and claiming service disruptions.
</p>
<p class="text-body-1">

View File

@@ -2,20 +2,22 @@
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout })
interface PageProps {
domains: { marketing: string; account: string; admin: string }
interface Plan {
id: number
name: string
slug: string
price: string
features: Record<string, string | number> | null
stock_quantity: number | null
}
interface VpsPlan {
name: string
cpu: string
ram: string
storage: string
bandwidth: string
price: string
interface PageProps {
plans: Plan[]
domains: { marketing: string; account: string; admin: string }
}
interface Feature {
@@ -27,6 +29,13 @@ interface Feature {
const page = usePage()
const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed<string>(() => `https://${props.value.domains?.account}`)
const plans = computed(() => props.value.plans || [])
const startingPrice = computed<string>(() => {
if (plans.value.length === 0) return '3.50'
const lowest = Math.min(...plans.value.map(p => parseFloat(p.price)))
return lowest % 1 === 0 ? lowest.toString() : lowest.toFixed(2)
})
const features: Feature[] = [
{ icon: 'tabler-database', title: 'RAID 10 SSD Storage', description: 'Redundant SSD arrays for fast read/write speeds and data protection.' },
@@ -37,43 +46,51 @@ const features: Feature[] = [
{ icon: 'tabler-server', title: 'VirtFusion Panel', description: 'Powerful control panel for managing your VPS with ease.' },
]
const plans: VpsPlan[] = [
{ name: 'Micro VPS', cpu: '1 vCPU', ram: '1 GB', storage: '25 GB SSD', bandwidth: '2 TB', price: '$4.20' },
{ name: 'Mini VPS', cpu: '1 vCPU', ram: '2 GB', storage: '50 GB SSD', bandwidth: '4 TB', price: '$6.00' },
{ name: 'Dev Starter', cpu: '2 vCPU', ram: '2 GB', storage: '60 GB SSD', bandwidth: '4 TB', price: '$8.00' },
{ name: 'Basic VPS', cpu: '2 vCPU', ram: '4 GB', storage: '80 GB SSD', bandwidth: '6 TB', price: '$12.00' },
{ name: 'Storage Box', cpu: '2 vCPU', ram: '2 GB', storage: '500 GB SSD', bandwidth: '8 TB', price: '$15.00' },
{ name: 'Standard VPS', cpu: '4 vCPU', ram: '8 GB', storage: '160 GB SSD', bandwidth: '8 TB', price: '$15.60' },
{ name: 'RAM Optimized', cpu: '4 vCPU', ram: '16 GB', storage: '240 GB SSD', bandwidth: '10 TB', price: '$19.00' },
{ name: 'Advanced VPS', cpu: '6 vCPU', ram: '16 GB', storage: '320 GB SSD', bandwidth: '10 TB', price: '$21.60' },
{ name: 'Pro VPS', cpu: '8 vCPU', ram: '32 GB', storage: '640 GB SSD', bandwidth: '16 TB', price: '$30.00' },
]
const includedFeatures: string[] = [
'1 IPv4 & 1 /64 IPv6',
'Near instant provisioning',
'VM backups',
'Windows (BYOL) & Linux support',
'Intel E5-2680 v4 processors',
'Full root access',
'VirtFusion control panel',
'RAID 10 backed storage',
'14-day money back guarantee',
]
// Keys from features JSON that should not be shown as table columns
const internalKeys = new Set([
'virtfusion_package_id',
'virtfusion_server_id',
'control_panel',
'os',
'ipv4',
'ipv6',
])
function getFeature(plan: Plan, key: string): string {
return String(plan.features?.[key] ?? '-')
}
function formatPrice(plan: Plan): string {
const price = parseFloat(plan.price) || 0
return price % 1 === 0 ? `$${price}` : `$${price.toFixed(2)}`
}
</script>
<template>
<div>
<!-- Hero -->
<div class="py-16" style="background: linear-gradient(135deg, rgb(var(--v-theme-primary), 0.1), rgb(var(--v-theme-surface)));">
<div class="hero-section" style="padding-block: 5.25rem; background: linear-gradient(135deg, rgb(var(--v-theme-primary), 0.1), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<VChip color="primary" variant="tonal" class="mb-4">VPS Hosting</VChip>
<h1 class="text-h2 font-weight-bold mb-3">Virtual Private Servers</h1>
<h1 class="text-h2 font-weight-bold mb-3">
Virtual Private <span class="hero-gradient-text">Servers</span>
</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular mb-4 mx-auto" style="max-width: 600px;">
High-performance VPS hosting with RAID 10 SSD storage, dedicated resources, and full root access from our Atlanta, GA datacenter.
</p>
<p class="text-body-1 text-medium-emphasis mb-8">
Starting at just <span class="text-primary font-weight-bold">$4.20/mo</span>
Starting at just <span class="text-primary font-weight-bold">${{ startingPrice }}/mo</span>
</p>
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="primary" size="x-large" rounded="lg">
@@ -85,13 +102,16 @@ const includedFeatures: string[] = [
</div>
<!-- Features -->
<VContainer class="py-16">
<div class="text-center mb-12">
<h2 class="text-h3 font-weight-bold mb-3">Why Choose EZSCALE VPS?</h2>
</div>
<VContainer class="marketing-section">
<SectionHeader
label="Features"
title="Why Choose EZSCALE VPS?"
highlight-word="EZSCALE"
subtitle="Enterprise-grade infrastructure with the simplicity you deserve."
/>
<VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="4">
<div class="d-flex ga-3 mb-4">
<VCol v-for="(feature, index) in features" :key="feature.title" cols="12" sm="6" md="4">
<div :class="['d-flex ga-3 mb-4 feature-card-hover pa-3 rounded-lg', `fade-in-up stagger-delay-${index + 1}`]">
<VAvatar color="primary" variant="tonal" size="44">
<VIcon :icon="feature.icon" size="22" />
</VAvatar>
@@ -105,16 +125,16 @@ const includedFeatures: string[] = [
</VContainer>
<!-- Plans Table -->
<div class="bg-surface-variant py-16">
<div class="section-alt-bg marketing-section">
<VContainer>
<div class="text-center mb-8">
<h2 class="text-h3 font-weight-bold mb-3">VPS Plans</h2>
<p class="text-body-1 text-medium-emphasis">
All plans hosted in our Atlanta, GA datacenter. DDoS protection, full root access, and VirtFusion panel included.
</p>
</div>
<SectionHeader
label="Pricing"
title="VPS Plans"
highlight-word="Plans"
subtitle="All plans hosted in our Atlanta, GA datacenter. DDoS protection, full root access, and VirtFusion panel included."
/>
<VCard>
<VCard class="feature-card-hover">
<VTable>
<thead>
<tr>
@@ -128,13 +148,13 @@ const includedFeatures: string[] = [
</tr>
</thead>
<tbody>
<tr v-for="plan in plans" :key="plan.name">
<tr v-for="plan in plans" :key="plan.id">
<td class="font-weight-bold">{{ plan.name }}</td>
<td>{{ plan.cpu }}</td>
<td>{{ plan.ram }}</td>
<td>{{ plan.storage }}</td>
<td>{{ plan.bandwidth }}</td>
<td class="text-primary font-weight-bold">{{ plan.price }}/mo</td>
<td>{{ getFeature(plan, 'cpu') }}</td>
<td>{{ getFeature(plan, 'ram') }}</td>
<td>{{ getFeature(plan, 'storage') }}</td>
<td>{{ getFeature(plan, 'bandwidth') }}</td>
<td class="text-primary font-weight-bold">{{ formatPrice(plan) }}/mo</td>
<td>
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="primary" size="small" variant="tonal">Order Now</VBtn>
@@ -146,7 +166,7 @@ const includedFeatures: string[] = [
</VCard>
<!-- Included with all plans -->
<VCard class="mt-8 pa-6">
<VCard class="mt-8 pa-6 feature-card-hover">
<h3 class="text-h5 font-weight-bold mb-4 text-center">Included With All Plans</h3>
<VRow>
<VCol
@@ -165,5 +185,28 @@ const includedFeatures: string[] = [
</VCard>
</VContainer>
</div>
<!-- CTA -->
<div class="marketing-section" style="background: linear-gradient(135deg, rgb(var(--v-theme-primary), 0.08), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<h2 class="text-h4 font-weight-bold mb-3">Ready to Get Started?</h2>
<p class="text-body-1 text-medium-emphasis mb-6">
Deploy your VPS in seconds with full root access, DDoS protection, and 24/7 support.
</p>
<div class="d-flex ga-3 justify-center flex-wrap">
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="primary" size="large" rounded="lg">
Get Started
<VIcon icon="tabler-arrow-right" end />
</VBtn>
</a>
<a href="/contact" class="text-decoration-none">
<VBtn color="primary" variant="outlined" size="large" rounded="lg">
Contact Sales
</VBtn>
</a>
</div>
</VContainer>
</div>
</div>
</template>

View File

@@ -2,16 +2,22 @@
import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout })
interface PageProps {
domains: { marketing: string; account: string; admin: string }
interface Plan {
id: number
name: string
slug: string
price: string
features: Record<string, string | number> | null
}
const page = usePage()
const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed(() => `https://${props.value.domains?.account}`)
interface PageProps {
plans: Plan[]
domains: { marketing: string; account: string; admin: string }
}
interface Feature {
icon: string
@@ -19,18 +25,16 @@ interface Feature {
description: string
}
interface Plan {
name: string
storage: string
databases: string
email: string
domains: string
bandwidth: string
ram: string
cores: string
price: string
popular?: boolean
}
const page = usePage()
const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed<string>(() => `https://${props.value.domains?.account}`)
const plans = computed(() => props.value.plans || [])
const startingPrice = computed<string>(() => {
if (plans.value.length === 0) return '2.39'
const lowest = Math.min(...plans.value.map(p => parseFloat(p.price)))
return lowest % 1 === 0 ? lowest.toString() : lowest.toFixed(2)
})
const features: Feature[] = [
{ icon: 'tabler-layout-dashboard', title: 'Enhance Control Panel', description: 'Modern, intuitive control panel for effortless website and server management.' },
@@ -41,54 +45,6 @@ const features: Feature[] = [
{ icon: 'tabler-terminal-2', title: 'SSH Access', description: 'Full SSH access for advanced users who need command-line control.' },
]
const plans: Plan[] = [
{
name: 'Small',
storage: '10 GB SSD',
databases: '2 MySQL DBs',
email: '5 Email Accounts',
domains: '1 Domain',
bandwidth: '1 TB BW',
ram: '512 MB RAM',
cores: '1 Core',
price: '$2.39',
},
{
name: 'Medium',
storage: '25 GB SSD',
databases: '6 MySQL DBs',
email: '20 Email Accounts',
domains: '4 Domains',
bandwidth: '1 TB BW',
ram: '1 GB RAM',
cores: '1 Core',
price: '$3.99',
popular: true,
},
{
name: 'Large',
storage: '100 GB SSD',
databases: 'Unlimited MySQL DBs',
email: 'Unlimited Email',
domains: '30 Domains',
bandwidth: '2 TB BW',
ram: '4 GB RAM',
cores: '4 Cores',
price: '$7.19',
},
{
name: 'Dedicated',
storage: '160 GB SSD',
databases: 'Unlimited MySQL DBs',
email: 'Unlimited Email',
domains: '100 Domains',
bandwidth: '4 TB BW',
ram: '8 GB RAM',
cores: '4 Cores',
price: '$15.99',
},
]
const includedFeatures: string[] = [
'Free SSL',
'Cloudflare DNS',
@@ -97,18 +53,50 @@ const includedFeatures: string[] = [
'SSH Access',
'Enhance Panel',
]
const internalKeys = new Set(['enhance_package_id', 'ssl', 'panel'])
function getFeature(plan: Plan, key: string): string {
return String(plan.features?.[key] ?? '-')
}
function formatPrice(plan: Plan): string {
const price = parseFloat(plan.price) || 0
return price % 1 === 0 ? `$${price}` : `$${price.toFixed(2)}`
}
interface PlanFeatureRow {
key: string
icon: string
label: string
}
const featureRows: PlanFeatureRow[] = [
{ key: 'storage', icon: 'tabler-database', label: 'Storage' },
{ key: 'databases', icon: 'tabler-stack-2', label: 'Databases' },
{ key: 'email', icon: 'tabler-mail', label: 'Email' },
{ key: 'domains', icon: 'tabler-world', label: 'Domains' },
{ key: 'bandwidth', icon: 'tabler-arrows-transfer-up', label: 'Bandwidth' },
{ key: 'ram', icon: 'tabler-device-desktop-analytics', label: 'RAM' },
{ key: 'cpu', icon: 'tabler-cpu', label: 'CPU' },
]
</script>
<template>
<div>
<!-- Hero -->
<div class="py-16" style="background: linear-gradient(135deg, rgb(var(--v-theme-warning), 0.1), rgb(var(--v-theme-surface)));">
<div class="hero-section" style="padding-block: 5.25rem; background: linear-gradient(135deg, rgb(var(--v-theme-warning), 0.1), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<VChip color="warning" variant="tonal" class="mb-4">Web Hosting</VChip>
<h1 class="text-h2 font-weight-bold mb-3">Managed Web Hosting</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular mb-8 mx-auto" style="max-width: 600px;">
<h1 class="text-h2 font-weight-bold mb-3">
Managed Web <span class="hero-gradient-text">Hosting</span>
</h1>
<p class="text-h6 text-medium-emphasis font-weight-regular mb-4 mx-auto" style="max-width: 600px;">
Fast, secure, and reliable web hosting powered by Enhance with free SSL, Cloudflare DNS, and Redis caching.
</p>
<p class="text-body-1 text-medium-emphasis mb-8">
Starting at just <span class="text-warning font-weight-bold">${{ startingPrice }}/mo</span>
</p>
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="warning" size="x-large" rounded="lg">
Get Started
@@ -119,16 +107,17 @@ const includedFeatures: string[] = [
</div>
<!-- Features -->
<VContainer class="py-16">
<div class="text-center mb-12">
<h2 class="text-h3 font-weight-bold mb-3">Everything You Need</h2>
<p class="text-body-1 text-medium-emphasis mx-auto" style="max-width: 550px;">
Every plan comes loaded with the tools and features you need to build and grow your website.
</p>
</div>
<VContainer class="marketing-section">
<SectionHeader
label="Features"
label-color="warning"
title="Everything You Need"
highlight-word="Need"
subtitle="Every plan comes loaded with the tools and features you need to build and grow your website."
/>
<VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="4">
<div class="d-flex ga-3 mb-4">
<VCol v-for="(feature, index) in features" :key="feature.title" cols="12" sm="6" md="4">
<div :class="['d-flex ga-3 mb-4 feature-card-hover pa-3 rounded-lg', `fade-in-up stagger-delay-${index + 1}`]">
<VAvatar color="warning" variant="tonal" size="44">
<VIcon :icon="feature.icon" size="22" />
</VAvatar>
@@ -142,25 +131,26 @@ const includedFeatures: string[] = [
</VContainer>
<!-- Plans -->
<div class="bg-surface-variant py-16">
<div class="section-alt-bg marketing-section">
<VContainer>
<div class="text-center mb-8">
<h2 class="text-h3 font-weight-bold mb-3">Hosting Plans</h2>
<p class="text-body-1 text-medium-emphasis mx-auto" style="max-width: 550px;">
Choose the plan that fits your needs. All plans include free SSL, Cloudflare DNS, and the Enhance control panel.
</p>
</div>
<SectionHeader
label="Pricing"
label-color="warning"
title="Hosting Plans"
highlight-word="Plans"
subtitle="Choose the plan that fits your needs. All plans include free SSL, Cloudflare DNS, and the Enhance control panel."
/>
<VRow justify="center">
<VCol v-for="plan in plans" :key="plan.name" cols="12" sm="6" lg="3">
<VCol v-for="(plan, index) in plans" :key="plan.id" cols="12" sm="6" lg="3">
<VCard
:variant="plan.popular ? 'elevated' : 'outlined'"
:class="['h-100', { 'border-warning border-opacity-100': plan.popular }]"
:elevation="plan.popular ? 8 : 0"
:variant="index === 1 ? 'elevated' : 'outlined'"
:class="['h-100 feature-card-hover', `fade-in-up stagger-delay-${index + 1}`, { 'border-warning border-opacity-100': index === 1 }]"
:elevation="index === 1 ? 8 : 0"
>
<VCardText class="pa-6 text-center">
<VChip
v-if="plan.popular"
v-if="index === 1"
color="warning"
size="small"
class="mb-2"
@@ -170,53 +160,17 @@ const includedFeatures: string[] = [
<h3 class="text-h5 font-weight-bold mb-1">{{ plan.name }}</h3>
<div class="text-h4 font-weight-bold text-warning mb-4">
{{ plan.price }}<span class="text-body-2 text-medium-emphasis">/mo</span>
{{ formatPrice(plan) }}<span class="text-body-2 text-medium-emphasis">/mo</span>
</div>
<VDivider class="mb-4" />
<VList density="compact" class="pa-0">
<VListItem class="px-0">
<VListItem v-for="row in featureRows" :key="row.key" class="px-0">
<template #prepend>
<VIcon icon="tabler-database" color="warning" size="18" class="me-2" />
<VIcon :icon="row.icon" color="warning" size="18" class="me-2" />
</template>
<VListItemTitle class="text-body-2">{{ plan.storage }}</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-stack-2" color="warning" size="18" class="me-2" />
</template>
<VListItemTitle class="text-body-2">{{ plan.databases }}</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-mail" color="warning" size="18" class="me-2" />
</template>
<VListItemTitle class="text-body-2">{{ plan.email }}</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-world" color="warning" size="18" class="me-2" />
</template>
<VListItemTitle class="text-body-2">{{ plan.domains }}</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-arrows-transfer-up" color="warning" size="18" class="me-2" />
</template>
<VListItemTitle class="text-body-2">{{ plan.bandwidth }}</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-device-desktop-analytics" color="warning" size="18" class="me-2" />
</template>
<VListItemTitle class="text-body-2">{{ plan.ram }}</VListItemTitle>
</VListItem>
<VListItem class="px-0">
<template #prepend>
<VIcon icon="tabler-cpu" color="warning" size="18" class="me-2" />
</template>
<VListItemTitle class="text-body-2">{{ plan.cores }}</VListItemTitle>
<VListItemTitle class="text-body-2">{{ getFeature(plan, row.key) }}</VListItemTitle>
</VListItem>
</VList>
@@ -236,8 +190,8 @@ const includedFeatures: string[] = [
<a :href="accountUrl + '/register'" class="text-decoration-none d-block">
<VBtn
:color="plan.popular ? 'warning' : 'warning'"
:variant="plan.popular ? 'elevated' : 'tonal'"
color="warning"
:variant="index === 1 ? 'elevated' : 'tonal'"
block
>
Choose Plan
@@ -249,5 +203,28 @@ const includedFeatures: string[] = [
</VRow>
</VContainer>
</div>
<!-- CTA -->
<div class="marketing-section" style="background: linear-gradient(135deg, rgb(var(--v-theme-warning), 0.08), rgb(var(--v-theme-surface)));">
<VContainer class="text-center">
<h2 class="text-h4 font-weight-bold mb-3">Ready to Launch Your Website?</h2>
<p class="text-body-1 text-medium-emphasis mb-6">
Get started with managed hosting that includes free SSL, Cloudflare DNS, and the Enhance control panel.
</p>
<div class="d-flex ga-3 justify-center flex-wrap">
<a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="warning" size="large" rounded="lg">
Get Started
<VIcon icon="tabler-arrow-right" end />
</VBtn>
</a>
<a href="/contact" class="text-decoration-none">
<VBtn color="warning" variant="outlined" size="large" rounded="lg">
Contact Sales
</VBtn>
</a>
</div>
</VContainer>
</div>
</div>
</template>

View File

@@ -20,6 +20,7 @@ export const marketingNavItems: MarketingNavItem[] = [
{ title: 'Dedicated Servers', href: '/dedicated-servers', description: 'Bare metal power', icon: 'tabler-server-2' },
{ title: 'Web Hosting', href: '/web-hosting', description: 'Managed web hosting', icon: 'tabler-world' },
{ title: 'Game Servers', href: '/game-servers', description: 'Low-latency game hosting', icon: 'tabler-device-gamepad-2' },
{ title: 'Battlefield ACP', href: '/battlefield-acp', description: 'BF server admin panel', icon: 'tabler-military-rank' },
],
},
{ title: 'Pricing', href: '/pricing' },

View File

@@ -9,10 +9,55 @@ use Inertia\Inertia;
Route::get('/', fn () => Inertia::render('Marketing/Home'))->name('home');
Route::get('/products', fn () => Inertia::render('Marketing/Products'))->name('products');
Route::get('/vps-hosting', fn () => Inertia::render('Marketing/VpsHosting'))->name('vps-hosting');
Route::get('/dedicated-servers', fn () => Inertia::render('Marketing/DedicatedServers'))->name('dedicated-servers');
Route::get('/web-hosting', fn () => Inertia::render('Marketing/WebHosting'))->name('web-hosting');
Route::get('/game-servers', fn () => Inertia::render('Marketing/GameServers'))->name('game-servers');
Route::get('/vps-hosting', function () {
$plans = Plan::query()
->where('service_type', 'vps')
->where('status', 'active')
->orderBy('sort_order')
->orderBy('price')
->get();
return Inertia::render('Marketing/VpsHosting', [
'plans' => $plans,
]);
})->name('vps-hosting');
Route::get('/dedicated-servers', function () {
$plans = Plan::query()
->where('service_type', 'dedicated')
->where('status', 'active')
->orderBy('sort_order')
->orderBy('price')
->get();
return Inertia::render('Marketing/DedicatedServers', [
'plans' => $plans,
]);
})->name('dedicated-servers');
Route::get('/web-hosting', function () {
$plans = Plan::query()
->where('service_type', 'hosting')
->where('status', 'active')
->orderBy('sort_order')
->orderBy('price')
->get();
return Inertia::render('Marketing/WebHosting', [
'plans' => $plans,
]);
})->name('web-hosting');
Route::get('/game-servers', function () {
$plans = Plan::query()
->where('service_type', 'game')
->where('status', 'active')
->orderBy('sort_order')
->orderBy('price')
->get();
return Inertia::render('Marketing/GameServers', [
'plans' => $plans,
]);
})->name('game-servers');
Route::get('/battlefield-acp', fn () => Inertia::render('Marketing/BattlefieldAcp'))->name('battlefield-acp');
Route::get('/pricing', function () {
$plans = Plan::query()
->where('status', 'active')