Files
Nathanwoodburn.github.io/templates/hosting.html
Nathan Woodburn 372ba908b8
All checks were successful
Build Docker / BuildImage (push) Successful in 48s
feat: Add tools to navbar
2025-10-26 19:36:25 +11:00

391 lines
18 KiB
HTML

<!DOCTYPE html>
<html data-bs-theme="light" lang="en-au" style="height: 100%;">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Nathan.Woodburn/</title>
<meta name="theme-color" content="#000000">
<link rel="canonical" href="https://nathan.woodburn.au/hosting">
<meta property="og:url" content="https://nathan.woodburn.au/hosting">
<meta name="fediverse:creator" content="@nathanwoodburn@mastodon.woodburn.au">
<meta name="twitter:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
<meta property="og:title" content="Nathan.Woodburn/">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
<meta property="og:type" content="website">
<meta name="twitter:title" content="Nathan.Woodburn/">
<meta property="og:description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
<meta name="description" content="G'day, this is my personal website. You can find out about me or check out some of my projects.">
<meta property="og:image" content="https://nathan.woodburn.au/assets/img/profile.jpg">
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="180x180" href="/assets/img/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/img/favicon/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="512x512" href="/assets/img/favicon/android-chrome-512x512.png">
<link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css">
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic&amp;display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cabin:700&amp;display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Anonymous+Pro&amp;display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&amp;display=swap">
<link rel="stylesheet" href="/assets/fonts/font-awesome.min.css">
<link rel="stylesheet" href="/assets/css/styles.min.css">
<link rel="stylesheet" href="/assets/css/brand-reveal.min.css">
<link rel="stylesheet" href="/assets/css/profile.min.css">
<link rel="stylesheet" href="/assets/css/Social-Icons.min.css">
<link rel="me" href="https://mastodon.woodburn.au/@nathanwoodburn" />
<script async src="https://umami.woodburn.au/script.js" data-website-id="6a55028e-aad3-481c-9a37-3e096ff75589"></script>
</head>
<body id="page-top" data-bs-spy="scroll" data-bs-target="#mainNav" data-bs-offset="77" style="display: flex;flex-direction: column;">{{handshake_scripts | safe}}
<nav class="navbar navbar-expand-md fixed-top navbar-light" id="mainNav" style="background: var(--bs-navbar-hover-color);">
<div class="container-fluid"><a class="navbar-brand" href="/#">
<div style="padding-right: 1em;display: inline-flex;">
<div class="slider"><span>/</span></div><span class="brand">Nathan.Woodburn</span>
</div>
</a><button data-bs-toggle="collapse" class="navbar-toggler navbar-toggler-right" data-bs-target="#navbarResponsive" type="button" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation" value="Menu"><i class="fa fa-bars"></i></button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto">
<li class="nav-item nav-link"><a class="nav-link" href="/">Home</a></li>
<li class="nav-item nav-link"><a class="nav-link" href="/hosting">Hosting</a></li>
<li class="nav-item nav-link"><a class="nav-link" href="/projects">Projects</a></li>
<li class="nav-item nav-link"><a class="nav-link" href="/tools">Tools</a></li>
<li class="nav-item nav-link"><a class="nav-link" href="/blog">Blog</a></li>
<li class="nav-item nav-link"><a class="nav-link" href="/now">Now</a></li>
</ul>
</div>
</div>
</nav>
<section class="text-center content-section" style="background: #110033;padding-bottom: 100px;flex: 1;display: flex;flex-direction: column;justify-content: center;">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto">
<h2>Web Hosting</h2>
<p style="margin-bottom: 10px;">Configure your hosting setup below</p><form id="costForm">
<div style="padding: 10px;">
<label style="font-weight: bold; margin-bottom: 15px; display: block;">Choose a plan:</label>
<div style="display: flex; gap: 15px; flex-wrap: wrap; margin-top: 10px;">
<div class="plan-card" onclick="selectPreset('standard')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
<input type="radio" name="preset" value="standard" onchange="handlePresetChange()" checked style="margin-bottom: 10px;">
<h4 style="margin: 0 0 10px 0; color: #333;">Standard WordPress</h4>
<div style="font-size: 24px; font-weight: bold; color: #007bff; margin-bottom: 10px;">$6/month</div>
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
<li style="margin-bottom: 5px;">✓ 1 CPU Core</li>
<li style="margin-bottom: 5px;">✓ 0.5GB RAM</li>
<li style="margin-bottom: 5px;">✓ 10GB Storage</li>
<li style="margin-bottom: 5px;">✓ Perfect for small sites</li>
</ul>
</div>
<div class="plan-card" onclick="selectPreset('premium')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
<input type="radio" name="preset" value="premium" onchange="handlePresetChange()" style="margin-bottom: 10px;">
<h4 style="margin: 0 0 10px 0; color: #333;">Premium WordPress</h4>
<div style="font-size: 24px; font-weight: bold; color: #28a745; margin-bottom: 10px;">$10/month</div>
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
<li style="margin-bottom: 5px;">✓ 1 CPU Core</li>
<li style="margin-bottom: 5px;">✓ 1GB RAM</li>
<li style="margin-bottom: 5px;">✓ 50GB Storage</li>
<li style="margin-bottom: 5px;">✓ Weekly Backups Included</li>
</ul>
</div>
<div class="plan-card" onclick="selectPreset('custom')" style="border: 2px solid #ddd; border-radius: 8px; padding: 20px; cursor: pointer; flex: 1; min-width: 250px; background: white; transition: all 0.3s ease;">
<input type="radio" name="preset" value="custom" onchange="handlePresetChange()" style="margin-bottom: 10px;">
<h4 style="margin: 0 0 10px 0; color: #333;">Custom Configuration</h4>
<div style="font-size: 24px; font-weight: bold; color: #6c757d; margin-bottom: 10px;">Variable</div>
<ul style="list-style: none; padding: 0; margin: 0; color: #666;">
<li style="margin-bottom: 5px;">✓ Choose your CPU</li>
<li style="margin-bottom: 5px;">✓ Choose your RAM</li>
<li style="margin-bottom: 5px;">✓ Choose your Storage</li>
<li style="margin-bottom: 5px;">✓ Optional Backups</li>
</ul>
</div>
</div>
</div>
<div id="customSliders" style="display: none;">
<div>
<label class="form-label" for="cpus" style="padding: 10px;">CPUs:
<span id="cpusValue" style="display:inline-block; min-width: 3ch; font-family: monospace;">1</span>
</label>
<input id="cpus" name="cpus" class="form-range" type="range"
min="1" max="4" value="1" step="1"
style="max-width: 500px; padding-top: 10px;"
oninput="updateValue('cpus')">
</div>
<div>
<label class="form-label" for="memory" style="padding: 10px;">Memory (GB):
<span id="memoryValue" style="display:inline-block; min-width: 5ch; font-family: monospace;">0.5</span>
</label>
<input id="memory" name="memory" class="form-range" type="range"
min="0" max="6" value="0" step="1"
style="max-width: 500px; padding-top: 10px;"
oninput="updateValue('memory')">
</div>
<div>
<label class="form-label" for="disk" style="padding: 10px;">Disk Space (GB):
<span id="diskValue" style="display:inline-block; min-width: 5ch; font-family: monospace;">10</span>
</label>
<input id="disk" name="disk" class="form-range" type="range"
min="10" max="500" value="10" step="10"
style="max-width: 500px; padding-top: 10px;"
oninput="updateValue('disk')">
</div>
<div style="padding: 10px;">
<label><input type="checkbox" id="backups" onchange="calculateCost()"> Backups ($5/month for up to 150GB)</label>
</div>
</div>
<div style="padding: 10px; font-weight: bold;">
Monthly Cost: $<span id="monthlyCost" style="display:inline-block; min-width: 6ch; font-family: monospace; text-align: left;">0.00</span><br>
<small>All prices are in AUD</small>
<br>
<input type="email" id="email" name="email" class="form-control" placeholder="Your Email" required style="margin-top: 10px;">
<!-- Allow message -->
<textarea id="message" name="message" class="form-control" placeholder="Your Message" rows="3" style="margin-top: 10px;"></textarea>
<button type="button" class="btn btn-primary" style="margin-top: 10px;" onclick="sendEnquiry()">Send Enquiry</button>
<small id="enquiryStatus" style="display: block; margin-top: 10px;"></small>
</div>
</form>
<script>
// Rates
const cpuRate = 3;
const memoryRate = 5;
const diskRate = 0.1;
const backupRate = 1;
// Memory values mapping
const memoryValues = [0.5, 1, 2, 4, 8, 16, 24];
// Initialize on load
document.addEventListener('DOMContentLoaded', () => {
// Check if parameters are in the URL
const urlParams = new URLSearchParams(window.location.search);
// Check for preset parameter
if (urlParams.has('preset')) {
const presetValue = urlParams.get('preset');
const presetRadio = document.querySelector(`input[name="preset"][value="${presetValue}"]`);
if (presetRadio) {
presetRadio.checked = true;
handlePresetChange();
}
}
if (urlParams.has('cpus')) {
document.getElementById('cpus').value = urlParams.get('cpus');
}
if (urlParams.has('memory')) {
const memoryParam = parseFloat(urlParams.get('memory'));
const memoryIndex = memoryValues.indexOf(memoryParam);
document.getElementById('memory').value = memoryIndex >= 0 ? memoryIndex : 0;
}
if (urlParams.has('disk')) {
document.getElementById('disk').value = urlParams.get('disk');
}
if (urlParams.has('backups')) {
document.getElementById('backups').checked = urlParams.get('backups') === 'true';
}
if (urlParams.has('email')) {
document.getElementById('email').value = urlParams.get('email');
}
if (urlParams.has('message')) {
document.getElementById('message').value = urlParams.get('message');
}
// Update the displayed values
updateValue('cpus');
updateValue('memory');
updateValue('disk');
calculateCost();
// Send the enquiry if the form is submitted
document.getElementById('costForm').addEventListener('submit', function(event) {
event.preventDefault();
sendEnquiry();
});
// Initialize preset handling and card styles
handlePresetChange();
updateCardStyles();
});
function updateValue(field) {
let value = document.getElementById(field).value;
if (field === 'memory') {
value = memoryValues[parseInt(value)];
}
document.getElementById(field + 'Value').textContent = value;
calculateCost();
}
function selectPreset(presetValue) {
document.querySelector(`input[name="preset"][value="${presetValue}"]`).checked = true;
handlePresetChange();
updateCardStyles();
}
function updateCardStyles() {
const cards = document.querySelectorAll('.plan-card');
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
cards.forEach((card, index) => {
const presetValues = ['standard', 'premium', 'custom'];
const isSelected = presetValues[index] === selectedPreset;
if (isSelected) {
card.style.borderColor = '#007bff';
card.style.background = '#f8f9fa';
card.style.transform = 'translateY(-2px)';
card.style.boxShadow = '0 4px 12px rgba(0,123,255,0.15)';
} else {
card.style.borderColor = '#ddd';
card.style.background = 'white';
card.style.transform = 'translateY(0)';
card.style.boxShadow = 'none';
}
});
}
function handlePresetChange() {
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
const customSliders = document.getElementById('customSliders');
updateCardStyles();
if (selectedPreset === 'custom') {
customSliders.style.display = 'block';
} else {
customSliders.style.display = 'none';
// Set preset values
if (selectedPreset === 'standard') {
document.getElementById('cpus').value = 1;
document.getElementById('memory').value = 0; // 0.5GB
document.getElementById('disk').value = 10;
document.getElementById('backups').checked = false;
} else if (selectedPreset === 'premium') {
document.getElementById('cpus').value = 1;
document.getElementById('memory').value = 1; // 1GB
document.getElementById('disk').value = 50;
document.getElementById('backups').checked = true;
}
// Update displayed values
updateValue('cpus');
updateValue('memory');
updateValue('disk');
}
calculateCost();
}
function calculateCost() {
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
// For presets, use fixed pricing
if (selectedPreset === 'standard') {
document.getElementById('monthlyCost').textContent = '6.00';
return;
} else if (selectedPreset === 'premium') {
document.getElementById('monthlyCost').textContent = '10.00';
return;
}
// Custom calculation
const cpus = parseFloat(document.getElementById('cpus').value);
const memoryIndex = parseInt(document.getElementById('memory').value);
const memory = memoryValues[memoryIndex];
const disk = parseFloat(document.getElementById('disk').value);
let monthlyCost = (cpus * cpuRate) + (memory * memoryRate) + (disk * diskRate);
if (document.getElementById('backups').checked) {
const backupUnits = Math.ceil(disk / 50);
monthlyCost += backupUnits * backupRate;
}
document.getElementById('monthlyCost').textContent = monthlyCost.toFixed(2);
}
function sendEnquiry() {
// Ensure that the email field is filled
const email = document.getElementById('email').value;
if (!email) {
document.getElementById('enquiryStatus').textContent = 'Please enter your email address.';
document.getElementById('enquiryStatus').style.color = 'red';
return;
}
// Collect form data
const selectedPreset = document.querySelector('input[name="preset"]:checked').value;
const cpus = document.getElementById('cpus').value;
const memoryIndex = parseInt(document.getElementById('memory').value);
const memory = memoryValues[memoryIndex];
const disk = document.getElementById('disk').value;
const backups = document.getElementById('backups').checked ? 'Yes' : 'No';
const message = document.getElementById('message').value;
const enquiryData = {
email: email,
preset: selectedPreset,
cpus: cpus,
memory: memory,
disk: disk,
backups: backups,
message: message
};
// Send the enquiry data to the server
fetch('/hosting/send-enquiry', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(enquiryData)
})
.then(response => {
if (response.ok) {
document.getElementById('enquiryStatus').textContent = 'Enquiry sent successfully!';
document.getElementById('enquiryStatus').style.color = 'green';
calculateCost();
} else {
document.getElementById('enquiryStatus').textContent = 'Failed to send enquiry. Please try again.';
document.getElementById('enquiryStatus').style.color = 'red';
}
})
.catch(error => {
console.error('Error sending enquiry:', error);
document.getElementById('enquiryStatus').textContent = 'Error sending enquiry. Please check your network connection.';
document.getElementById('enquiryStatus').style.color = 'red';
});
}
</script>
</div>
</div>
</div>
</section>
<footer style="background: #110033;">
<div class="container text-center">
<div class="row">
<div class="col">
<p class="copyright">Copyright ©&nbsp;Nathan.Woodburn/ 2025</p>
</div>
</div>
</div>
</footer>{{custom | safe}}
<script src="/assets/bootstrap/js/bootstrap.min.js"></script>
<script src="/assets/js/script.min.js"></script>
<script src="/assets/js/grayscale.min.js"></script>
<script src="/assets/js/hacker.min.js"></script>
</body>
</html>