diff --git a/website/database/seeders/ConfigOptionSeeder.php b/website/database/seeders/ConfigOptionSeeder.php
index 3621328..880f402 100644
--- a/website/database/seeders/ConfigOptionSeeder.php
+++ b/website/database/seeders/ConfigOptionSeeder.php
@@ -498,6 +498,88 @@ class ConfigOptionSeeder extends Seeder
]);
$gen14Ipv4->plans()->syncWithoutDetaching($gen14AllPlans);
+
+ // ─── Dedicated 14th Gen — LFF Drive Bays ────────────────────────
+ $gen14Lff = PlanConfigGroup::updateOrCreate(
+ ['name' => 'Dedicated 14th Gen — LFF Drive Bays'],
+ [
+ 'description' => 'Pick a starter drive configuration for the 3.5" LFF bays. Customers needing a heterogeneous setup (mixed sizes per bay, hot spares, etc.) can request a custom layout via the post-order ticket flow.',
+ 'mode' => 'preset',
+ 'service_type' => 'dedicated',
+ 'is_active' => true,
+ 'sort_order' => 50,
+ ],
+ );
+
+ $gen14LffOption = $this->seedRadioOption($gen14Lff, 'LFF Drive Bays', false, 1);
+ $this->seedValues($gen14LffOption, [
+ ['label' => 'No drives — configure via ticket', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
+ ['label' => '2× 4 TB SATA HDD', 'value' => '2x4tb-hdd', 'monthly' => 24.00],
+ ['label' => '4× 4 TB SATA HDD', 'value' => '4x4tb-hdd', 'monthly' => 48.00],
+ ['label' => '2× 8 TB SATA HDD', 'value' => '2x8tb-hdd', 'monthly' => 40.00],
+ ['label' => '4× 8 TB SATA HDD', 'value' => '4x8tb-hdd', 'monthly' => 80.00],
+ ['label' => '8× 8 TB SATA HDD (R540 / R740xd LFF only)', 'value' => '8x8tb-hdd', 'monthly' => 160.00],
+ ['label' => '12× 8 TB SATA HDD (R740xd LFF only)', 'value' => '12x8tb-hdd', 'monthly' => 240.00],
+ ]);
+
+ $gen14LffPlanSlugs = ['r440-4lff', 'r540-8lff', 'r740xd-12lff'];
+ $gen14LffPlans = Plan::query()->whereIn('slug', $gen14LffPlanSlugs)->pluck('id');
+ $gen14Lff->plans()->syncWithoutDetaching($gen14LffPlans);
+
+ // ─── Dedicated 14th Gen — SFF Drive Bays ────────────────────────
+ $gen14Sff = PlanConfigGroup::updateOrCreate(
+ ['name' => 'Dedicated 14th Gen — SFF Drive Bays'],
+ [
+ 'description' => 'Pick a starter drive configuration for the 2.5" SFF bays. Mixed-size setups via post-order ticket.',
+ 'mode' => 'preset',
+ 'service_type' => 'dedicated',
+ 'is_active' => true,
+ 'sort_order' => 51,
+ ],
+ );
+
+ $gen14SffOption = $this->seedRadioOption($gen14Sff, 'SFF Drive Bays', false, 1);
+ $this->seedValues($gen14SffOption, [
+ ['label' => 'No drives — configure via ticket', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
+ ['label' => '2× 480 GB SATA SSD', 'value' => '2x480gb-ssd', 'monthly' => 20.00],
+ ['label' => '4× 480 GB SATA SSD', 'value' => '4x480gb-ssd', 'monthly' => 40.00],
+ ['label' => '2× 1.92 TB SATA SSD', 'value' => '2x1920gb-ssd', 'monthly' => 36.00],
+ ['label' => '4× 1.92 TB SATA SSD', 'value' => '4x1920gb-ssd', 'monthly' => 72.00],
+ ['label' => '8× 1.92 TB SATA SSD', 'value' => '8x1920gb-ssd', 'monthly' => 144.00],
+ ['label' => '16× 1.92 TB SATA SSD (R740 16-bay / R740xd 24-SFF only)', 'value' => '16x1920gb-ssd', 'monthly' => 288.00],
+ ['label' => '24× 1.92 TB SATA SSD (R740xd 24-SFF only)', 'value' => '24x1920gb-ssd', 'monthly' => 432.00],
+ ]);
+
+ $gen14SffPlanSlugs = ['r640-8sff', 'r740-16sff', 'r740xd-24sff'];
+ $gen14SffPlans = Plan::query()->whereIn('slug', $gen14SffPlanSlugs)->pluck('id');
+ $gen14Sff->plans()->syncWithoutDetaching($gen14SffPlans);
+
+ // ─── Dedicated 14th Gen — NVMe Drive Bays ───────────────────────
+ $gen14Nvme = PlanConfigGroup::updateOrCreate(
+ ['name' => 'Dedicated 14th Gen — NVMe Drive Bays'],
+ [
+ 'description' => 'Pick a starter NVMe drive configuration. U.2 bays direct-attach to CPU PCIe lanes — no RAID controller in the data path; software RAID (ZFS / mdraid / btrfs) only.',
+ 'mode' => 'preset',
+ 'service_type' => 'dedicated',
+ 'is_active' => true,
+ 'sort_order' => 52,
+ ],
+ );
+
+ $gen14NvmeOption = $this->seedRadioOption($gen14Nvme, 'NVMe Drive Bays', false, 1);
+ $this->seedValues($gen14NvmeOption, [
+ ['label' => 'No drives — configure via ticket', 'value' => 'none', 'monthly' => 0, 'is_default' => true],
+ ['label' => '2× 1 TB U.2 NVMe', 'value' => '2x1tb-nvme', 'monthly' => 44.00],
+ ['label' => '4× 1 TB U.2 NVMe', 'value' => '4x1tb-nvme', 'monthly' => 88.00],
+ ['label' => '2× 2 TB U.2 NVMe', 'value' => '2x2tb-nvme', 'monthly' => 96.00],
+ ['label' => '4× 2 TB U.2 NVMe', 'value' => '4x2tb-nvme', 'monthly' => 192.00],
+ ['label' => '8× 2 TB U.2 NVMe (R640 NVMe 10-bay / R740xd NVMe 24-bay)', 'value' => '8x2tb-nvme', 'monthly' => 384.00],
+ ['label' => '16× 2 TB U.2 NVMe (R740xd NVMe 24-bay only)', 'value' => '16x2tb-nvme', 'monthly' => 768.00],
+ ]);
+
+ $gen14NvmePlanSlugs = ['r640-10nvme', 'r740xd-24nvme'];
+ $gen14NvmePlans = Plan::query()->whereIn('slug', $gen14NvmePlanSlugs)->pluck('id');
+ $gen14Nvme->plans()->syncWithoutDetaching($gen14NvmePlans);
}
/**
diff --git a/website/resources/ts/Components/Marketing/Dedicated/DedicatedConfigurator/BuildSummary.vue b/website/resources/ts/Components/Marketing/Dedicated/DedicatedConfigurator/BuildSummary.vue
index 41d82ac..3e62c0d 100644
--- a/website/resources/ts/Components/Marketing/Dedicated/DedicatedConfigurator/BuildSummary.vue
+++ b/website/resources/ts/Components/Marketing/Dedicated/DedicatedConfigurator/BuildSummary.vue
@@ -175,7 +175,7 @@ function formatPrice(amount: number): string {
Setup fee
- Hardware acquisition · charged on first invoice
+ Server provisioning & deployment · charged on first invoice
@@ -188,7 +188,7 @@ function formatPrice(amount: number): string {
- Non-refundable once hardware is purchased. Switch to Semi-Annual or Annual to waive.
+ Non-refundable once your build starts. Switch to Semi-Annual or Annual to waive.
@@ -243,16 +243,9 @@ function formatPrice(amount: number): string {
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
- position: sticky;
- top: 24px;
- max-height: calc(100vh - 48px);
- overflow-y: auto;
-
- @media (max-width: 960px) {
- position: relative;
- top: auto;
- max-height: none;
- }
+ // Stickiness is owned by the parent grid cell (.detail-grid__summary) on
+ // the page — keeping it here would create a doubly-sticky effect that
+ // doesn't behave correctly. The card is just a plain card.
}
.build-summary__head {
diff --git a/website/resources/ts/Pages/Marketing/DedicatedServerDetail.vue b/website/resources/ts/Pages/Marketing/DedicatedServerDetail.vue
index 49816e1..de4a27c 100644
--- a/website/resources/ts/Pages/Marketing/DedicatedServerDetail.vue
+++ b/website/resources/ts/Pages/Marketing/DedicatedServerDetail.vue
@@ -145,18 +145,15 @@ const store = useDedicatedConfiguratorStore()
-
+
-
All main bays ship empty
-
- Drive selection isn't in the v1 self-serve configurator. Once you order, we'll reach out to confirm your drive layout — SATA / SAS / NVMe options, RAID/ZFS preference, and per-bay placement. Drives arrive separately and slot into the pre-installed trays during assembly.
-
+
Need a custom drive layout?
- Need specific drives sourced before you order? Open a ticket with your spec — we'll quote the drive cost and add it to your first invoice.
+ The drive bay configurator covers the most common configurations. If you need a heterogeneous setup (mixed sizes per bay, hot spares, RAID layout preference, specific drive vendor) — open a ticket with your spec and we'll build to it.
@@ -216,7 +213,10 @@ const store = useDedicatedConfiguratorStore()
display: grid;
grid-template-columns: minmax(0, 1fr) 380px;
gap: 32px;
- align-items: start;
+ // Default `stretch` behaviour: the right column grows to the row's full
+ // height (matching the configurator column). Combined with sticky on
+ // .detail-grid__summary that gives us a sidebar that stays in view all
+ // the way down through the configurator scroll.
@media (max-width: 1024px) {
grid-template-columns: 1fr;
@@ -225,10 +225,19 @@ const store = useDedicatedConfiguratorStore()
}
.detail-grid__summary {
- position: relative;
+ position: sticky;
+ // 64px navbar (Vuetify VAppBar default) + 16px breathing room.
+ top: 80px;
+ align-self: start; // stop the cell from stretching the inner card vertically
+ max-height: calc(100vh - 96px);
+ overflow-y: auto;
@media (max-width: 1024px) {
- order: -1; // on mobile, show summary above configurator so total is visible first
+ position: relative;
+ top: auto;
+ max-height: none;
+ overflow-y: visible;
+ order: -1; // mobile: summary above configurator so total is visible first
}
}
diff --git a/website/resources/ts/Pages/Marketing/DedicatedServers.vue b/website/resources/ts/Pages/Marketing/DedicatedServers.vue
index 0e8c254..e9184e1 100644
--- a/website/resources/ts/Pages/Marketing/DedicatedServers.vue
+++ b/website/resources/ts/Pages/Marketing/DedicatedServers.vue
@@ -102,7 +102,7 @@ const faqs = [
},
{
question: 'How do setup fees work?',
- answer: 'Setup fees range from $349 to $799 depending on chassis class (covers our hardware-acquisition cost). They\'re charged on monthly and quarterly cycles, waived on Semi-Annual and Annual commitments. Once we\'ve placed the hardware order with our supplier (typically within 24 hours of your order), the setup fee is non-refundable. Rental fees still fall under the 14-day money-back guarantee.',
+ answer: 'Setup fees range from $349 to $799 depending on chassis class. They cover the work of building, racking, configuring iDRAC, and deploying your server. They\'re charged on monthly and quarterly cycles, waived on Semi-Annual and Annual commitments. Once your build starts (typically within 24 hours of your order), the setup fee is non-refundable. Rental fees still fall under the 14-day money-back guarantee.',
},
{
question: 'What operating systems are supported?',
diff --git a/website/tests/Feature/Marketing/DedicatedServersTest.php b/website/tests/Feature/Marketing/DedicatedServersTest.php
index 258fa0a..db2e6ed 100644
--- a/website/tests/Feature/Marketing/DedicatedServersTest.php
+++ b/website/tests/Feature/Marketing/DedicatedServersTest.php
@@ -115,19 +115,40 @@ test('non-R740xd chassis get the standard CPU group', function (): void {
expect($names)->not->toContain('Dedicated 14th Gen — CPU Upgrade (R740xd)');
});
-test('seeder creates the 6 dedicated 14th-gen config groups', function (): void {
+test('seeder creates the 9 dedicated 14th-gen config groups', function (): void {
$names = PlanConfigGroup::query()
->where('name', 'like', 'Dedicated 14th Gen%')
->pluck('name')
->all();
- expect(count($names))->toBe(6);
+ expect(count($names))->toBe(9);
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade');
expect($names)->toContain('Dedicated 14th Gen — CPU Upgrade (R740xd)');
expect($names)->toContain('Dedicated 14th Gen — RAM Upgrade');
expect($names)->toContain('Dedicated 14th Gen — Operating System');
expect($names)->toContain('Dedicated 14th Gen — Bandwidth');
expect($names)->toContain('Dedicated 14th Gen — IPv4 Block');
+ expect($names)->toContain('Dedicated 14th Gen — LFF Drive Bays');
+ expect($names)->toContain('Dedicated 14th Gen — SFF Drive Bays');
+ expect($names)->toContain('Dedicated 14th Gen — NVMe Drive Bays');
+});
+
+test('drive bay groups are attached to chassis by bay type', function (): void {
+ // LFF chassis should have LFF Drive Bays attached
+ $r440 = Plan::where('slug', 'r440-4lff')->first();
+ expect($r440->configGroups->pluck('name'))->toContain('Dedicated 14th Gen — LFF Drive Bays');
+ expect($r440->configGroups->pluck('name'))->not->toContain('Dedicated 14th Gen — SFF Drive Bays');
+ expect($r440->configGroups->pluck('name'))->not->toContain('Dedicated 14th Gen — NVMe Drive Bays');
+
+ // SFF chassis should have SFF Drive Bays
+ $r640 = Plan::where('slug', 'r640-8sff')->first();
+ expect($r640->configGroups->pluck('name'))->toContain('Dedicated 14th Gen — SFF Drive Bays');
+ expect($r640->configGroups->pluck('name'))->not->toContain('Dedicated 14th Gen — LFF Drive Bays');
+
+ // NVMe chassis should have NVMe Drive Bays
+ $nvme = Plan::where('slug', 'r740xd-24nvme')->first();
+ expect($nvme->configGroups->pluck('name'))->toContain('Dedicated 14th Gen — NVMe Drive Bays');
+ expect($nvme->configGroups->pluck('name'))->not->toContain('Dedicated 14th Gen — SFF Drive Bays');
});
test('RAM upgrade group standardizes on DDR4-2400 across all tiers', function (): void {