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, '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 ───────────────────────────────────── // ─── MySQL Hosting Plans ─────────────────────────────────────
[ [
'name' => 'Bronze', '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 // Vertical sidebar navigation layout
@use "@layouts/styles/vertical-nav"; @use "@layouts/styles/vertical-nav";
// Marketing page shared styles
@use "marketing";
// ━━━ Project-specific overrides ━━━ // ━━━ Project-specific overrides ━━━
html { 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> <script lang="ts" setup>
import { usePage } from '@inertiajs/vue3' import { useForm, usePage } from '@inertiajs/vue3'
import { computed } from 'vue' import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useTheme } from 'vuetify' import { useTheme } from 'vuetify'
import { marketingNavItems } from '@/navigation/marketing' import { marketingNavItems } from '@/navigation/marketing'
import ThemeSwitcher from '@/Components/ThemeSwitcher.vue' import ThemeSwitcher from '@/Components/ThemeSwitcher.vue'
@@ -9,6 +9,45 @@ import logoWhite from '@images/ezscale_logo_white.png'
const theme = useTheme() const theme = useTheme()
const isDark = computed(() => theme.global.current.value.dark) 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 { interface PageProps {
domains: { marketing: string; account: string; admin: string } domains: { marketing: string; account: string; admin: string }
} }
@@ -53,7 +92,11 @@ const socialLinks = [
<template> <template>
<VApp> <VApp>
<VAppBar flat> <VAppBar
:elevation="isScrolled ? 4 : 0"
:class="isScrolled ? 'navbar-scrolled' : 'navbar-transparent'"
class="marketing-navbar"
>
<VContainer class="d-flex align-center"> <VContainer class="d-flex align-center">
<a href="/" class="d-inline-flex align-center"> <a href="/" class="d-inline-flex align-center">
<img <img
@@ -94,7 +137,11 @@ const socialLinks = [
</VMenu> </VMenu>
<a v-else :href="item.href" class="text-decoration-none"> <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 }} {{ item.title }}
</VBtn> </VBtn>
</a> </a>
@@ -148,16 +195,23 @@ const socialLinks = [
High-performance VPS, dedicated servers, and hosting solutions with 24/7 support and enterprise-grade infrastructure. High-performance VPS, dedicated servers, and hosting solutions with 24/7 support and enterprise-grade infrastructure.
</div> </div>
<VForm class="subscribe-form d-flex align-center"> <VForm class="subscribe-form d-flex align-center" @submit.prevent="subscribeNewsletter">
<VTextField <VTextField
label="Subscribe to newsletter" v-model="newsletterForm.email"
:label="newsletterSuccess ? 'Subscribed!' : 'Subscribe to newsletter'"
placeholder="john@email.com" placeholder="john@email.com"
variant="outlined" variant="outlined"
density="comfortable" density="comfortable"
hide-details hide-details
type="email"
:color="newsletterSuccess ? 'success' : undefined"
/> />
<VBtn class="align-self-end rounded-s-0"> <VBtn
Subscribe type="submit"
class="align-self-end rounded-s-0"
:color="newsletterSuccess ? 'success' : undefined"
>
{{ newsletterSuccess ? 'Done!' : 'Subscribe' }}
</VBtn> </VBtn>
</VForm> </VForm>
</div> </div>
@@ -291,6 +345,14 @@ const socialLinks = [
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.marketing-navbar {
transition: all 0.3s ease;
}
.navbar-scrolled {
backdrop-filter: blur(10px);
}
.footer-title { .footer-title {
color: rgba(255, 255, 255, 92%); color: rgba(255, 255, 255, 92%);
} }

View File

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

View File

@@ -45,7 +45,7 @@ const sections: Section[] = [
<p class="text-body-1 mb-6"> <p class="text-body-1 mb-6">
This Acceptable Use Policy ("AUP") governs the use of all services provided by {{ companyName }} 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 ("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 By using our Services, you agree to comply with this policy. Violations may result in suspension
or termination of your account. or termination of your account.
</p> </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> <script lang="ts" setup>
import { useForm } from '@inertiajs/vue3' import { useForm } from '@inertiajs/vue3'
import MarketingLayout from '@/Layouts/MarketingLayout.vue' import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
import AppTextField from '@/Components/app-form-elements/AppTextField.vue' import AppTextField from '@/Components/app-form-elements/AppTextField.vue'
import AppSelect from '@/Components/app-form-elements/AppSelect.vue' import AppSelect from '@/Components/app-form-elements/AppSelect.vue'
import AppTextarea from '@/Components/app-form-elements/AppTextarea.vue' import AppTextarea from '@/Components/app-form-elements/AppTextarea.vue'
@@ -39,13 +40,12 @@ const contactInfo = [
<template> <template>
<div> <div>
<VContainer class="py-16"> <VContainer class="marketing-section">
<div class="text-center mb-12"> <SectionHeader
<h1 class="text-h2 font-weight-bold mb-3">Contact Us</h1> label="Get in Touch"
<p class="text-h6 text-medium-emphasis font-weight-regular"> title="Contact Us"
Have a question? We'd love to hear from you. subtitle="Have a question? We'd love to hear from you."
</p> />
</div>
<VRow> <VRow>
<!-- Contact Form --> <!-- Contact Form -->
@@ -115,7 +115,7 @@ const contactInfo = [
<!-- Contact Info --> <!-- Contact Info -->
<VCol cols="12" md="5"> <VCol cols="12" md="5">
<div class="d-flex flex-column ga-4"> <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"> <VCardText class="d-flex align-center ga-4">
<VAvatar color="primary" variant="tonal" size="48"> <VAvatar color="primary" variant="tonal" size="48">
<VIcon :icon="info.icon" size="24" /> <VIcon :icon="info.icon" size="24" />
@@ -140,8 +140,9 @@ const contactInfo = [
<p class="text-body-2 mb-3"> <p class="text-body-2 mb-3">
Our support team is available 24/7 through your account dashboard. Our support team is available 24/7 through your account dashboard.
</p> </p>
<VBtn variant="outlined" size="small"> <VBtn variant="outlined" size="small" href="https://ezscale.support/en/knowledgebase" target="_blank">
Visit Knowledge Base Visit Knowledge Base
<VIcon icon="tabler-external-link" end size="16" />
</VBtn> </VBtn>
</VCardText> </VCardText>
</VCard> </VCard>

View File

@@ -2,28 +2,28 @@
import { usePage } from '@inertiajs/vue3' import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue' import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue' import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout }) 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 { interface PageProps {
plans: Plan[]
domains: { marketing: string; account: string; admin: string } domains: { marketing: string; account: string; admin: string }
} }
const page = usePage() const page = usePage()
const props = computed(() => page.props as unknown as PageProps) const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed(() => `https://${props.value.domains?.account}`) const accountUrl = computed(() => `https://${props.value.domains?.account}`)
const plans = computed(() => props.value.plans || [])
interface ServerConfig {
model: string
formFactor: string
cpu: string
coresThreads: string
clockSpeed: string
ram: string
bays: string
price: string
inStock: boolean
}
const features = [ const features = [
{ icon: 'tabler-cpu', title: 'Dedicated Hardware', description: 'No shared resources — all CPU, RAM, and storage are exclusively yours.' }, { 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.' }, { 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 = [ const included = [
{ icon: 'tabler-world', label: '10 TB Bandwidth' }, { icon: 'tabler-world', label: '10 TB Bandwidth' },
{ icon: 'tabler-network', label: '1 Gbps Port' }, { icon: 'tabler-network', label: '1 Gbps Port' },
@@ -133,15 +42,30 @@ const included = [
{ icon: 'tabler-hexagons', label: '1x /64 IPv6 Subnet' }, { icon: 'tabler-hexagons', label: '1x /64 IPv6 Subnet' },
{ icon: 'tabler-dashboard', label: 'SynergyCP Panel' }, { 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> </script>
<template> <template>
<div> <div>
<!-- Hero --> <!-- 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"> <VContainer class="text-center">
<VChip color="success" variant="tonal" class="mb-4">Dedicated Servers</VChip> <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;"> <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. Enterprise-grade Dell PowerEdge servers with full root access, SynergyCP management, and same-day deployment from our Atlanta datacenter.
</p> </p>
@@ -155,14 +79,17 @@ const included = [
</div> </div>
<!-- Features --> <!-- Features -->
<VContainer class="py-16"> <VContainer class="marketing-section">
<div class="text-center mb-12"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Enterprise Hardware</h2> label="Features"
<p class="text-body-1 text-medium-emphasis">Every dedicated server comes with these features included.</p> label-color="success"
</div> title="Enterprise Hardware"
highlight-word="Hardware"
subtitle="Every dedicated server comes with these features included."
/>
<VRow> <VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="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"> <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"> <VAvatar color="success" variant="tonal" size="44">
<VIcon :icon="feature.icon" size="22" /> <VIcon :icon="feature.icon" size="22" />
</VAvatar> </VAvatar>
@@ -176,39 +103,40 @@ const included = [
</VContainer> </VContainer>
<!-- Server Configurations --> <!-- Server Configurations -->
<div class="bg-surface-variant py-16"> <div class="section-alt-bg marketing-section">
<VContainer> <VContainer>
<div class="text-center mb-8"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Server Configurations</h2> label="Servers"
<p class="text-body-1 text-medium-emphasis">Real Dell PowerEdge servers. Storage sold separately -- configure your drives at checkout.</p> label-color="success"
</div> title="Server Configurations"
highlight-word="Configurations"
subtitle="Real Dell PowerEdge servers. Storage sold separately -- configure your drives at checkout."
/>
<VRow> <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 <VCard
variant="outlined" variant="outlined"
class="h-100" :class="['h-100 feature-card-hover', `fade-in-up stagger-delay-${Math.min(index + 1, 8)}`, { 'server-card-unavailable': !isInStock(plan) }]"
:class="{ 'server-card-unavailable': !server.inStock }"
:style="server.inStock ? {} : { opacity: 0.7 }"
> >
<VCardText class="pa-5"> <VCardText class="pa-5">
<!-- Header with model and stock status --> <!-- Header with model and stock status -->
<div class="d-flex align-center justify-space-between mb-1"> <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 <VChip
:color="server.inStock ? 'success' : 'error'" :color="isInStock(plan) ? 'success' : 'error'"
size="small" size="small"
variant="tonal" variant="tonal"
> >
{{ server.inStock ? 'In Stock' : 'Sold Out' }} {{ isInStock(plan) ? 'In Stock' : 'Sold Out' }}
</VChip> </VChip>
</div> </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 --> <!-- Price -->
<div class="mb-4"> <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> <span class="text-body-2 text-medium-emphasis">/mo</span>
</div> </div>
@@ -218,25 +146,25 @@ const included = [
<div class="mb-4"> <div class="mb-4">
<div class="d-flex align-center ga-2 py-1"> <div class="d-flex align-center ga-2 py-1">
<VIcon icon="tabler-cpu" size="16" color="medium-emphasis" /> <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>
<div class="d-flex align-center ga-2 py-1"> <div class="d-flex align-center ga-2 py-1">
<VIcon icon="tabler-topology-star-3" size="16" color="medium-emphasis" /> <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>
<div class="d-flex align-center ga-2 py-1"> <div class="d-flex align-center ga-2 py-1">
<VIcon icon="tabler-database" size="16" color="medium-emphasis" /> <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>
<div class="d-flex align-center ga-2 py-1"> <div class="d-flex align-center ga-2 py-1">
<VIcon icon="tabler-server" size="16" color="medium-emphasis" /> <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>
</div> </div>
<!-- Order Button --> <!-- Order Button -->
<a <a
v-if="server.inStock" v-if="isInStock(plan)"
:href="accountUrl + '/register'" :href="accountUrl + '/register'"
class="text-decoration-none d-block" class="text-decoration-none d-block"
> >
@@ -261,11 +189,14 @@ const included = [
</div> </div>
<!-- Included With Every Server --> <!-- Included With Every Server -->
<VContainer class="py-16"> <VContainer class="marketing-section">
<div class="text-center mb-8"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Included With Every Server</h2> label="Included"
<p class="text-body-1 text-medium-emphasis">No hidden fees. All servers come with these essentials.</p> label-color="success"
</div> title="Included With Every Server"
highlight-word="Every"
subtitle="No hidden fees. All servers come with these essentials."
/>
<VRow justify="center"> <VRow justify="center">
<VCol v-for="item in included" :key="item.label" cols="6" sm="4" md="2"> <VCol v-for="item in included" :key="item.label" cols="6" sm="4" md="2">
<div class="text-center"> <div class="text-center">
@@ -279,7 +210,7 @@ const included = [
</VContainer> </VContainer>
<!-- CTA --> <!-- 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"> <VContainer class="text-center">
<h2 class="text-h4 font-weight-bold mb-3">Need a Custom Configuration?</h2> <h2 class="text-h4 font-weight-bold mb-3">Need a Custom Configuration?</h2>
<p class="text-body-1 text-medium-emphasis mb-6"> <p class="text-body-1 text-medium-emphasis mb-6">
@@ -302,3 +233,9 @@ const included = [
</div> </div>
</div> </div>
</template> </template>
<style lang="scss" scoped>
.server-card-unavailable {
opacity: 0.7;
}
</style>

View File

@@ -2,27 +2,57 @@
import { usePage } from '@inertiajs/vue3' import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue' import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue' import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout }) defineOptions({ layout: MarketingLayout })
interface Plan {
id: number
name: string
slug: string
description: string | null
price: string
features: Record<string, string | number> | null
}
interface PageProps { interface PageProps {
plans: Plan[]
domains: { marketing: string; account: string; admin: string } 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 page = usePage()
const props = computed(() => page.props as unknown as PageProps) 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 = [ function formatPrice(plan: Plan): string {
{ name: 'Minecraft', icon: 'tabler-cube', description: 'Java & Bedrock editions with mod support.', startingAt: '$7.99/mo' }, const price = parseFloat(plan.price) || 0
{ name: 'Rust', icon: 'tabler-shield', description: 'High-performance Rust servers with Oxide support.', startingAt: '$14.99/mo' }, return price % 1 === 0 ? `$${price}` : `$${price.toFixed(2)}`
{ 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' },
]
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-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-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.' }, { icon: 'tabler-shield-check', title: 'DDoS Protection', description: 'Always-on protection to keep your server online.' },
@@ -33,40 +63,64 @@ const features = [
<template> <template>
<div> <div>
<!-- Hero --> <!-- 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"> <VContainer class="text-center">
<VChip color="error" variant="tonal" class="mb-4">Game Servers</VChip> <VChip color="error" variant="tonal" class="mb-4">Game Servers</VChip>
<h1 class="text-h2 font-weight-bold mb-3">Game Server Hosting</h1> <h1 class="text-h2 font-weight-bold mb-3">
<p class="text-h6 text-medium-emphasis font-weight-regular mb-8 mx-auto" style="max-width: 600px;"> 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. Low-latency game server hosting with instant setup, mod support, and DDoS protection.
</p> </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"> <VBtn color="error" size="x-large" rounded="lg">
Get Your Server Notify Me
<VIcon icon="tabler-arrow-right" end /> <VIcon icon="tabler-bell" end />
</VBtn> </VBtn>
</a> </div>
</VContainer> </VContainer>
</div> </div>
<!-- Supported Games --> <!-- Supported Games -->
<VContainer class="py-16"> <VContainer class="marketing-section">
<div class="text-center mb-12"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Supported Games</h2> label="Games"
<p class="text-body-1 text-medium-emphasis">Popular titles with more being added regularly.</p> label-color="error"
</div> title="Supported Games"
highlight-word="Games"
subtitle="Popular titles with more being added regularly."
/>
<VRow> <VRow>
<VCol v-for="game in games" :key="game.name" cols="12" sm="6" md="4"> <VCol v-for="(plan, index) in plans" :key="plan.id" cols="12" sm="6" md="4">
<VCard variant="outlined" class="h-100"> <VCard variant="outlined" :class="['h-100 feature-card-hover', `fade-in-up stagger-delay-${index + 1}`]">
<VCardText class="d-flex align-center ga-4"> <VCardText class="pa-5">
<div class="d-flex align-center ga-4 mb-3">
<VAvatar color="error" variant="tonal" size="48"> <VAvatar color="error" variant="tonal" size="48">
<VIcon :icon="game.icon" size="24" /> <VIcon :icon="getIcon(plan)" size="24" />
</VAvatar> </VAvatar>
<div> <div>
<h3 class="text-subtitle-1 font-weight-bold">{{ game.name }}</h3> <h3 class="text-subtitle-1 font-weight-bold">{{ plan.name }}</h3>
<p class="text-body-2 text-medium-emphasis mb-1">{{ game.description }}</p> <p class="text-body-2 text-medium-emphasis mb-0">{{ plan.description }}</p>
<span class="text-body-2 text-error font-weight-medium">From {{ game.startingAt }}</span> </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> </div>
</VCardText> </VCardText>
</VCard> </VCard>
@@ -75,14 +129,17 @@ const features = [
</VContainer> </VContainer>
<!-- Features --> <!-- Features -->
<div class="bg-surface-variant py-16"> <div class="section-alt-bg marketing-section">
<VContainer> <VContainer>
<div class="text-center mb-12"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Why Gamers Choose EZSCALE</h2> label="Features"
</div> label-color="error"
title="Why Gamers Choose EZSCALE"
highlight-word="EZSCALE"
/>
<VRow> <VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="3"> <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"> <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"> <VAvatar color="error" variant="tonal" size="56" class="mb-3">
<VIcon :icon="feature.icon" size="28" /> <VIcon :icon="feature.icon" size="28" />
</VAvatar> </VAvatar>
@@ -93,5 +150,26 @@ const features = [
</VRow> </VRow>
</VContainer> </VContainer>
</div> </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> </div>
</template> </template>

View File

@@ -2,6 +2,7 @@
import { usePage } from '@inertiajs/vue3' import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue' import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue' import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout }) defineOptions({ layout: MarketingLayout })
@@ -13,101 +14,442 @@ const page = usePage()
const props = computed(() => page.props as unknown as PageProps) const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed(() => `https://${props.value.domains?.account}`) 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', 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-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-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' }, { 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 = [ interface ValueProp {
{ value: '10,000+', label: 'Active Customers' }, icon: string
{ value: '99.99%', label: 'Uptime SLA' }, title: string
{ value: '50+', label: 'Server Locations' }, description: string
{ value: '24/7', label: 'Expert Support' }, 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> </script>
<template> <template>
<div> <div>
<!-- Hero Section --> <!-- 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> <VContainer>
<VRow align="center" justify="center"> <VRow
<VCol cols="12" md="8" class="text-center"> align="center"
justify="center"
>
<VCol
cols="12"
md="8"
class="text-center"
>
<h1 class="text-h2 text-md-h1 font-weight-bold mb-4"> <h1 class="text-h2 text-md-h1 font-weight-bold mb-4">
Cloud Hosting Cloud Hosting
<span class="text-primary">Made Simple</span> <span class="hero-gradient-text">Made Simple</span>
</h1> </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. VPS, Dedicated Servers, Web Hosting, and Game Servers. Deploy in minutes with enterprise-grade infrastructure.
</p> </p>
<div class="d-flex justify-center ga-4 flex-wrap">
<a :href="accountUrl + '/register'" class="text-decoration-none"> <div class="d-flex justify-center ga-4 flex-wrap mb-4">
<VBtn color="primary" size="x-large" rounded="lg"> <a
:href="accountUrl + '/register'"
class="text-decoration-none"
>
<VBtn
color="primary"
size="x-large"
rounded="lg"
>
Start Free Trial Start Free Trial
<VIcon icon="tabler-arrow-right" end /> <VIcon
icon="tabler-arrow-right"
end
/>
</VBtn> </VBtn>
</a> </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 View Pricing
</VBtn> </VBtn>
</a> </a>
</div> </div>
<p class="text-body-2 text-disabled">
No credit card required. Deploy in 60 seconds.
</p>
</VCol> </VCol>
</VRow> </VRow>
</VContainer> </VContainer>
</div> </div>
<!-- Features Section --> <!-- Products Section -->
<VContainer class="py-16"> <VContainer class="marketing-section">
<div class="text-center mb-12"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Our Products</h2> label="Our Products"
<p class="text-body-1 text-medium-emphasis">Everything you need to build, deploy, and scale.</p> title="Everything You Need to Build, Deploy, and Scale"
</div> highlight-word="Scale"
/>
<VRow> <VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="3"> <VCol
<VCard variant="outlined" class="h-100" :href="feature.href"> 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"> <VCardText class="text-center pa-6">
<VAvatar :color="feature.color" variant="tonal" size="64" class="mb-4"> <VAvatar
<VIcon :icon="feature.icon" size="32" /> :color="product.color"
variant="tonal"
size="64"
class="mb-4"
>
<VIcon
:icon="product.icon"
size="32"
/>
</VAvatar> </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> </VCardText>
</VCard> </VCard>
</VCol> </VCol>
</VRow> </VRow>
</VContainer> </VContainer>
<!-- Stats Section --> <!-- Why Choose EZSCALE Section -->
<div class="bg-surface-variant py-16"> <div class="section-alt-bg">
<VContainer> <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> <VRow>
<VCol v-for="stat in stats" :key="stat.label" cols="6" md="3" class="text-center"> <VCol
<div class="text-h3 font-weight-bold text-primary">{{ stat.value }}</div> v-for="(prop, index) in valueProps"
<div class="text-body-1 text-medium-emphasis mt-1">{{ stat.label }}</div> :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> </VCol>
</VRow> </VRow>
</VContainer> </VContainer>
</div> </div>
<!-- CTA Section --> <!-- Stats Section -->
<VContainer class="py-16"> <div class="section-alt-bg">
<VCard color="primary" class="text-center pa-12"> <VContainer class="marketing-section">
<h2 class="text-h3 font-weight-bold text-white mb-3">Ready to get started?</h2> <VRow>
<p class="text-h6 font-weight-regular mb-6" style="opacity: 0.9;"> <VCol
Deploy your first server in under 60 seconds. 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> </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 Create Free Account
<VIcon icon="tabler-arrow-right" end /> <VIcon
icon="tabler-arrow-right"
end
/>
</VBtn> </VBtn>
</a> </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> </VCard>
</VContainer> </VContainer>
</div> </div>
</template> </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 { usePage } from '@inertiajs/vue3'
import { computed } from 'vue' import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue' import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout }) 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 accountUrl = computed(() => `https://${props.value.domains?.account}`)
const plans = computed(() => props.value.plans || []) 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 { function getMonthlyPrice(plan: Plan): string {
const price = parseFloat(plan.price ?? '0') || 0 const price = parseFloat(plan.price ?? '0') || 0
return price % 1 === 0 ? price.toString() : price.toFixed(2) 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 return plans.value.length > 1 && index === 1
} }
// Feature comparison data // Feature comparison data — excluding internal keys
const featureComparison = computed(() => { const featureComparison = computed(() => {
if (plans.value.length === 0) return [] if (plans.value.length === 0) return []
const allFeatures = new Set<string>() const allFeatures = new Set<string>()
plans.value.forEach(plan => { plans.value.forEach(plan => {
if (plan.features) { 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 => ({ return Array.from(allFeatures).map(feature => ({
feature, feature: humanizeFeatureKey(feature),
plans: plans.value.map(plan => ({ plans: plans.value.map(plan => ({
value: plan.features?.[feature] ?? null, value: plan.features?.[feature] ?? null,
})), })),
@@ -86,17 +130,11 @@ const faqs = [
<VCard class="pricing-card" flat> <VCard class="pricing-card" flat>
<!-- Plan Cards Section --> <!-- Plan Cards Section -->
<VContainer> <VContainer>
<div class="text-center"> <SectionHeader
<h3 class="text-h3 pricing-title mb-2"> label="Pricing"
Pricing Plans title="Pricing Plans"
</h3> subtitle="All plans include 24/7 monitoring and enterprise-grade infrastructure. Choose the best plan to fit your needs."
<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>
<!-- Plan Cards --> <!-- Plan Cards -->
<VRow v-if="plans.length"> <VRow v-if="plans.length">
@@ -109,6 +147,7 @@ const faqs = [
<VCard <VCard
flat flat
border border
class="feature-card-hover"
:class="isPopular(index) ? 'border-primary border-opacity-100' : ''" :class="isPopular(index) ? 'border-primary border-opacity-100' : ''"
> >
<VCardText <VCardText
@@ -167,8 +206,8 @@ const faqs = [
<!-- Plan Features --> <!-- Plan Features -->
<VList class="card-list mb-4"> <VList class="card-list mb-4">
<VListItem <VListItem
v-for="(value, feature) in plan.features" v-for="feat in getDisplayFeatures(plan)"
:key="String(feature)" :key="feat.key"
> >
<template #prepend> <template #prepend>
<VIcon <VIcon
@@ -179,7 +218,7 @@ const faqs = [
</template> </template>
<VListItemTitle class="text-body-1"> <VListItemTitle class="text-body-1">
{{ value }} {{ feat.value }}
</VListItemTitle> </VListItemTitle>
</VListItem> </VListItem>
</VList> </VList>
@@ -209,17 +248,26 @@ const faqs = [
We're finalizing our plans. Check back soon or sign up to be notified. We're finalizing our plans. Check back soon or sign up to be notified.
</p> </p>
</VCard> </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> </VContainer>
<!-- Feature Comparison Table --> <!-- Feature Comparison Table -->
<VContainer v-if="plans.length && featureComparison.length"> <VContainer v-if="plans.length && featureComparison.length">
<VCardText class="text-center py-16 pricing-section"> <VCardText class="text-center py-16 pricing-section">
<h3 class="text-h3 mb-2"> <SectionHeader
Pick a plan that works best for you label="Compare Plans"
</h3> title="Pick a Plan That Works Best for You"
<p class="text-body-1"> subtitle="Stay cool, we have a 30-day money back guarantee!"
Stay cool, we have a 30-day money back guarantee! />
</p>
<VTable class="text-no-wrap border rounded pricing-table"> <VTable class="text-no-wrap border rounded pricing-table">
<thead> <thead>

View File

@@ -72,7 +72,8 @@ const products = [
</VAvatar> </VAvatar>
<div> <div>
<h2 class="text-h5 font-weight-bold">{{ product.title }}</h2> <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>
</div> </div>

View File

@@ -70,7 +70,7 @@ const creditTiers: CreditTier[] = [
<!-- Introduction --> <!-- Introduction -->
<p class="text-body-1 mb-6"> <p class="text-body-1 mb-6">
This Service Level Agreement ("SLA") is part of the 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 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. uptime commitments and the remedies available to you if we fail to meet them.
</p> </p>

View File

@@ -77,9 +77,9 @@ const sections: Section[] = [
<p class="text-body-1 mb-3"> <p class="text-body-1 mb-3">
By creating an account, placing an order, or otherwise using any of our Services, you acknowledge 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 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="/privacy-policy" 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="/acceptable-use" 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="/sla" class="text-primary text-decoration-none font-weight-medium">Service Level Agreement</Link>.
</p> </p>
<p class="text-body-1 mb-3"> <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 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> <h2 class="text-h5 font-weight-bold mb-4">6. Acceptable Use</h2>
<p class="text-body-1 mb-3"> <p class="text-body-1 mb-3">
Your use of the Services is subject to our 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: 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> </p>
<ul class="text-body-1 mb-3 ml-6"> <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> <h2 class="text-h5 font-weight-bold mb-4">7. Service Level Agreement</h2>
<p class="text-body-1 mb-3"> <p class="text-body-1 mb-3">
Our commitment to uptime and service reliability is detailed in our 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. The SLA outlines our uptime guarantees, credit policies, and the process for reporting and claiming service disruptions.
</p> </p>
<p class="text-body-1"> <p class="text-body-1">

View File

@@ -2,20 +2,22 @@
import { usePage } from '@inertiajs/vue3' import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue' import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue' import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout }) defineOptions({ layout: MarketingLayout })
interface PageProps { interface Plan {
domains: { marketing: string; account: string; admin: string } id: number
name: string
slug: string
price: string
features: Record<string, string | number> | null
stock_quantity: number | null
} }
interface VpsPlan { interface PageProps {
name: string plans: Plan[]
cpu: string domains: { marketing: string; account: string; admin: string }
ram: string
storage: string
bandwidth: string
price: string
} }
interface Feature { interface Feature {
@@ -27,6 +29,13 @@ interface Feature {
const page = usePage() const page = usePage()
const props = computed(() => page.props as unknown as PageProps) const props = computed(() => page.props as unknown as PageProps)
const accountUrl = computed<string>(() => `https://${props.value.domains?.account}`) 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[] = [ const features: Feature[] = [
{ icon: 'tabler-database', title: 'RAID 10 SSD Storage', description: 'Redundant SSD arrays for fast read/write speeds and data protection.' }, { 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.' }, { 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[] = [ const includedFeatures: string[] = [
'1 IPv4 & 1 /64 IPv6', '1 IPv4 & 1 /64 IPv6',
'Near instant provisioning', 'Near instant provisioning',
'VM backups', 'VM backups',
'Windows (BYOL) & Linux support', 'Windows (BYOL) & Linux support',
'Intel E5-2680 v4 processors',
'Full root access', 'Full root access',
'VirtFusion control panel', 'VirtFusion control panel',
'RAID 10 backed storage', 'RAID 10 backed storage',
'14-day money back guarantee', '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> </script>
<template> <template>
<div> <div>
<!-- Hero --> <!-- 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"> <VContainer class="text-center">
<VChip color="primary" variant="tonal" class="mb-4">VPS Hosting</VChip> <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;"> <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. High-performance VPS hosting with RAID 10 SSD storage, dedicated resources, and full root access from our Atlanta, GA datacenter.
</p> </p>
<p class="text-body-1 text-medium-emphasis mb-8"> <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> </p>
<a :href="accountUrl + '/register'" class="text-decoration-none"> <a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="primary" size="x-large" rounded="lg"> <VBtn color="primary" size="x-large" rounded="lg">
@@ -85,13 +102,16 @@ const includedFeatures: string[] = [
</div> </div>
<!-- Features --> <!-- Features -->
<VContainer class="py-16"> <VContainer class="marketing-section">
<div class="text-center mb-12"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Why Choose EZSCALE VPS?</h2> label="Features"
</div> title="Why Choose EZSCALE VPS?"
highlight-word="EZSCALE"
subtitle="Enterprise-grade infrastructure with the simplicity you deserve."
/>
<VRow> <VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="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"> <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"> <VAvatar color="primary" variant="tonal" size="44">
<VIcon :icon="feature.icon" size="22" /> <VIcon :icon="feature.icon" size="22" />
</VAvatar> </VAvatar>
@@ -105,16 +125,16 @@ const includedFeatures: string[] = [
</VContainer> </VContainer>
<!-- Plans Table --> <!-- Plans Table -->
<div class="bg-surface-variant py-16"> <div class="section-alt-bg marketing-section">
<VContainer> <VContainer>
<div class="text-center mb-8"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">VPS Plans</h2> label="Pricing"
<p class="text-body-1 text-medium-emphasis"> title="VPS Plans"
All plans hosted in our Atlanta, GA datacenter. DDoS protection, full root access, and VirtFusion panel included. highlight-word="Plans"
</p> subtitle="All plans hosted in our Atlanta, GA datacenter. DDoS protection, full root access, and VirtFusion panel included."
</div> />
<VCard> <VCard class="feature-card-hover">
<VTable> <VTable>
<thead> <thead>
<tr> <tr>
@@ -128,13 +148,13 @@ const includedFeatures: string[] = [
</tr> </tr>
</thead> </thead>
<tbody> <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 class="font-weight-bold">{{ plan.name }}</td>
<td>{{ plan.cpu }}</td> <td>{{ getFeature(plan, 'cpu') }}</td>
<td>{{ plan.ram }}</td> <td>{{ getFeature(plan, 'ram') }}</td>
<td>{{ plan.storage }}</td> <td>{{ getFeature(plan, 'storage') }}</td>
<td>{{ plan.bandwidth }}</td> <td>{{ getFeature(plan, 'bandwidth') }}</td>
<td class="text-primary font-weight-bold">{{ plan.price }}/mo</td> <td class="text-primary font-weight-bold">{{ formatPrice(plan) }}/mo</td>
<td> <td>
<a :href="accountUrl + '/register'" class="text-decoration-none"> <a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="primary" size="small" variant="tonal">Order Now</VBtn> <VBtn color="primary" size="small" variant="tonal">Order Now</VBtn>
@@ -146,7 +166,7 @@ const includedFeatures: string[] = [
</VCard> </VCard>
<!-- Included with all plans --> <!-- 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> <h3 class="text-h5 font-weight-bold mb-4 text-center">Included With All Plans</h3>
<VRow> <VRow>
<VCol <VCol
@@ -165,5 +185,28 @@ const includedFeatures: string[] = [
</VCard> </VCard>
</VContainer> </VContainer>
</div> </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> </div>
</template> </template>

View File

@@ -2,16 +2,22 @@
import { usePage } from '@inertiajs/vue3' import { usePage } from '@inertiajs/vue3'
import { computed } from 'vue' import { computed } from 'vue'
import MarketingLayout from '@/Layouts/MarketingLayout.vue' import MarketingLayout from '@/Layouts/MarketingLayout.vue'
import SectionHeader from '@/Components/Marketing/SectionHeader.vue'
defineOptions({ layout: MarketingLayout }) defineOptions({ layout: MarketingLayout })
interface PageProps { interface Plan {
domains: { marketing: string; account: string; admin: string } id: number
name: string
slug: string
price: string
features: Record<string, string | number> | null
} }
const page = usePage() interface PageProps {
const props = computed(() => page.props as unknown as PageProps) plans: Plan[]
const accountUrl = computed(() => `https://${props.value.domains?.account}`) domains: { marketing: string; account: string; admin: string }
}
interface Feature { interface Feature {
icon: string icon: string
@@ -19,18 +25,16 @@ interface Feature {
description: string description: string
} }
interface Plan { const page = usePage()
name: string const props = computed(() => page.props as unknown as PageProps)
storage: string const accountUrl = computed<string>(() => `https://${props.value.domains?.account}`)
databases: string const plans = computed(() => props.value.plans || [])
email: string
domains: string const startingPrice = computed<string>(() => {
bandwidth: string if (plans.value.length === 0) return '2.39'
ram: string const lowest = Math.min(...plans.value.map(p => parseFloat(p.price)))
cores: string return lowest % 1 === 0 ? lowest.toString() : lowest.toFixed(2)
price: string })
popular?: boolean
}
const features: Feature[] = [ const features: Feature[] = [
{ icon: 'tabler-layout-dashboard', title: 'Enhance Control Panel', description: 'Modern, intuitive control panel for effortless website and server management.' }, { 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.' }, { 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[] = [ const includedFeatures: string[] = [
'Free SSL', 'Free SSL',
'Cloudflare DNS', 'Cloudflare DNS',
@@ -97,18 +53,50 @@ const includedFeatures: string[] = [
'SSH Access', 'SSH Access',
'Enhance Panel', '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> </script>
<template> <template>
<div> <div>
<!-- Hero --> <!-- 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"> <VContainer class="text-center">
<VChip color="warning" variant="tonal" class="mb-4">Web Hosting</VChip> <VChip color="warning" variant="tonal" class="mb-4">Web Hosting</VChip>
<h1 class="text-h2 font-weight-bold mb-3">Managed Web Hosting</h1> <h1 class="text-h2 font-weight-bold mb-3">
<p class="text-h6 text-medium-emphasis font-weight-regular mb-8 mx-auto" style="max-width: 600px;"> 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. Fast, secure, and reliable web hosting powered by Enhance with free SSL, Cloudflare DNS, and Redis caching.
</p> </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"> <a :href="accountUrl + '/register'" class="text-decoration-none">
<VBtn color="warning" size="x-large" rounded="lg"> <VBtn color="warning" size="x-large" rounded="lg">
Get Started Get Started
@@ -119,16 +107,17 @@ const includedFeatures: string[] = [
</div> </div>
<!-- Features --> <!-- Features -->
<VContainer class="py-16"> <VContainer class="marketing-section">
<div class="text-center mb-12"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Everything You Need</h2> label="Features"
<p class="text-body-1 text-medium-emphasis mx-auto" style="max-width: 550px;"> label-color="warning"
Every plan comes loaded with the tools and features you need to build and grow your website. title="Everything You Need"
</p> highlight-word="Need"
</div> subtitle="Every plan comes loaded with the tools and features you need to build and grow your website."
/>
<VRow> <VRow>
<VCol v-for="feature in features" :key="feature.title" cols="12" sm="6" md="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"> <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"> <VAvatar color="warning" variant="tonal" size="44">
<VIcon :icon="feature.icon" size="22" /> <VIcon :icon="feature.icon" size="22" />
</VAvatar> </VAvatar>
@@ -142,25 +131,26 @@ const includedFeatures: string[] = [
</VContainer> </VContainer>
<!-- Plans --> <!-- Plans -->
<div class="bg-surface-variant py-16"> <div class="section-alt-bg marketing-section">
<VContainer> <VContainer>
<div class="text-center mb-8"> <SectionHeader
<h2 class="text-h3 font-weight-bold mb-3">Hosting Plans</h2> label="Pricing"
<p class="text-body-1 text-medium-emphasis mx-auto" style="max-width: 550px;"> label-color="warning"
Choose the plan that fits your needs. All plans include free SSL, Cloudflare DNS, and the Enhance control panel. title="Hosting Plans"
</p> highlight-word="Plans"
</div> subtitle="Choose the plan that fits your needs. All plans include free SSL, Cloudflare DNS, and the Enhance control panel."
/>
<VRow justify="center"> <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 <VCard
:variant="plan.popular ? 'elevated' : 'outlined'" :variant="index === 1 ? 'elevated' : 'outlined'"
:class="['h-100', { 'border-warning border-opacity-100': plan.popular }]" :class="['h-100 feature-card-hover', `fade-in-up stagger-delay-${index + 1}`, { 'border-warning border-opacity-100': index === 1 }]"
:elevation="plan.popular ? 8 : 0" :elevation="index === 1 ? 8 : 0"
> >
<VCardText class="pa-6 text-center"> <VCardText class="pa-6 text-center">
<VChip <VChip
v-if="plan.popular" v-if="index === 1"
color="warning" color="warning"
size="small" size="small"
class="mb-2" class="mb-2"
@@ -170,53 +160,17 @@ const includedFeatures: string[] = [
<h3 class="text-h5 font-weight-bold mb-1">{{ plan.name }}</h3> <h3 class="text-h5 font-weight-bold mb-1">{{ plan.name }}</h3>
<div class="text-h4 font-weight-bold text-warning mb-4"> <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> </div>
<VDivider class="mb-4" /> <VDivider class="mb-4" />
<VList density="compact" class="pa-0"> <VList density="compact" class="pa-0">
<VListItem class="px-0"> <VListItem v-for="row in featureRows" :key="row.key" class="px-0">
<template #prepend> <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> </template>
<VListItemTitle class="text-body-2">{{ plan.storage }}</VListItemTitle> <VListItemTitle class="text-body-2">{{ getFeature(plan, row.key) }}</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>
</VListItem> </VListItem>
</VList> </VList>
@@ -236,8 +190,8 @@ const includedFeatures: string[] = [
<a :href="accountUrl + '/register'" class="text-decoration-none d-block"> <a :href="accountUrl + '/register'" class="text-decoration-none d-block">
<VBtn <VBtn
:color="plan.popular ? 'warning' : 'warning'" color="warning"
:variant="plan.popular ? 'elevated' : 'tonal'" :variant="index === 1 ? 'elevated' : 'tonal'"
block block
> >
Choose Plan Choose Plan
@@ -249,5 +203,28 @@ const includedFeatures: string[] = [
</VRow> </VRow>
</VContainer> </VContainer>
</div> </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> </div>
</template> </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: '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: '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: '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' }, { title: 'Pricing', href: '/pricing' },

View File

@@ -9,10 +9,55 @@ use Inertia\Inertia;
Route::get('/', fn () => Inertia::render('Marketing/Home'))->name('home'); Route::get('/', fn () => Inertia::render('Marketing/Home'))->name('home');
Route::get('/products', fn () => Inertia::render('Marketing/Products'))->name('products'); Route::get('/products', fn () => Inertia::render('Marketing/Products'))->name('products');
Route::get('/vps-hosting', fn () => Inertia::render('Marketing/VpsHosting'))->name('vps-hosting'); Route::get('/vps-hosting', function () {
Route::get('/dedicated-servers', fn () => Inertia::render('Marketing/DedicatedServers'))->name('dedicated-servers'); $plans = Plan::query()
Route::get('/web-hosting', fn () => Inertia::render('Marketing/WebHosting'))->name('web-hosting'); ->where('service_type', 'vps')
Route::get('/game-servers', fn () => Inertia::render('Marketing/GameServers'))->name('game-servers'); ->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 () { Route::get('/pricing', function () {
$plans = Plan::query() $plans = Plan::query()
->where('status', 'active') ->where('status', 'active')